Skip to content

  • Projects
  • Groups
  • Snippets
  • Help
  • This project
    • Loading...
  • Sign in / Register
T
Tidal
  • Overview
    • Overview
    • Details
    • Activity
    • Cycle Analytics
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Charts
  • Issues 0
    • Issues 0
    • List
    • Board
    • Labels
    • Milestones
  • Merge Requests 0
    • Merge Requests 0
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
    • Charts
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Charts
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • PLN
  • Tidal
  • Repository

Switch branch/tag
  • Tidal
  • armada
  • ui
  • src
  • kits
  • Waveform.tsx
Find file
BlameHistoryPermalink
  • PLN (Algolia)'s avatar
    feat(cale): a page for hearing whether a kit is any good · ec200263
    PLN asked for an SPA to audition the cut samples, "click-based looping not just
    strudel coding". La Cale — `armada/ui/kits.html`, the hold, where the cargo gets
    checked before it goes on stage.
    
    The reason it needs to exist is that a grade in a manifest is a claim, and this
    week the claim has been wrong twice in ways no report caught: a loop that fades to
    silence grades S, and 24 loops of pure digital silence graded A. Both are obvious
    in two seconds of listening and invisible in a table.
    
    **Auditioning is two different tests, so it is two buttons.** Play-once gives you
    the file untouched — attack, tail, what it actually is. Loop plays it seamlessly at
    rate 1, which is the only way to hear the seam, and the seam is the thing that
    decides whether a loop is usable because it is the sound the audience gets every
    bar. One AudioBufferSourceNode with `loop = true`, so drift cannot exist and
    nothing is resampled between the file and the speaker.
    
    **The rack is where a kit is actually judged**, because the questions that matter —
    do these two loops sit together, does this vocal survive on that break, is the bass
    really at this tempo — cannot be answered one file at a time. It schedules every
    voice from a look-ahead clock against an absolute grid, at
    
        rate = buffer.duration / (bars * 4 * 60 / bpm)
    
    which is exactly `loopAt bars`. So the layering you hear is the layering the rig
    plays, and the exported block is not an approximation of the session — it is the
    same two numbers in Tidal's syntax. Scheduling per bar rather than letting nodes
    free-run matters more than it sounds: the cutter's bar tolerance is 1 ms, which is
    60 ms of skew after a minute.
    
    The grid means what Tidal means. A bar-loop row has cells only on multiples of its
    own length — a 4-bar loop in an 8-bar phrase has two entries, not eight, because
    firing it on bar 3 plays its bar 1 under the phrase's bar 4 — and those cells
    become `mask "t f"`. A chop row is 16 steps in a bar and becomes `s "x ~ ~ x …"`.
    
    Three things the export gets right that are easy to get wrong, all pinned by tests:
    the fader is linear but Tidal's gain is quartic, so 0.5 exports as `gain 0.84` and
    copying it across would be a 12 dB error; families land on their own `dN` with no
    two voices sharing an orbit; and the mask samples the grid on the loop's stride,
    not every bar, which otherwise silences an entry that is switched on.
    
    Deliberately not wavesurfer, though the project already depends on it: a kit page
    shows a hundred rows, and a hundred instances means a hundred fetches and decodes
    to draw something 200 px wide. `kitindex.py` precomputes a 128-point envelope per
    sample, so a row costs one `<svg>` and no network — audio is fetched only when
    something is played. Same ship-the-index instinct as the tide-table.
    
    `kitindex.py` merges two sources on purpose. The FOLDER is the truth about what
    exists (via `pvbanks.playable`, so it agrees with kitgate and the watcher), which
    is why the 111 kits that predate the Foundry are browsable too. A cut MANIFEST is
    the truth about what a file MEANS — tier, family, bars, bpm, CLAP tags — and none
    of it is re-derived here, so the page and the gate cannot disagree. `n` comes from
    the folder listing, never the manifest order, because `n` is what SuperDirt plays.
    
    Two staleness traps closed while building it: the envelope cache is keyed on
    path+size+mtime, so a restaged kit misses it (a stale envelope over fresh audio is
    a lie that looks like data), and the index is written to `dist/` as well as
    `public/`, because `npm run build` copies public/ once and the LAN page would
    otherwise show an index from before the last cut.
    
    Also: the dev server hardcoded `Content-Type: audio/flac` for everything under
    /audio. The masters are FLAC; the kits are WAV. Now by extension, matching
    serve.py's `guess_type`.
    
    13 vitest cases on the export; `npm test`. Verified end to end through the real
    path — `serve.py --dir ui/dist` returns 200 on /kits.html and 206 `audio/x-wav`
    on a Range request through the new `samples` mount.
    PLN (Algolia) authored Aug 21, 2026
    ec200263
Waveform.tsx 2.13 KB
EditWeb IDE
×

Replace Waveform.tsx

Attach a file by drag & drop or click to upload


Cancel
A new branch will be created in your fork and a new merge request will be started.