- 21 Aug, 2026 40 commits
-
-
TODO.md: the Fred section moves from "RESUME HERE" to shipped, with the gate numbers, the six-cuts-five-discarded history, and the audition-first list of 6 kitcheck advisories that are now waiting on PLN's ears rather than on any tool. completed-archive.md: #21 (La Cale) and #20b (the pack) as standalone entries. The learnings worth carrying: the rack had to BE `loopAt` rather than resemble it; a gate blocks on defects and reports properties, because blocking on a property is a gate everybody learns to ignore; `n` is the folder's index, never the manifest's; a rounded tempo is a stretched loop; and three tests that passed while proving nothing, which was most of the day's actual value.
PLN (Algolia) authored -
A parallel session in this shared checkout committed `027-the-instruments-were-the- broken-part.md` at 00:05, but 027 was taken at 17:34 by `027-the-label-and-the-lens.md` and 028 by `028-la-cale-and-the-empty-loop.md`. Renumbered to the next free slot; content untouched, only the filename and the heading. The captain's log is a sequence on purpose — it is the documentary's spine — so two entries sharing a number is not cosmetic. Worth noting that a shared working tree makes this collision structural rather than careless: neither session could see the other's number until both had committed. A worktree per session avoids the `git add` race but not this.
PLN (Algolia) authored -
Achievement log for the night before CosmicFest, and the board amendment that carries the resume points a cold session will need. The log's through-line is not the LED daemon; it is that FOUR separate instruments returned confident wrong numbers in one evening — `ps -o %cpu` summed as if it were instantaneous (it is a lifetime average; the main pulsar process read 2.1% while one child renderer burned 112%), `pw-top`'s ERR column read as current when it is cumulative (delta over 25 s: zero on every node), a shell path built as `live/$f` when `$f` already began `live/`, and a `grep -cE '[1-9] move'` that silently skips `10 move`. Three of the four returned a plausible number rather than an error, which is the whole problem: a wrong tool is rarely silent-and-empty, it is confident-and-slightly-off. Board amendment records what the next session must not redo: * The highlighter is EXONERATED by its own instrument — 5 RAF/s x 1.671 ms = 0.8% of a core, `markers 293 live, creates/destroys 0/s` over 4h42m. The marker leak is dead in the field. Do NOT re-open that hunt. * Five hypotheses for the ~112% renderer are already dead, each with its evidence, so they are not re-tried: editor-background (disabled), sound-browser rAF loops (all terminate), fork-per-LED (2.8 ms p50, bench p99 18 ms zero overrun), aseqdump pipe buffering (disproved on an isolated client). * gig-up is 5 blockers -> 1, and the survivor is PLN's decision to defer 20 column moves rather than renumber knobs hours before playing. * CosmicFest EXISTS (PLN's own words) but has no canonical www page. The date and venue must come from him, never backfilled from eb9221de. * Parked and flagged: 3205e7f unpushed in the pulsar-tidalcycles fork, sound-browser.js's 144 unvalidated insertions, backlog.md's +60 lines, and a one-line hello_fred.tidal stub.
PLN (Algolia) authored -
150 samples in 43 kits from Fred again..'s stem pack, staged at Samples/Fred/output/ and linked into Dirt-Samples, plus the page for deciding whether any of it is good. The pack took six cuts. Each discarded run was discarded because looking at its output found something: dead bars grading S, one rotation pass leaving 1 loop in 6 on a weak beat, then the big one — 24 of 153 loops were digital silence at -104…-75 dBFS, graded A and B, because every term in the loop rubric is vacuously perfect on nothing. Presence is a precondition now, at the grader and before the finder ranks. La Cale (armada/ui/kits.html) plays rate = dur / (bars x barlen), which is literally loopAt, so what you hear is what the rig does and the exported .tidal block is the same two numbers in Tidal's syntax. Python suite 112 -> 128; new vitest suite 24 cases.
PLN (Algolia) authored -
TASKS_DUMP.md opens by saying the Task API was unreachable, so the tree had to be rebuilt from git + memory + the task logs. That was true of the tools and false of the data: a near-limit checkpoint of this session had already written the whole TodoWrite state to .claude/RESUME.md — 112 items, 22 done, 7 current, 83 open. The curated epics in this file are about a fifth of the real board. Same failure shape as the three that cost this session hours: an instrument reported absence, and nobody asked whether it could have found the thing if it were there. Reconciled against 2ef09af6..c821bdf5, nine commits a parallel session landed in this checkout while the release thread was busy: * EPIC I, new — rig hardening and the Bridge. gig-up --converge and the gearbox were two owners of one setting with no arbiter, and ppd re-asserts on AC transitions, so a converge done on battery was silently undone by plugging in for the set. check-drift told PLN to delete his set the day before a gig. * CosmicFest prep across 24 track files and a new livecode_parade.tidal — but content/lives/2026/ has no cosmicfest page, so the date is NOT canonical and is not mine to invent. It is now the one blocking question on the board, because a date is what turns every H1 row into a deadline. Also flagged: four of the seven in-progress items are stale-active (the SoundCloud writer, the v4 re-cut, the mix/master/split stack, the per-track AT all landed), and A3-old + A4-Content-ID are superseded duplicates whose only live residue is two safety rules and one sanctioned playlist deletion. Merge candidates, PLN's call, not mine. The 112-item list is NOT durable — .claude/ is untracked and the checkpoint ref expires in about two weeks. Promoting it into this file is now a real task.
PLN (Algolia) authored -
Third round of "screenshot it rather than assume". The tag chips were absolutely positioned inside the waveform cell with a translucent background, so they covered the first ~15% of every envelope — which is the attack of the loop, the one part you look at to decide whether a cut landed on the beat. It read as a gap in the audio, and I nearly went looking for a bug in the envelope generator. Tags now sit on their own 11 px line under the waveform, truncated, with the CLAP confidence in the tooltip. `fred_marea_drums` reads correctly at a glance now: twelve loops, kick transients visible, three `kit` loops separated by @4:31 / @1:45 / @1:12.
PLN (Algolia) authored -
The headroom warning said "pulsar at N% — #7 (highlighter marker pool). This is the JITTER SOURCE; a Window:Reload is the stopgap". I read that, believed it, and spent a chunk of a pre-gig evening hunting a leak that is already fixed. What is actually true as of 2026-08-21: * Both documented marker leaks are FIXED on the running branch — `decorateCodeBlocks` tracks its markers in `blockMarkers` and destroys them (6e9f374, 2026-07-18), and `#createPositionMarkers` destroys the previous row markers before clearing the map (also 6e9f374; the cached-highlight pool was bounded in ba77c4f, 2026-08-03). * PLN's renderer was **3 minutes old and flat at 112%** across five 2 s samples. A leak GROWS. Accumulation cannot explain a fresh process holding steady, so whatever the burn is, it is not the marker pool. * `editor-background` is confirmed in disabledPackages, so it is not that either. The sound-browser's three `requestAnimationFrame(processChunk)` loops all terminate on `currentIndex < total`, so they are bounded too. Also worth recording: `ps -o %cpu` is a LIFETIME AVERAGE, not an instantaneous reading. My first two measurements ("122%", "130%") summed lifetime averages across the process tree and were meaningless; the honest number comes from sampling /proc/<pid>/stat utime+stime twice. The main pulsar process reads 2.1% while a child renderer burns 112% — a tree sum hides which process to look at. So the warning now reports the symptom, says explicitly that the known leaks are fixed and that a fresh hot renderer rules out accumulation, and points at `tidalcycles:highlight-stats` — the package's own live instrument (DOM writes/s, live markers, RAF rate, active events), which is the only thing that can attribute a burn INSIDE the renderer. A warning that names the wrong cause is worse than one that names none: it spends the reader's time defending its guess.PLN (Algolia) authored -
`loopAt` is exact, so a rounded tempo is a stretched loop — and the cheat sheet is where the number gets typed. It reported ANGIE and BIG HEN as "133 bpm" over files cut at 132.51, which plays every one of them 0.4% fast. Two decimals now, and each kit prints its own `setcps` line rather than leaving the ÷60÷4 to the reader: `loopAt` squeezes the file into N bars of the *current* cps, so the cps is half the instruction and omitting it is what makes the rounding bite. Same fix in La Cale's UI, where the transport read 123.05 next to a row reading 123.0 — one `fmtBpm` for the transport, the rack header and every row, because the same tempo printed two ways reads as a disagreement in the data.
PLN (Algolia) authored -
Run 6 through the whole gate. 150 samples in 43 kits across 9 tracks, staged at `~/Work/Sound/Samples/Fred/output/<kit>/` and symlinked into Dirt-Samples, so `s "fred_marea_drums" # n 3` resolves right now. restage --dry-run 0 of 43 kits (idempotent, as designed) grade re-run S×84 A×28 B×38 · manifest matches the audio on disk, 0 mismatches kitcheck 144 clean, 6 advisory, 0 failing · 150 of 150 distinct sounds n indices 43 kit dirs hold exactly their 150 manifest files bank-check "No shadowed bank names. Every `s \"name\"` is unambiguous." rights 43 of 43 third_party, with provenance — per-track release blocks MAREA is the ground truth: it declares `123BPM` in its filenames and the grid read **123.0**. PLEASEMAKEITBETTER read 20% tempo confidence and correctly shipped chops only. Nothing in the pack now sits below **-63.7 dBFS** peak, where run 3 had files at -104. **The gate had to be restructured before it would pass, and not by loosening it.** It blocked all 150 samples on 20 `near-silent` and 17 `mono-incompatible` files, none of which was broken. That is a category error I built in: a kit is gain-staged as a unit and deliberately never boosted, so its quiet members are quiet *on purpose*; and low interchannel correlation in a vocal double or a stereo pad is the width someone paid for. So the rule is now explicit — **the gate blocks on defects, reports properties.** Defects, which block: `clipping`, `empty`, and `mono-incompatible` **on bass only**, where summing to mono on a house PA kills the fundamental and nobody chose that. Properties, which are printed with the numbers that let PLN judge them by eye and go straight into La Cale's audition-first list: 20 near-silent (crest 14-34 dB — sparse, not silent), 17 wide (corr -0.35…+0.19), 2 marginal seams (+7.7 and +6.7 dB, where the threshold for an audible click is +20). Also dropped a false positive: `off-grid` on something the manifest calls a chop. A sub-bar one-shot has no bar grid to be off; `grade` guesses from duration, the manifest knows, so the manifest wins. The 6 kitcheck advisories are all weak bar periodicity on sustained material (pads, keys, synth) plus two MAREA drum loops at 0.08-0.12 against a 0.30 floor — bar-aligned but not repeating units. Those two are the first rows to audition. Blocking a pack on a property is not the cautious direction. It is a gate nobody can pass, which is a gate everybody learns to ignore. Suite 125 → 128.PLN (Algolia) authored -
PLN's set work for CosmicFest, plus one new track and three mechanical fixes found by the pre-gig gate. His edits, across 23 tracks: the ^43/^44 -> ^41 control migration continued by hand (37857225 retired gMask and freed ^41 for d1's gate), and real musical work alongside it — a `resetCycles` head on rose_rouge, new `octersubbus` and `squizbus` sends, a `slow 2 $ ply 2` rework of rose_rouge's ^54 line, gain/lpf/room adjustments, and sample swaps (`n "74"` -> `n "25"` on the Savoy break). New track: live/midi/nova/techno/livecode_parade.tidal. Three fixes from the gate, none of which move a control number, so nothing changes under his hands: * mute map — d10 in something_about_drums declared gM2; the authored map (tools/lcxl_grid.py) puts d4-d12 on gM3. fix-mute-roles.py --apply, then re-run to confirm it converged to 0. * crushbus 101 in ouais_je_funk was shared by d9 and d10, with DIFFERENT ranges (16->1.5 and 16->6) landing on one bus instance — last event wins, so the knob turned for one orbit was heard on the other. d9 moved to its own 91 per the <orbit><slot> convention. * crushbus 41 in ghosts_in_the_toilets was shared by d4 and d7 the same way. d7 moved to 71. pvlint goes 4 errors -> 0 on the setlist. NOT included, deliberately: the remaining 20 column moves (ghosts 10, something_about_drums 7, perfect 2, the_revolution 1). PLN: "migration i unfinishe dgrrr lets not finish it before set for now." Renumbering which physical knob does what in four tracks he plays tomorrow is a muscle-memory change, not a safety fix, and two of the moves cannot resolve mechanically anyway (one overflow -> gSel, one FIXME with no slot). gig-up stays honestly red on `surface grid intact` until it is finished after the gig.
PLN (Algolia) authored -
check-drift flagged 13 uncommitted .tidal files as "the stale-buffer signature" and its remedy section led with `git checkout -- <file>`. I nearly relayed that. All 13 were PLN's own work: the ^43/^44 -> ^41 gMask-retirement migration (37857225 freed ^41 for d1's gate) plus real musical edits — new octersubbus and squizbus lines, `slow 2 $ ply 2` reworks, gain 1.4->1.6, a sample change from n "74" to n "25". `git checkout --` would have destroyed a set the night before the gig, on the advice of a check that was confident and wrong. The script's own design comment already had the right principle: "drift is not control numbers changed, it is control numbers moved AWAY FROM THE GRID". Its per-file test just cannot see direction — it counts CHANGED ^NN lines (>= 4) and ANDs that with a global grid failure. Both were true here for innocent reasons: the grid is unaligned because the migration is HALF-APPLIED, and those files are the half that is done. Volume of change cannot separate a repair from a regression. Only direction can, and migrate-columns --plan already knows it: 0 pending moves for a file means its edits ARE the alignment. So: the DRIFT message now names both hypotheses with the one command that distinguishes them, the grid failure reports migrate-columns' pending count (20 here) and spells out that UNALIGNED is not REGRESSED, and the destructive remedy is behind an explicit "look at the diff first" with this incident as the reason. A gate that recommends data loss on ambiguous evidence is worse than no gate. Still exits non-zero, honestly: 9 knobs are genuinely out of column and 20 moves are genuinely pending. That is a real finding about an incomplete migration, and completing it is a decision about muscle memory that belongs to the person whose hands are on the desk — not something to apply the night before a gig.
PLN (Algolia) authored -
Two gate/monitor fixes found while clearing gig-up blockers the night before a gig. ABSENT IS NOT BROKEN. check-audio-graph hard-FAILED the moment Ardour's Master was on the internal codec, which made the whole pre-gig gate NO-GO while PLN was rehearsing on the laptop jack with the UMC in a bag. PLN: "no umc atm playing on the jack now. we need to be tolerant to various setups." He is right, and the repo already knows the rule — section 3 of this same script downgrades to warn when the interface is absent, and rig.py opens with "REPORT ABSENT AS ABSENT". Section 2 just never got the memo. Severity now depends on whether the UMC EXISTS. No UMC on the bus: warn, say plainly that the last hop cannot be proven until the interface is plugged in, and tell him to re-run at the venue. UMC present and Master still on the codec: that is the real regression this check was written for — Ardour restores its own saved ports and does NOT follow the PipeWire default sink, so it will happily ignore an interface sitting right there. Result: OK (3 warnings) instead of a NO-GO on a legitimate setup. A DEEP QUEUE IS NOT A BUFFER, IT IS A DELAY. PLN: "when i move faders up/down quickly, [it] lags by 500ms+, almost 1s, behind ahah". Chased this to the wrong end twice, worth recording. First guess was fork+exec per LED write — measured it: 2.8 ms p50, 8 ms p99, and --bench shows the coalescer at p99 18 ms with ZERO overrun, so the LED path was never the problem. Faders have no LEDs at all (row D), so a fader sweep does no LED writes; and the Pulsar HUD has no MIDI tap, so it cannot lag on MIDI either. Second guess was aseqdump block-buffering its stdout into a pipe (the exact bug this repo documents for Python) — disproved with an isolated aseqdump client fed by aseqsend: median 40 ms inter-arrival at a 25/s send rate, last event landing BEFORE the last send. It flushes per event. The real seam is the Bridge's SSE fan-out: `queue.Queue(maxsize=512)`. A fader sweep is ~400 CC/s, so 512 deep is 1.3 s of backlog — and once it fills it STAYS full, because drop-oldest holds the buffer AT capacity. Every event the browser renders is then 512 events stale, permanently. The drop policy was already right; the DEPTH was the latency. 512 was picked as "generous" and is really a latency budget nobody priced. Now 64 (~160 ms at that rate) — jitter absorption rather than a queue. Paired with the browser-side coalescing from 72937cc7, the consumer drains far faster than it fills, so it should rarely be reached. Verified: 79/79 bridge tests, check-audio-graph OK on the jack.
PLN (Algolia) authored -
Screenshotted the Fred view instead of trusting it, and it showed two things. The transport read **133 bpm on a 132.5 bpm kit**, because selecting a kit rounded its tempo. Harmless in a display, not harmless here: the rack sets `rate = dur / (bars × barlen)`, so 133 stretches every loop in that kit by 0.4% and the mismatch goes straight into the audio. Half-integer tempos are ordinary in this corpus. No rounding, and the "match this kit" button now compares to 0.01 bpm so it stops offering a value it already has. And `fred_bighen_bass` listed three loops all labelled `bass`, two of them also both "8 bars · 132.5 bpm" — the kit is named for the stem, so the stem name distinguishes nothing. The manifest already knew `start_s`, so rows now carry `@2:14`: where in the source track this loop was cut from, which is the thing you actually want when choosing between two loops off one stem.
PLN (Algolia) authored -
gig-up's "tools executable" gate has been NO-GO on this box, and the reason is the trap the repo already has a lesson about: chmod fixes YOUR tree, not the repo. parvagues-protect.sh and install-protect.sh both ran fine here and were recorded 100644, so a fresh clone gets two non-executable protect scripts — and the gate that catches it was being read as noise because it never went green. Fixed with `git update-index --chmod=+x`, which is the half that was skipped. lcxl-path.py (new in 7bda1ccd) had the same defect and gets the same treatment. Note the gate checks BOTH `-x` on disk and the index mode. Only the index half was failing, which is exactly the failure a local chmod hides.
PLN (Algolia) authored -
Captain's-log entry for the Fred pack and La Cale. The keeper: nobody wrote "prefer silence" anywhere. It fell out of five reasonable measures, each correctly implemented, none of which had an opinion about whether there was any audio in the file. A composite score is a specification of what you want, and every term you weight lightly is a loophole. Second keeper, the one that will save a future session: three tests today went green while proving nothing, and each fake test hid a real question. The finder test used the stem's own beat grid — which finds no beats inside silence, so no window was ever generated there and it passed with the fix disabled. The niceness test looked its hook up in the wrong scope, got None, and fell back to a no-op lambda. Break the fix, watch the test fail, put the fix back.
PLN (Algolia) authored -
`Deck.scheduleBar` mixed the decision (what plays, when) with the audio nodes, so the only rule in the rack with real musical consequence could not be tested without a browser. Extracted as `hitsInBar(voice, barIndex, bpm, swing, bufDur)`. The rule is that a bar-loop starts only on a bar that is a multiple of its own length. Break it and the rack still makes sound — it plays a 4-bar loop's bar 1 underneath the phrase's bar 4, so the groove turns around in the wrong place. That is not a crash, it is a musical wrongness you would find mid-set and probably blame on the sample. 11 cases, including the ones I would otherwise have got wrong: a negative bar index when the transport is nudged backwards, a phrase that runs past bar 8, a cleared cell at the second entry of a 4-bar loop, and the two asymmetries between the modes — a bar-loop is stretched to the DECK tempo (that is what layering means) while a chop never is (a stab is a stab at 90 or 174), and a chop is unconstrained by stride because it is sub-bar by definition. Confirmed the stride tests fail when the rule is disabled: 2 failed / 22 passed. Suite 13 → 24.
PLN (Algolia) authored -
Follow-up to 72937cc7. Having found that the Bridge panel could not see the LED watcher, I went looking for what else was watching the surface. Nothing was — and one of the things that claimed to be could not fail. THE FALSE-GREEN GATE. The pre-gig check for "LCXL -> SC" was: aconnect -l | grep -A3 "Launch Control XL" | grep -q "128:" `grep -A3` matches each of the THREE lines containing "Launch Control XL" and prints three lines after each; the last window runs off the end of the LCXL block and into the next client header, which is literally `client 128: 'SuperCollider'`. So the string "128:" was in the haystack whether or not a single wire existed. This gate has been passing since it was written, for a reason unrelated to what it measures. It would have said green with the desk connected to nothing. Proved rather than argued: recorded the live graph, stripped only the Midi Through -> SC edge from it, and ran both gates against the doctored file. Old gate PASSED. New gate reported "LCXL -> Midi Through, but Through does NOT reach SC" and exited 1. tools/lcxl-path.py replaces it by walking the graph. It has to: the real path here is TWO hops (LCXL 20:0 -> Midi Through 14:0 -> SC in2), so a direct-only test would be wrong in the other direction. It takes an optional recorded-graph file argument precisely so the gate can be tested for FAILURE without unplugging hardware at a venue — the reason the old one was never caught is that nobody could cheaply watch it fail. TWO UNITS NOBODY STARTED. lcxl-leds-watch and midi-autoconnect were both `disabled`, so neither came up at login; they ran only when started by hand. Both now join the auto-start loop. A NEW GATE. "The surface is wired" and "the surface is lit" are different daemons and different failures, and only the first was ever checked. The new "LCXL LEDs" soft check reads: if the board is on the bus, the watcher must be active. Deliberately conditional on the board — at a kitchen table an unplugged desk is normal and must not warn, but at the venue this is the difference between playing blind and not. Verified both new gates in BOTH directions: green as the rig stands, and red with the watcher stopped / the Through edge cut. A gate observed only passing is indistinguishable from the one this commit deletes.
PLN (Algolia) authored -
The Fred section now names every run that was thrown away and what found the fix, because that list is the actual work: 161 samples (dead bars), 153 (rotation), 153 (24 of them digital silence graded A/B), aborted (staging root), 129 (silence filtered at export only, so BIGHEN's tonal kit lost three loops instead of gaining three real ones). Run 6 is the first where the finder itself refuses a silent window. #21 moves to landed with its commands and its three honest gaps — the Strudel tab is text-only, swing is in the engine but not the UI, and a rack cannot be saved as a named preset.
PLN (Algolia) authored -
PLN, mid-cut: "you're hogging the system :(". Nine children at ~88% of a core each put the load average at **15.6 on 16 cores**, and the machine he performs and edits on became unusable. My fault twice over — the default was one subprocess per track regardless of the box, and nothing told the scheduler this work was background. Both fixed at the source rather than by remembering to pass a flag: * `--jobs` defaults to **half the cores, capped at 6**. It is the total that matters, not the track count, and each child peaks near 2 GB. * Children start at **nice 19 and SCHED_IDLE** via a preexec hook, so they only run when nothing else wants the CPU. The nine still saturate the sixteen cores; the editor now wins every scheduling decision, because nothing waits on this work but me. (The live run was reniced in place rather than killed — eight minutes of analysis is not worth a restart.) The test for that hook is worth a note, because the first version of it was fake: it looked the hook up through `_fan_out.__globals__`, where a nested function does not live, got `None`, fell back to a no-op lambda, and passed. Same shape of mistake as the finder test earlier today. So `run_at_idle_priority` is module-level now and the test asserts on a **real child's** reported nice and scheduling policy — verified to read `0 0` without the hook and `19 5` with it. Also in here, from the same run: the presence gate I added to the finder was doing a full `max(abs(window))` per candidate — at 8 bars that is ~700 k samples times thousands of windows, which is most of why this run was slower than the last. It now reads a per-beat peak table computed once per stem. Exact, not approximate: the inter-beat segments partition `[int(times[i]*sr), int(times[e]*sr))` precisely, so it visits the same samples — with a test that checks the two agree for every window rather than trusting the argument. And `kitgate --link` now takes each kit's staging root from the manifest instead of `publish.samples_root()`. A pack cut with `--samples-root` lives beside its source, so the default root would have looked in the wrong place and called every kit missing. Suite 122 → 125.PLN (Algolia) authored -
PLN, the night before a gig: "i dont see midi feedback visuel anymore on the LCXL fix this first plz". The hardware was fine. The daemon was gone. The journal had the whole story and nobody was reading it: lcxl-leds --watch: no LCXL sequencer port; retrying in 30s (x8) Stopping LCXL LED watcher... Stopped LCXL LED watcher. The board was unplugged, the watcher retried, then it was STOPPED — and `Restart=always` does not resurrect a unit somebody stopped. On replug the board came back and the daemon did not. Three independent holes let that become a silent, session-long dark surface: 1. `lcxl-leds-watch` was MISSING from rig.py's SERVICES table, so the Bridge panel showed a fully green rig over a dark board. That is precisely the failure the module's own docstring opens with ("A GREEN UNIT IS NOT SOUND"), one table row away. It was also in neither gig-up.sh nor converge: NOTHING on this box checked whether the surface was lit. 2. The unit was `linked`, not `enabled` — it never started at login. It only ever ran because something started it by hand. 3. `midi-autoconnect` was `disabled` too. The wiring it enforces (LCXL 20:0 -> Midi Through 14:0 -> SuperCollider in2) happened to be intact, so knobs still worked and the fault stayed invisible. But the prescribed fix for the LED stall IS a replug, and a replug drops those connections with nothing to re-apply them. The two failures compound: the remedy for one silently triggers the other, mid-set. Fixed: both units enabled, and the watcher is on the panel with a state that `active` cannot express. It retries forever by design, so "alive" is not "lit" — _leds_state() cross-checks the board via procfs and reports the third case honestly. No board is ABSENT, not broken; board present with the daemon down is "plugged in but DARK, start this", the one combination that was silently wrong. All five services now read green for a reason each, not by omission. Also, the daylight ramp. PLN: "maybe brightness can be leveraged, top brightness always would make more readable signals even in day perfs." The six-step unipolar ramp spent THREE steps on dim shades — exactly the budget that vanishes outdoors. The replacement keeps five steps and puts every one at full brightness by using the two mixed hues the old ramp never touched: red 15 (g0,r3) -> orange 31 (g1,r3) -> amber 63 (g3,r3) -> yellow 62 (g3,r2) -> green 60 (g3,r0) Every value has a component at maximum, so nothing depends on a brightness difference to be legible. It costs one step and gains three readable ones — the same trade he already made for the DJ filters in July ("i agree on clarity > resolution"), which have been all-full-brightness ever since. This just brings the unipolar knobs in line. LCXL_DIM_RAMP=1 restores the old ramp, so a dark-stage revert is one env var and a restart, not a code change. And the gear's MIDI monitor: "super noisy ... cant we do way more dense". "Control change" spent 14 characters saying "CC", and a single fader sweep prepended ~100 near-identical rows. Events now abbreviate (CC/ON/OFF/PB/...) and consecutive events from the same control coalesce into ONE row that updates in place with a x-count — a 100-event sweep is one line, one DOM write per event, no node churn. Row height 20px -> 15px on top of that. Verified: 79/79 bridge tests pass; /api/rig reports all five services up with per-service proof; ramp asserted to have max(g,r)==3 at every step.PLN (Algolia) authored -
`armada/ui/README.md` was still the stock `npm create vite` text after four pages had been built in it, so the one file a cold reader opens first said nothing true. It now lists the five pages and what question each answers, the commands, and the two things that are easy to get wrong: audio is *mounted* through `audio-mounts.json` (read by both vite dev and serve.py, so a URL works in both — a UI that only works under `npm run dev` is a demo), and `kits.json` is generated, uncommitted, and written to `dist/` as well as `public/`. The `packs/` README now says where a pack's audio is staged, since it is no longer one place: kits cut from someone else's stems live beside their source (`Samples/Fred/output/<kit>/`, `--samples-root`) so the pack stays one movable, deletable thing, while ParVagues' own kits sit directly under `Samples/`. The bank name is the directory name either way, so `s "<kit>"` cannot tell the difference. Then I took a headless screenshot of the page instead of assuming it rendered, which found three things a typecheck cannot: a legacy kit's filenames are long enough to truncate with nothing to recover them from (now `title`), the flag filter rendered as a dead `! 0` on every kit with no flags (now hidden), and the empty-rack text ran the full width of the screen.
PLN (Algolia) authored -
Rejecting silent loops at export was the right fix in the wrong place. BIGHEN's strings stem plays in two sections and rests between them, and all six of the windows the finder ranked highest sat inside a rest — so filtering at the gate took `fred_bighen_tonal` from four silent loops down to one real one, when it should have gone to four real ones. `fred_bighen_fx` disappeared entirely for the same reason. The composite is degenerate on silence in the finder too, and for the same reason it was in the grader: a rest is maximally self-similar in the SSM, its seam is immaculate, its zero crossings are trivial. So presence is checked before ranking, on the window itself, sharing `grade.THRESH["empty_dbfs"]` by import so the two stages cannot disagree about what silence is. The test for this took two attempts, and the first one was the interesting one. A stem gated on its own beat grid CANNOT reproduce the bug: `beat_grid` finds no beats in the silence, so no window is ever generated there, and the test passed with the gate disabled — proving nothing. The bug needs a grid derived from ANOTHER stem, which is exactly how `stempack` calls `analyze_stem` and exactly why it does so: sharing a rhythmic track's grid gives every stem windows across the full duration, including the parts where that stem is not playing. Verified the test now fails with the gate off and passes with it on. Suite 119 → 122.
PLN (Algolia) authored -
`public/kits.json` is a 1.2 MB snapshot of audio that lives outside the repo, and `.kitindex-cache.json` is derived from file mtimes — both regenerate in seconds and neither belongs in history. @strudel/web went in while working out whether the sequencer should be a Strudel playground. It should not: the deck already plays the rack with `loopAt` semantics, and a second audio graph would only add a way for the two to disagree about what the same cells mean. The Strudel tab emits text for strudel.cc instead. So the package comes back out — a dependency nothing imports is a false statement about what the project needs, and it would have shipped in the lockfile forever.
PLN (Algolia) authored -
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 -
PLN, 2026-08-21: "i dont use the webui most of the time, tray indicator is the only ui i use for parvagues gear bro". The gearbox shipped with an engine, a CLI and a web panel — and its one everyday surface still said Silent/Cool/Standard/ Extreme/Normal, still wrote governor/EPP/platform_profile itself through `sudo perf-audio`, and was DISABLED at login. So the surface he uses was the one surface that had not moved. What this does * tools/bridge/gearbox.py — the client both faces speak. Names the three gears and the three trims once, reads the persisted state unprivileged, and builds the argv for a switch. rig.py's gear() now delegates to it, so the tray and the web panel cannot drift on what a gear is called. * perf-tray.py — the mode list is gone. In its place: gear (off/standard/ aggressive) and trim (quiet/auto/cool) as two INLINE radio blocks under their own headers, because the separation is the design. A reader who sees one flat list keeps believing there is a single "how fast" dial and that quiet and cool are two words for it; two headers teach the model every time the menu opens. * It no longer writes any thermal knob itself. Switching goes `sudo -n` to the arbiter, which is the point — a tray reaching around it would rebuild the four-writer mess GEARBOX.md removed, in one click. * The desired-mode bookkeeping is gone with it. The gearbox persists the gear and re-asserts it from a udev hook on power-source changes and a system-sleep hook on resume; a second bookkeeper in the tray could only ever disagree. * The checked gear is read from the FILE, never inferred from sysfs. Inference (perf.detect_mode) cannot tell a deliberate gear from power-profiles-daemon having moved the same knobs, which it does by design since the gearbox cooperates with ppd rather than masking it. * "Gear ▸" (the apps and services) is now "Rig ▸": gear is the thermal lever now, and the web panel already calls that collection the rig. * perf-tray.service was never tracked — it lived only in ~/.config. Now in the repo, so the unit and the script it runs can no longer drift apart. The icon carries the lever, so it reads with the menu shut Width = gear, colour = trim, drawn as the badge's border. One element for two axes because the panel renders this at ~22 px and any design with two separate marks turns to mush there. Width is the cue that has to survive a dark room and a peripheral glance ("am I in the gig gear?" is the mid-set question), so gear gets shape and trim, which is set-and-forget, gets colour. Off draws no ring: a bare badge is the honest picture of nothing steering the machine. First attempt used 2 px / 5 px, which looked right in a mockup and VANISHED on the panel — 64→22 px scaling turns a 2 px stroke into 0.7 px of grey. Rendering every combination at 20/24/32 px before believing it gave 6 px / 11 px, and then forced the temperature numeral and the sparkline to shrink with the ring, since an 11 px border over a 30 px numeral clips the digits at exactly the size where aggressive is the gear whose temperature you most want to read. Verified on hardware, through the real menu over its D-Bus interface (Plasma's own protocol), not by calling the Python directly: * live tooltip reads "gear: Standard · Cool · cap 15/35W" * the menu exports two radio groups, correctly checked, headers live * clicking Aggressive -> GEAR=aggressive, RAPL 45 W / 115 W * clicking Standard -> GEAR=standard, back to 15/35 W * clicking Cool -> TRIM=cool, fans 0 -> ~2900 rpm * a CLI-side gear change is picked up by the tray within one refresh * enabled and running under graphical-session.target, so it survives logout Harness caveat worth writing down: the very FIRST dbusmenu Event sent to a never-opened menu landed on the wrong item (asked for Aggressive, got trim Auto). Every subsequent one mapped exactly, from both dbus-send and gdbus. Reading it as provisional ids on a menu Qt has not yet had to show — a human cannot click an item without opening the menu first, which is what the later probes did. 18 tests for gearbox.py (79 in the bridge suite, all green). They defend two things, neither arithmetic: the sudoers contract (the argv IS a security boundary — `--trim cool` must stay two words, and `performance`/`--boot`, both real helper verbs, must never be reachable from a face that only knows gears), and the honest-unset rule (never-engaged is not the same state as off, which is why off had to become an explicit gear).PLN (Algolia) authored -
kitgate blocked the Fred pack and the reason turned out to be much worse than the flag it raised. Measuring the flagged files: 24 of 153 shipped loops have a peak between **-104 and -75 dBFS**. They are not quiet. They are digital silence. And they graded **0.707 to 0.896 — tier A and B**. The mechanism is that every sub-score in the loop rubric is vacuously perfect on nothing. The seam between two silences is immaculate. The DC of silence is exactly zero. Its zero crossings are trivially fine. Its bar length is whatever you cut it to, so it is bar-exact by construction. Level carries 10% of the composite, so a silent window scores 0.90 × perfect + 0.10 × nothing ≈ 0.90 and comes out tier A. The composite had a **degenerate optimum at "no audio at all"**, and the finder, doing exactly its job, walked into it: `fred_marea_bass` shipped three loops and all three were silence, from a stem measuring -22 dBFS RMS at 54% activity. Nothing downstream could catch it either. `_has_dead_bars` compares bars to EACH OTHER, so it finds a loop that fades out and is blind to one that was never there — a uniformly silent clip has a perfectly flat bar profile. This is the same trap as the fade-out loops grading S, one level deeper. So presence stops being a weighted term and becomes a **precondition**: peak below `empty_dbfs` ⇒ flag `empty`, grade 0.0, tier D, and the existing MIN_TIER filter drops it at export. The floor is -60 dBFS, and it is placed rather than tuned: the pack's peaks run -104…-75 and then jump to -47.1, a **28.4 dB empty gap**, so -60 sits in clean air with nothing near it. Three details the measurements forced: * **Presence is measured on the CHANNELS, not the mono sum.** An anti-phase stereo pair sums to exact zero; calling that "empty" would both mis-report it and suppress the mono-incompatible flag, which is the one useful thing to say about it. (Caught by an existing test, which is why it is an existing test.) * **`near-silent` (RMS < -45) is advisory and now excludes `empty`.** RMS on sparse material measures how much silence it contains, not whether it is silent — a hat loop peaking at -14.7 reads -48.8 RMS. Two questions, two lenses. * **`mono-incompatible` no longer fires on silence.** Two independent noise floors are uncorrelated, so silence reads as maximally mono-incompatible; four of MAREA's flagged vox loops were simply empty. `kitcheck` asks the presence question too, from its own code path, sharing the one constant by import so the two lenses cannot drift apart. Also: restage was not idempotent. Export stages in float and writes PCM_24, so the peak read back off disk misses the ceiling by a quantization step, measures a hair hot, and asks to be staged again — by -0.00003 dB, every run, requantizing every file in the kit each time. `DEAD_BAND_DB = 0.05` (inaudible, four orders of magnitude above the quantization floor). The dry run now reports 0 of 42 kits, which is what it was supposed to say in the first place. 7 tests; suite 115 → 119.
PLN (Algolia) authored -
The cheat sheet numbers `n` by enumerating the manifest sorted by filename. SuperDirt numbers it by enumerating the *folder* sorted by filename. Those two agree only for as long as the folder holds exactly the manifest's files — and `export_track` writes into a kit dir it never empties. So re-cutting a pack is quietly unsafe. If a loop that was `00_kick_2b.wav` last run comes out as `00_kick_4b.wav` this run, both files are on disk, the folder listing gains an entry at position 0, and every index from there on addresses the wrong sound. Nothing else notices: the manifest is right, the grades are right, every file is a good loop, and the cheat sheet you paste from looks correct. You find out on stage. Noticed while looking at the run-3 kits and asking whether runs 1 and 2 had left anything behind (they hadn't — all 58 files carried run-3 mtimes). The absence of the bug today is not the absence of the bug. check_indices compares the two listings **index for index**, not as sets, so it distinguishes the two severities the report needs to separate: an orphan that sorts last pollutes the bank but shifts nothing, while one that sorts early breaks addressing from that point on — and it names the index where it breaks. `--prune` deletes what the manifest does not list, scoped to manifest kit dirs and to audio files only. Listing comes from `pvbanks.playable`, so "what SuperDirt will load" keeps one definition across kitgate, bank-check and the watcher — a checker and a loader that disagree here is the whole failure mode. 3 tests (clean / shifting orphan / trailing orphan); suite 112 → 115.
PLN (Algolia) authored -
#20 lands; #21 (auditioner SPA), #22 (acapellas + demucs catches), #23 (audit the existing 163 kits) opened with enough detail to start cold. The Fred pack's finish sequence is written out step by step because the final run is still in flight.
PLN (Algolia) authored -
Periodicity measuring length then role, dead bars grading S, no headroom against amp=gain^4, and 1 loop in 6 opening on a weak beat. Plus the two bugs the checks caught in my own work, which is the argument for having them.
PLN (Algolia) authored -
Chasing "does a shipped loop actually start on its downbeat" and nearly filing a false alarm on the way. First measurement said 17% — but it scored 16 slots with no full-band term while the rotator scores bars*4 slots WITH one, so the disagreement was the instrument's. Factored the scorer out (_downbeat_slot_scores) so the rotation and its verification cannot use lookalike measures, and re-measured with the real one. The bug is real and smaller than the first number: over 60 shipped bar-loops, slot 0 was the best slot in 29 and in the WORSE HALF of slots in 10. Median miss was 0.032 — i.e. usually a near-tie — but one loop in six genuinely opens on a weak beat, which is exactly the "timed ok but cut BCDA/DABC" complaint from 2026-07-11 that this function was written to fix. Mechanism: the roll point is zero-crossing snapped by up to ~10 ms against a 20 ms attack window, and between two near-tied slots that is enough to flip which one wins. On clean synthetic material rotation is idempotent (pass 1 finds slot 4, pass 2 finds 0); on real music the tie-breaking is fragile. So it now checks its own result and repeats, up to 3 passes, keeping a pass only if the measured miss improves. Safe because rotation is free: two rolls compose into one and the guard cannot oscillate. Best slot 29 -> 46 of 60, worse half 10 -> 1, median miss 0.032 -> 0.000. Default is 3 rather than opt-in: it is strictly better and applies to every caller, including export_take, so the GUI's forge gets it too. 112 tests.
PLN (Algolia) authored -
Provenance for a cut that shipped: which stem each loop came from, at what tempo, graded how. The audio stays in Samples/ — this is the record, not a copy.
PLN (Algolia) authored -
Two bugs in one small feature, both mine. The 'worth a second listen' table was appended by a str.replace whose anchor did not match, and I had not asserted on that one — so the code was present, the function computed 39 rows, and the report silently shipped without them. Every other patch in this file asserts its anchor; this is why. And the comparison was at the wrong level. A `hits` kit IS percussion, so CLAP hearing 'a kick drum' in one is agreement, not contradiction — comparing family names instead of their underlying stems reported every kick and snare in the pack as suspicious. Same for fx/tonal/vox, which all map to `other` and genuinely cannot contradict each other. Also adds --report-only, which rebuilds the report and cheatsheet from the manifest. The manifest is the record and those are views of it, so anything that legitimately changes the audio after a cut (restage staging a kit and re-grading it) leaves them describing numbers that are no longer true — and re-cutting a whole pack to refresh a markdown table would be absurd.
PLN (Algolia) authored -
kitgate re-grades the shipped files and compares against what the finder recorded, on the principle that two code paths reaching the same verdict is worth more than one path asserting it. It immediately caught my own bug: staging applied the kit gain at write time, AFTER grading, so the recorded grade described the pre-staging clip. Several sub-scores have a level term, so the two differ — 0.875 recorded against 0.925 on disk — and the gate correctly called that a defect. Staged clips are now re-graded before the manifest is written, so the number stored is the number you get by grading the file. Selection still runs on the pre-staging grades, which is fine and worth stating: the gain is uniform across a kit, so it cannot reorder its members. This is the second time the cross-check has paid for itself in a day — it also caught the stale manifest after restage. A checker that only ever agrees with the thing it checks is not earning anything. Also: the report now lists cuts whose CLAP instrument tag contradicts the kit they landed in. Nothing is re-filed on that basis. For a stem whose filename token is unrecognised there is no producer label to fall back on, so it lands wherever the coarse measurement pointed — MAREA's `marea` stem went into the drums kit while CLAP hears a spoken voice. CLAP is the one signal here that can separate a sung line from a keyboard, so it is used to say what to audition first, not to decide. 24 tests.
PLN (Algolia) authored -
Anyone reading a periodicity number needs to know it was length-biased before the fix and that its floor is per-role — otherwise the vox column looks like a disaster.
PLN (Algolia) authored -
These are stems from a loud commercial master — MAREA's ALL DRUMS and KIT both peak at +0.2 dBFS — so a loop cut from them arrives at full scale. grade already calls that clipping, and SuperDirt cannot use it: amp = gain^4, so `# gain 1.2` is +7.6 dB and the orbit clips the moment you touch it. The house rule is peaks at -6 dBFS at the source and let the amplification happen downstream, so that is where kits land now. Two decisions inside kit_headroom_gain that matter more than the ceiling: NOT per file. One scalar for the whole kit. Normalising each file on its own would make a sparse hat loop exactly as loud as a full drum bus, throwing away the balance that two loops from one kit otherwise have for free — the reason to cut a kit from one track in the first place. NEVER boosts. A kit already below the ceiling is left exactly where it is. A quiet stem is quiet on purpose; lifting it amplifies its noise floor and discards real information about the arrangement. restage.py applies the same function to kits already on disk, so a kit restaged after the fact is bit-identical to one cut with it in place. Useful beyond this pack: hand-cut kits carry whatever level their source had, and there are 163 kits in Samples/. It also re-grades the manifest for anything it changed. Whoever moves the audio owns the record of it — every grade has a level term, so a stale manifest describes bytes that no longer exist, and kitgate compares the two precisely to catch that drift. Verified on the synthetic pack: clipping cleared, two drum loops A -> S, and the gate then caught the stale grades until restage started writing them back. 23 tests in the file.
PLN (Algolia) authored -
Four checks, run before anything is linked into Dirt-Samples. grade is deliberately re-run over the WRITTEN FILES rather than trusted from the finder's in-memory scoring: same verdict by a different code path, so a disagreement means one of them is wrong, which is the entire reason to run it twice. Bank shadowing is the one that would hurt silently. loadSoundFiles defaults to appendToExisting = false, so a new folder whose basename matches an existing bank frees that bank's buffers and takes the name — no error anywhere, and the only symptom is that an old track now plays the wrong sound. Checked BEFORE linking, through tools/pvbanks.py so this and bank-check.py cannot disagree about what a bank is. Rights is advisory and never blocks the load, because loading a kit locally is not releasing a track — but it does say out loud when a kit cut from someone else's stems is not yet ledgered. Self-test on the synthetic pack immediately blocked on clipping in the fixture, which is the fixture's fault and the gate's point.
PLN (Algolia) authored -
Two errors stacked in one metric, both found by looking at its distribution instead of its verdicts. **It penalised short loops for being short.** Autocorrelation at lag k has only N-k overlapping frames, but the score was normalised by ac[0], which sums all N — so the number was partly a function of how much of the loop the lag consumed, and a 1-bar lag consumes half a 2-bar loop against an eighth of an 8-bar one. On a perfectly periodic click track the biased estimator reports 0.418 / 0.623 / 0.754 at 2 / 4 / 8 bars for IDENTICAL material. Dividing by the per-lag overlap count first: 0.834 / 0.830 / 0.862, flat as it must be. Corpus medians moved 0.12 -> 0.24 (2 bar), 0.31 -> 0.41, 0.43 -> 0.49. **And then it asked every role the same question.** Split by family across 81 bar-loops: drums 0.754, fx 0.549, tonal 0.214, bass 0.173, vox 0.087 — an 8.7x spread end to end. A drum loop that does not repeat at the bar is broken. A sung phrase that does not repeat within itself is a sung phrase, and a held chord is a perfectly good loop with no onset period at all. One threshold across all of them condemned 12 of 20 vocal loops for the crime of being vocal. Floors are now per family, and vox is measured-but-never-flagged. Also: dead bars are now rejected at EXPORT rather than reported afterwards. A 4-bar loop whose last two bars are a fade grades S — silent-to-silent is a perfect seam and the length is still an exact bar multiple — and 10 of the first 161 cuts were exactly that. An objective defect with an objective test belongs in the gate, not in a report someone has to read. First full run for the record: 161 samples, 43 kits, ZERO bar-length errors and 161 of 161 distinct sounds, so the bar-multiple guarantee and the export dedup both hold at scale. 21 tests in this file, 104 in the suite.
PLN (Algolia) authored -
Two ways to be wrong about a stem, pulling opposite directions. Plus the README section for stempack/kitcheck and the thresholds, all picked from measured data.
PLN (Algolia) authored -
librosa's peak-picker finds peaks in whatever onset envelope it is handed, flat ones included: a steady 50 Hz sine yields 84 onsets over six seconds (14/s) and was being filed as drums, which made the bass branch unreachable for exactly the signal it was written for. Onset count is now gated on the frame-RMS crest — ~1.0 for a sustained tone against 3.1-9.5 for anything struck. Measured across MAREA/ANGIE/ME to pick the threshold rather than guess it. Worth recording that crest is a SPARSITY measure, not a percussion one — MAREA's single vocal phrase reads 17.98 — so it only ever gates onset density and is never used alone. HPSS was tried first and rejected: a kick-heavy drum bus reads 0.094 percussive, because HPSS hears a modern kick as harmonic. 18 tests.
PLN (Algolia) authored -
Three separate errors, each of which quietly threw away good material. **Where it looked.** Evenly-spaced probe windows sample the gaps. A stem is not stationary — measured on ANGIE, even windows read HIHATS at -60.9 dBFS / 1.4% active where the full file says -48.8 / 9.7%, and missed PAD STUFF's material entirely (0.0% vs 6.9%) because that stem only plays in two sections. Windows are now ranked by energy and chosen where the stem is actually playing. **What it measured.** Usability was gated on overall RMS, which on a sparse stem measures how much silence it contains, not whether it has material in it. ANGIE's HIHATS stem is -48.8 dBFS RMS and a perfectly good hi-hat stem peaking at -14.7. Level and activity now come from the whole file (it is only a frame-RMS pass) and the gate is peak plus activity relative to the stem's own peak. Three ANGIE stems and two MAREA stems come back from the dead. **What it concluded.** "Mostly low energy, low centroid ⇒ bass" fires on a drum bus, because a modern kick carries most of a drum mix's energy: ALL DRUMS reads lf 0.86, centroid 216 Hz, indistinguishable from BASS on those features. So ALL DRUMS was being filed into the bass kit AND mono-summed, and KICK with it. Onset density is asked first now (ALL DRUMS 5.5/s vs BASS 1.1/s), and a `hits` claim survives a bass measurement, because a kick IS low and slow and only the label says it is one hit. And the authority question underneath all of it. The house rule is that a sound's role is never INFERRED from its name; it is not that a producer's label about their own stem is worthless. Those have very different evidential weight. Presence and bass are decided by measurement — a stem under -40 dBFS peak has nothing in it, and "all energy under 150 Hz, nothing above 2 kHz" is unambiguous. Everything else keeps the label, because onset density cannot separate a rhythmic rhodes from a drum bus: MAREA's KEYS2 reads 5.1 onsets/s and its sparse CLAP reads 1.0, so measurement alone files both in the wrong kit, silently. Disagreements are reported, not resolved. Also: register_fred_rights.py records a freshly cut third-party kit in the rights ledger as third_party WITH its provenance, at the moment of cutting. Left alone it would land as `unknown` at the next --init, which blocks correctly but blocks with no reason attached.
PLN (Algolia) authored
-