- 29 Aug, 2026 6 commits
-
-
PLN, auditioning CosmicFest: "at 22:42 its end of reose_rouge, theres a loud blip noise, at 22:44.9 its the start of rose rouge, could we cut noise and blend better?" The lab could only ever answer one question — where does the next track start — so garbage living in the 2.9 s BETWEEN two tracks had nowhere to go. It survived as prose in a comment box, which no downstream tool reads. `n` marks the playhead as noise and deliberately leaves the cut call untouched: a blip between two tracks is not an opinion about where either begins, and forcing it through the cut field would corrupt the one number the mastering chain trusts. `N` clears them, because a mis-tapped defect marker you cannot remove is worse than no marker at all. They render in warm orange, distinct from every existing mark colour: those are all CANDIDATES for a cut, and this one is the opposite — a place to avoid. Exported from both the decided and skipped blocks, so a seam PLN rejects can still carry "and there is garbage at 22:42". That combination is exactly what happened here and exactly what the old export could not represent.
PLN (Algolia) authored -
PLN: "wait you dont get the notes when i skip? DANG they have lots of info." The exporter emitted skipped boundaries as bare integers: skipped: Object.entries(calls).filter(([, c]) => c.t === null).map(([k]) => Number(k)) so eight CosmicFest seams exported as `[2,4,9,10,15,16,17,18]` while the notes he had typed into every one of them vanished. And a skip is not an absence of judgement — it is usually a judgement that the SEAM is wrong, which is more informative than confirming a cut time. What was lost included "theres no blue_gold in this set, we're hearing do_it_right throughout", "there was no gimme_acid in this performance", "theres no funk->piment transition! in this set i playd piment once, then funk", and "it could be called Outro Dub Siren". The notes were never actually gone: the textarea writes `{t: t ?? null, comment}`, so a skipped call keeps its comment in state and in localStorage. Emitting `skipped` as {id: {source, note}} therefore recovers notes ALREADY STORED, not only future ones — PLN re-exports and gets them back verbatim. Verified the built bundle carries it, after first mis-verifying: `grep -c` counts LINES and a minified bundle is one line, so the honest count is 2 occurrences, not 1. With his corrections the true set is 14 tracks, written to judge_specs/cosmicfest-2026_setlist_ear.json as ground truth. Scored against it, the three-lens pipeline named 12 of 14 tracks correctly and labelled 81% of the 63 minutes, 85% of the labelled overlap correct. bombe_dj, rose_rouge, ouais_je_funk, punkachien and livecode_parade landed at 100%. The two failures are the same failure. `perfect` was called gimme_acid and the improvised outro was called REVOLUTION, and both are reference-bound: crossmatch can only choose among tracks it holds a prior render of, so audio from a track it has never heard gets assigned to the nearest thing it has. All five "ghosts" — blue_gold, desire, gimme_acid, quand_on_decolle, REVOLUTION — are that same mechanism. It is a structural limit, and the fix is one render per track, not a better threshold. Also banks his ear-feedback in performance_notes.md, including two things no analysis could have produced: a loud blip in the 2.9 s between two tracks (a defect belonging to NEITHER, which the boundary tool currently has no way to express), and why desire was dropped — "Clearly didnt play desire i was not found of that closing at opal".PLN (Algolia) authored -
PLN, on hearing a rose_rouge sample inside a seam I had labelled quand_on_decolle: "where is our samples mathcing? gosh, i should not do so excruciating details myself. you have all the data bro." He was right. I had 86 offset-consistent detections and printed 4, because `locate` reported each bank's single BEST window and threw away temporal extent — which is the informative part. What was hiding in data I already had: rose -> rose_rouge 22:35-24:00 (max cons 0.81), and on to 31:30 movie_paris -> something_about_drums 6:15 (0.62), 10:30 (0.49) take5 -> take_5_drops 32:10-35:30 love_parade -> livecode_parade 55:20-56:10 So rose_rouge is continuously present 22:35-31:45 — a stretch I had split into quand_on_decolle, blue_gold and UNRESOLVED. And something_about_drums sits in the 5:04-11:33 hole I reported as unclaimed, exactly where PLN said he played it ("im sure i played something about drums between bombe and do it right"). Then his second point, which is the principled fix: "its almost tfidf we should do here. when we know rose sample is around a time slot, its a tell. when we see e.g. _jungle_BreakS_, it's way less idf relevant." Implemented as `fuse`: evidence for track T at time t is the sum, over detected banks that T uses, of offset-consistency divided by that bank's df. A bank in one track hands over all its evidence; a bank in eighty splits it eighty ways. One refinement, from measuring rather than assuming: plain log-IDF is too flat for this. jungle_breaks (df=80) carries 0.50x the weight of rose (df=2) — a 2x penalty for being 40x more common, which would still let it dominate since it fires constantly. 1/df gives 0.0125 vs 0.50. Two normalisations that are not decoration. Per track, divide by the total 1/df its banks could contribute, or a track referencing many rare banks beats a track with one perfect tell — the reference-size bias in a third costume. And close gaps before deciding: a rare sample fires intermittently INSIDE the track that owns it, so `rose` at 22:35, 25:15, 28:15, 30:00, 31:10 is one track, not five appearances. A NEGATIVE result worth recording. Weighting let MAX_DF rise from 6 to 40, taking fingerprint coverage from 15/23 to 23/23 candidate tracks — and it added no evidence at all: every new bank scored consistency 0.02-0.21 against a 0.30 threshold. I guessed reference duration was the binding constraint and that was WRONG: correlation with best consistency is -0.144, and the three longest references are among the worst detectors. Then the confound surfaced — suns_keys belongs to blue_gold, which PLN did not play, so its low score is a TRUE NEGATIVE. Failures and true negatives are indistinguishable without the setlist, so the reason remains unestablished rather than explained away. Also fixes two things that wasted PLN's time. The Boundary Lab query key is `set`, not `gig` — bounds.tsx does get('set') ?? 'opal-festival-2026', so a wrong key silently loads the DEFAULT document, and he auditioned OPAL's Sunset Forest seams believing they were CosmicFest. The variable there is named `gig`, which is exactly how I made the mistake: grepping found the word, not the key. And the generator offered "bombe_dj -> bombe_dj" as cut #1, a seam between a track and itself, because it read the un-merged segment file.PLN (Algolia) authored -
PLN: "dotn we have a webapp for listening to seams in a set? you could use it for me to confirm your labels. reusable tooling pattern." It does — armada/ui/bounds.html, the Boundary Lab. So this builds no second page. make_bounds.py generates the document that page already reads, which means the confirmation loop is the one that already stores calls per gig in localStorage and already exports boundaries-<gig>.json. That direction is the point. The three-lens fusion is a machine opinion: 11 of 18 segments have two lenses behind them, 4 are honestly unresolved, and the two rows that were WRONG were caught by PLN's memory rather than by any score. An analysis pipeline whose output lands in a report has nowhere to be corrected. The contract was read from BoundaryLab.tsx, not assumed, and two details would have been wrong if guessed: marks[].t is in MASTER time (the page subtracts clipStart itself), and `url` is handed to WaveformPlayer with dur = clipEnd - clipStart, so it must point at a CLIP of that length rather than at the whole recording. Mark keys are colour-coded by the page, so the segments where a lens OVERRODE the embedding get `takeover` — brand magenta, which its own comment calls "the one to try first" — because that is exactly where an ear call is worth most. Clips plus a mount rather than the whole file, following audio-mounts.json's own reasoning: it maps /audio/<prefix>/ for BOTH vite dev and armada/serve.py deliberately, since "a UI that only works under npm run dev is a demo, not a tool". A 40s clip also loads instantly on a phone over LAN, which is where this gets used. Peaks are normalised per clip because these are 40s windows of a room mic whose level wanders, and a waveform you cannot see is not a waveform. Verified rather than asserted: /bounds.html 200, the document 200 at 190 KB, a clip 200 at 641 KB, and a Range request returns 206 with exactly 1024 bytes — so seeking works, which is the whole point of auditioning on a phone.
PLN (Algolia) authored -
PLN corrected two rows: he played rose_rouge and livecode_parade, not blue_gold. The fusion now reaches both WITHOUT being told, which is the whole point. 29:57 crossmatch said blue_gold at a weak 0.44 margin; tempo measured 118.1 against blue_gold's declared 124 — a clash — and rose_rouge is declared 118, with locate holding `rose` at 0.602 offset consistency. Two lenses override one. 54:59 the same shape: measured 130.0, gimme_acid declares 80, livecode_parade declares 130, locate had `love_parade` at 0.399. Both were only reachable because crossmatch STRUCTURALLY cannot name them. rose_rouge played neither reference gig, and livecode_parade's .tidal was created in the CosmicFest prep commit itself, so no prior render of it can exist anywhere. A blind spot, not a tuning failure — and the fix is one render per new track. The tempo lens took three attempts, and the first two measured nothing. librosa.feature.tempo returned 107-130 for every segment of an 80-170 BPM set: that is its log-normal prior centred on 120, not the music. Mode of inter-onset intervals on the full mix returned 233-648 BPM, locking onto the hats rather than the beat — and its apparent matches were hollow, because five octave multiples at 5% tolerance passes roughly a quarter of random values. Autocorrelation of the 40-120 Hz kick band works: bombe 123.9/124, punkachien 170.1/170, vague_de_crime 120.1/120, do_it_right 178.3/2 = 89.15/89. Four exact to 0.1%. That validation is what licenses the overrides. An instrument which agrees where it should is one whose disagreements carry information; without those four hits I would be overriding a strong embedding on the word of an estimator I had not checked (feedback_check_the_instrument_first). I also had to fix my own fusion: counting each top-3 tempo candidate as a vote let cafe_bouillant beat ouais_je_funk, which measured 120.1 against a declared 120 — a perfect match demoted by an alphabetical tie. Tempo compatibility CONFIRMS the embedding's proposal; it only overrides when it clashes AND a second lens agrees on a specific alternative. Four rows are now marked unresolved rather than confidently labelled. That is a real answer: a confident wrong row costs more than a blank one.
PLN (Algolia) authored -
PLN: "what gig-log didnt run that night? HOW COME? ... thats sad and needs proper tech debt payment! We cant afford to miss that." The answer is not a crash. gig-log.service was installed 2026-07-29 and then left `disabled`, and rig_units.py mentioned it ZERO times. So the authored inventory could not enable it, --ensure could not assert it, and gig-up never gated on it. It ran only when somebody remembered to start it by hand — and the sessions that exist, up to 2026-08-20, are exactly those manual runs. Two known failure shapes, both already named in this repo, stacked: The unit lived ONLY in $HOME, never in the repo. That is 4d9f1059 again, the two reconcilers rescued from $HOME after five orbits reached no Ardour track. A file that exists in one home directory is not infrastructure, it is a habit. And it was missing from the one authored list. That is what 406a4610 was written to end ("the list lived in three places"). An inventory only prevents the class of bug it actually enumerates; gig-log was simply left off, so it inherited none of the protection. What was lost is not just thermals and xruns. It is the TRACK BOUNDARIES — the gig log's `track` events are the real tracklist (reference_gig_log_is_the_tracklist). Recovering CosmicFest's set afterwards took two independent DSP lenses, 30 reference renders, and it still leaves two unlabelled stretches. One always-on 1 Hz recorder costing 0.4% of a core would have made all of that unnecessary. Paid in three places so it cannot recur: - tools/gig-log.service, versioned, symlinked into ~/.config/systemd/user - gig-log in rig_units.py SERVICES, so --ensure enables it and the generated parvagues-rig.target Wants it (8 units now) - a HARD gate in gig-up.sh The gate deliberately does not ask "is the unit active". A green unit is not a recording — the process can be up while the writer is wedged (reference_sc_supervision, feedback_verify_the_plumbing). It asserts that a session file's mtime is within 20 seconds, i.e. it is writing NOW. Verified it can actually fail, because this repo already has a log titled "the checks that could not fail": stopped the recorder, waited 25s, the gate failed; restarted, age 1s, passed. Still open, recorded not fixed: the HUD's eval-events.jsonl also missed the set. It holds 21 evals on 2026-08-23 from 18:31-18:59 (prep at home) and then four `pluie` evals at 22:14 with zero orbits, with a 3h15m hole across the gig itself. Pulsar was running, so the likely cause is reference_pulsar_lazy_activation — a reload leaves the package dormant — but that is a hypothesis, not a diagnosis.
PLN (Algolia) authored
-
- 28 Aug, 2026 9 commits
-
-
Two independent lenses over a 63-minute handheld stage-mic MP3, for a night with no stems and no gig-log. They agree where they can both see, and where they disagree the reason is structural rather than mysterious. Confirmed by both lenses, independently: 0:00-5:04 bombe z=2.33 margin 0.67 + diams_dj 85% offset consistency (1056/1244) 31:27-35:46 take5_drops z=1.91 margin 0.58 + take5 cons 0.38 8:54-11:28 something_about_drums + movie_paris cons 0.49 Crossmatch alone, but with margins strong enough to stand: 11:33-16:58 am_i_doing_it_right margin 0.87 18:13-22:32 you_my_sunshine margin 0.82 48:25-51:20 punkachien margin 1.06 <- only findable because Montreuil supplied the render 38:31-41:41 piment_bresilien margin 1.19 51:30-54:10 mafia margin 0.69 57:29-60:29 vague_de_crime margin 0.82 The two disagreements are the interesting part, because they are predictable. crossmatch can only name a track it holds a prior render of, and is confident about two banks that have none: rose @31:20 at 0.602 consistency (rose_rouge played neither gig) and love_parade @56:10 at 0.399 (livecode_parade is NEW — its .tidal was created in the CosmicFest prep commit itself, so no prior render can exist). Trust locate there. The fix for next time is one render per new track and the reference set covers it forever. Recorded as gaps rather than answers: blue_gold wins four short scattered segments at margins 0.11-0.44, and that scatter is the signature of a fallback, not a performance — material with no matching reference lands on whatever is nearest. Segment edges come from a 45s median filter so they are +/- 20s and are NOT cut points; bounds.html and ears do that job.
PLN (Algolia) authored -
`locate` normalises by reference size. `crossmatch` did not, and the number said so: correlation(reference length, seconds won) = +0.586. Ceci (524s) and Piment (506s) won most of the set; WAP and REVOLUTION won nothing at all. The cause is that per-track score is a MAX over that track's windows, so a 524s reference simply gets more chances at a high max than a 154s one. Same asymmetry, second occurrence, new location — which is the pattern in feedback_count_what_you_bound: a bias survives a fix when the metric moves. The correction turns "how similar is this window to track T" into "how UNUSUALLY similar, for track T", by standardising each track's column over the whole query. A track generically close to everything now has a high mean and wins nothing; a track that spikes in one place wins there. It assumes each track occupies a minority of the recording — true for 15-30 tracks over 63 minutes, false for a 2-track set, so it is stated rather than buried, and --no-standardise shows the raw bias. Two more things, both from PLN's "almost the same lineup" observation: Montreuil26's tracks_bandcamp adds 15 more renders, and six of them are tracks OPAL never had — Jeudi Drill, PunkAChien, L'Or Bleu, Premier Septembre, Quand on decolle, Techno Orage. That directly explains the two disagreements between the methods: `locate` was confident about rose and love_parade, and crossmatch mislabelled both, because rose_rouge and livecode_parade are absent from the OPAL reference set and it can only pick the nearest thing it holds. Six tracks now appear in BOTH gigs, so their names must merge or one track splits its own vote and each half loses. canon_track does that with an explicit alias table rather than fuzzy matching: a wrong explicit merge is auditable, whereas a silent fuzzy merge of two different tracks would be invisible in the output. Also caches the query profile keyed on mtime and window geometry, because recomputing 63 minutes of chroma_cqt per run costs five minutes and this tool exists to be iterated on.
PLN (Algolia) authored -
The first scan reported raw hash collisions and I then analysed the noise. Every bank occupied 375 of 378 time bins. Three things were wrong, each traceable to prior art rather than to tuning: Offset consistency was missing entirely, and it is half the algorithm. Wang's 2003 Shazam paper is explicit: real matches agree on a time offset, so the (query_t, ref_t) scatterplot shows a diagonal and the offset histogram spikes. Counting collisions without that step counts coincidences. Fixed offset is wrong anyway for this material. Sonnleitner, Arzt & Widmer (ISMIR 2016, DJ mix monitoring) allow the offset line a free slope, because a DJ plays material at a different rate — and a livecoder changing setcps does the same, across 80 to 170 BPM in one set. Raw counts measure loudness. Measured, not assumed: 8 of 18 banks had their global peak in the final minute, at 1.3-2.5x density. The finale is loud and broadband, so it makes more spectral peaks, so it matches everything better. Scores are now normalised by each window's own query-hash count and by each bank's reference size. But the real problem was the reference unit, and PLN named the fix: "comparing to prior recs, of each track of the opal songs for example (almost the same lineup in the end!)". A 0.4s dry one-shot cannot survive a PA and a 320 kbps encode — too few hashes, below the detection floor by construction. Meanwhile Prod/Opal26_master/tracks holds FIFTEEN ear-verified per-track renders, 154 to 524 seconds each, same performer, same sample library, same patches. That changes the task from sample detection to VERSION identification, where the literature is unambiguous: when audio is related but not identical you stop matching exact time-frequency points and match sequences of features instead (Serra et al. on covers, McFee & Ellis on structure). So `crossmatch` embeds 15s windows as MFCC mean+std, chroma and spectral contrast, matches by cosine against every reference window, and median-filters over 45s because a track lasts minutes and a one-window flicker is noise by definition. Standardising before the cosine is not cosmetic: raw MFCC coefficients differ in scale by orders of magnitude, so without it the first two — level and spectral tilt — decide everything, and level is exactly what a room mic gets wrong. The docstring says what it cannot do. The three cafe_* tracks are the same instruments at nearly the same tempo and no timbral profile will separate them, so the output reports a runner-up margin and flags thin ones: a win by 0.01 is not a win. Also included, from the custom-mode thread: the generated Components entry table (40 of 48 controls need changing, all of row A already matches), and the sniff that caught Components driving the device with documented feature controls — 9F 0B 7F then CC 30 = 6 — plus an undocumented cmd 0x05 whose read/write forms match the audiocontrol project's reverse-engineered protocol.
PLN (Algolia) authored -
PLN downloaded the CosmicFest capture and called it "an interesting test". It is: a 63-minute handheld stage-mic MP3, and all three boundary lenses the toolbox owns need something it does not have. Orbit-activity needs stems, there are none. The gig-log `track` events are the real tracklist, but the 1 Hz recorder never ran that night — newest session is 2026-08-20. Gap detection assumes a set stops between tracks, and a livecoded set does not. What is left is audio, plus one large advantage: we know what was prepped, so this is matching over ~23 candidates rather than open discovery. PLN warns "the set was slightly altered", so candidates are treated as candidates — presence is strong evidence, absence is weak. Two lenses, because neither is sufficient. Tempo finds seams but cannot name them: five candidates sit at 120 BPM and three at 124, and tempo from audio is only ever determined up to a factor of two. Sample signature names a segment, and PLN pointed at exactly the right prior art — sample_tfidf.py already computes which sounds IDENTIFY a track. take5 and love_parade are df=1; jungle_breaks is in 80 tracks and worthless. 15 of 23 candidates carry a sound rare enough (df<=6) to fingerprint; 19 such banks, 533k reference hashes. Constellation fingerprints rather than correlation, because the reference is a dry one-shot off disk and the query is that sound through a PA, a room, a limiter, "ppl messed with it", and a 320 kbps encode. Peak constellations encode only which time-frequency points stick up locally, which survives all of that. My first attempt at extracting banks was wrong and the output said so: 9 tokens across 23 tracks, every count 1, and a label reading "shares every bank" that actually meant "nothing parsed". The regex looked for `s "name"` while the ParVagues idiom applies a bare string — `$ "meth_bass" # n "..."`. The fix was not a better regex, it was using the parser that already existed. The docstring carries the limits because they are easy to launder: a detection means the SOUND occurred, not that the TRACK played, since banks get reused live. Recall is asymmetric — a track whose signature never sounded is invisible. And vote counts are not comparable between banks, because a 582-file bank offers far more chances to match than a 4-file one. Also adds `lcxl3.py sniff`: capture raw SysEx in full. The custom-mode format is undocumented, and Components writes modes to the device over SysEx — capturing those bytes is the difference between a layout authored in lcxl_grid.py and one merely stored in a web app.
PLN (Algolia) authored -
The v3 and the 169 tracks disagree about what a CC means, and only one of them is cheap to change. Paint is DAW-mode-only. Tested rather than believed, in three steps with PLN watching the surface: magenta on the MIDI port in standalone did nothing; the same paint on the DAW port with DAW mode off did nothing; `daw on` followed by the same bytes lit all 24 rings and 16 buttons. So the guide's "only available once DAW mode is enabled" is a hardware fact, not an artefact of how the document is organised. But DAW mode's CC map is fixed, and it overlaps v2's with DIFFERENT meanings. The map was read off the hardware, one control per row, not off the PDF's low-resolution diagram: fader1 -> cc5 ch16, A1 -> 13, B1 -> 21, C1 -> 29, E1 -> 37 ch1, F1 -> 45 ch1, shift -> 63 ch7. row v2 (lcxl_grid) v3 DAW A 13-20 13-20 identical B 29-36 21-28 C 49-56 29-36 <- v2's row B numbers D 77-84 5-12 E 41-44, 57-60 37-44 F 73-76, 89-92 45-52 Sixteen indices collide. The one that decides it: v3 #41 is button E5, while v2 #41 is button E1 — the kick gate the whole #94 migration just aligned 169 files onto. Renumbering would move the kick gate four buttons to the right, silently, with nothing erroring. That is the same failure class b5ad8b6c was written to end, so the corpus keeps its numbering and software absorbs the difference. Three things make this cheap rather than clever: SuperCollider's binding is `MIDIFunc.cc({...})` with no cc, channel or src filter — only the number matters, so a pure renumber is sufficient and no channel juggling is needed. Every encoder row is still in ABSOLUTE mode (queried: rows 1/2/3 all read 0), so a v3 encoder already behaves like a v2 pot. Relative mode needs an integrator that owns the value — genuinely better, since it kills pot-pickup outright, and it is where paint-by-value belongs — but it is a separate change and `--relative` is left as an experiment that honestly warns the map no longer applies. The v2 numbers are DERIVED from lcxl_grid.ROW_CCS positionally rather than retyped, so if the authored table moves, the translation follows. Publishing a virtual port rather than resolving SuperCollider's once: SC runs MIDIIn.connectAll a single time at boot, so a port that appears later is never connected, and a binding resolved once is the rig's most repeated failure mode. The driver re-asserts the aconnect link on a timer, the same shape as the two reconciler units. midi-autoconnect had the same stale-name bug: it hardcoded "Launch Control XL", which matches neither v3 port, and since it routes aconnect's complaints to /dev/null the failure was completely silent — Ardour stopped hearing the surface with every check green. It now resolves the source, preferring the driver's translated port over the raw device, and echoes what it picked once per change rather than every two seconds.PLN (Algolia) authored -
4d9f1059 rescued these two scripts from $HOME, where they had been living un-versioned, and committed them without the exec bit. The symlinks in ~/.local/bin resolved fine to a file systemd then refused to run: Process: ExecStart=/home/pln/.local/bin/tidal-ardour-autoroute.sh (code=exited, status=203/EXEC) Active: activating (auto-restart) An auto-restarting unit reads as busy rather than broken, which is why rig_units reported 'activating' and nobody chased it. Net effect: the autoroute that puts twelve orbits onto Ardour's Tidal NN tracks has not run since 23 Aug — the exact regression 4d9f1059 was written to end. The recurrence has a cause, and it is repo config: core.fileMode = false here, so git ignores exec-bit changes from the working tree entirely. That is why 4e0d63ae already had to fix the mirror image of this (+x on disk, 644 in the index), and why chmod can never be the whole fix in THIS repo — it silently does nothing as far as the index is concerned. `git update-index --chmod=+x` is the tool that works. Both halves done: chmod for this checkout, update-index for the index, so a fresh clone is born runnable.
PLN (Algolia) authored -
Not an extension of lcxl-leds.py, deliberately. v2 and v3 are different protocols, not different constants: v2 is a template/palette SysEx driving 3-colour buttons, v3 has true RGB per control, a 128x64 OLED, endless encoders, and a CC map that only exists in DAW mode. Folding v3 in would produce a file that lies about both. When the numbering settles, lcxl_grid.py stays the one authored table and this becomes its transport. Every byte sequence in here is quoted from the official programmer's reference v1.0, not recalled — RGB is 01h 53h <idx> <R> <G> <B>, the bitmap is 09h with 1216 bytes and a 7Fh terminator rather than F7h, and the feature CCs live on channel 7 of the DAW in port. Transport is python-rtmidi via mido rather than shelling out. aseqsend on this system takes -s as a FILENAME: `aseqsend -p 20:0 -s "B0 25 05"` prints 'cannot open B0 25 05' and exits 0. A silent no-op, which is how a first paint attempt can look like a hardware answer. lcxl-leds.py shells out that way today, which is worth revisiting. The subcommand that matters is `keystone`. The guide files 'Colouring the surface' and 'Controlling the screen' inside the DAW mode chapter, prefaced 'only available once DAW mode is enabled'. If that is how the document is organised rather than a hardware restriction, paint works in standalone and the corpus keeps its v2 numbering — a 169-file difference. So it gets tested in three steps with PLN's eyes on the surface, not assumed either way. `sweep` exists for the same reason: the DAW-mode index map is a low-resolution diagram in the PDF, so we light each index in turn and read the map off the hardware.
PLN (Algolia) authored -
PLN said snare was 'also a default tidalcycles CC0/public domain pack'. Upstream disagrees: tidalcycles/Dirt-Samples tracks 52 files under sn/ and zero under snare/. The 90-file snare bank is local and was unidentified. His second memory was the one that paid — 'the very early tidalcycles docs pointed me to that sample pack'. The banks come from slab.org/tmp/samples-extra.zip, the Tidal Club course pack from Week 1 lesson 2. Proven by reading the zip's central directory over a range request, so 113 MB stayed on the server: SNARE0, SNARE119, KICK142, CLAP163 are all in it, and the local folder is named samples-extra. Three candidates died on dates and names, which is worth recording so nobody re-runs them: Blood Sport prefixes its banks bs- and holds no SNARE*.wav; the 90s-sample-cds torrent was downloaded 2024-12-20, after these files' 2019-11-07 mtime; MF_Drums_Samples names its files BD_MF_Valve11.wav. And there is no content-based route at all — AcoustID needs song-length audio, not 0.4s hits. The uncomfortable part: this makes the ledger worse, not better. The pack ships no licence for these banks — the only licence file in it is Blood Sport's own. And snare's files carry ISFT 'Sonic Foundry Sound Forge 6.0', IENG 'TJ', ICRD 2003-08-30, i.e. 24-bit one-shots rendered on a 2002 commercial DAW. An official course pack establishes provenance, not licence. So kick, cleared on 2026-08-16 on the note 'TidalCycles official / samples-extra CC0 pack', rests on the same unverified premise. Same pack, same absent licence, but cleared is releasable and unknown blocks. One question to yaxu settles six banks at once.
PLN (Algolia) authored -
The board carried 'can third-party SysEx paint arbitrary text to the OLED?' as UNVERIFIED, with a note that it mattered: HUD v2's alarms are derived state, not control values, so a closed OLED meant the alarms stayed on screen. The official programmer's reference settles it — arbitrary 128x64 bitmaps, and the firmware ACKs each frame specifically so you can animate. Two more close the same way. LED brightness is CC 111 and non-volatile, so open taste call #11 stops being an argument about whether a bypassed filter should be bright and becomes a knob. And endless encoders emit deltas, so pot-pickup does not exist to solve. The new one is worse than the ones it closed. DAW mode is the only mode the guide documents paint in, and DAW mode's CC map is fixed and PARTLY OVERLAPS v2's with different meanings: v2's row-B knobs 29-36 are v3's row 3, and v2's faders 77-84 are v3's relative-encoder row 1. Nothing errors. Controls just move one row. That is the exact failure class b5ad8b6c spent 169 files fixing, so that branch does not merge until this is settled. Also retracted, with evidence: DIN is not our answer to the USB-hub single point of failure. aconnect shows only Midi Through and the controller — this machine has no MIDI DIN input at all, so DAW mode disabling the DIN outputs costs nothing today. And snare is still not cleared. 'Also a default tidalcycles CC0 pack' is a name collision: upstream tracks 52 files under sn/ and zero under snare/. The 90-file snare bank is local and unidentified, and still gates 4 tracks. CosmicFest is closed as a mastering input by PLN's call. Five probes agree nothing was captured to this machine; he had a stage mic and rates it poor.
PLN (Algolia) authored
-
- 23 Aug, 2026 4 commits
-
-
PLN: "so many tracks still having ^42 instead of ^41 for kicks its confusing." Remap phase 2 (5910aacc) column-aligned the board so column N IS orbit N, but it was scoped to the FOURTEEN files of the OPAL set. Everything else still spoke the layout from when gMask held CC 41, where an orbit's gate row started one column late: d1 -> ^42, d2 -> ^43. button-column-audit found 169 such files, every one of them shift +1 — a single layout generation, not a scatter of typos. Layout-shifted files: 169 -> 0. Buttons on their own column: 35.7% -> 78.6%. TWO SYSTEMIC FIXES IN migrate-columns.py, both found by running it on the whole corpus instead of a setlist. Each refused a batch rather than writing bad Haskell, which is the only reason they were cheap to find: 1. THE SEGMENT BOUNDARY WAS DEPTH-BLIND. A chain step ends at the next line starting with `$`/`#`, but a `$` inside an open paren is a CONTINUATION, not a new step. So a multi-line body truncated after one line, left depth 1, and the balance check refused the file. 10 of 169 were unmigratable. The boundary is now paren-aware, and the hit line is no longer assumed to BE a step start: `^NN` is routinely referenced from the middle of a body, so we walk back to the nearest enclosing step whose segment both balances and contains the hit. Negative depth was the tell that the anchor was too late, not that the source was broken. 2. COMMENTING OUT A `$`-STEP CAN UNDER-APPLY THE STEP ABOVE IT. Not a paren problem — an ARITY problem, and silent-eval is what caught it: 4 files stopped compiling while 165 were fine. $ midiOff "^57" (mask "<[t f]!0 ...>") <- a function, still hungry $ stack [ ... ] <- its argument Comment the `stack` and GHC says "midiOff is applied to too few arguments" and the whole do-block dies, all twelve orbits — far worse than the cross-orbit trigger the comment-out was avoiding. `#`-steps stay commentable (`#` takes a ControlPattern, so a shorter chain is still well-typed); `$`-steps are now ANNOTATED and left live, and the FIXME says so, because an annotated line IS still firing. VERIFIED against a pre-migration baseline, not against hope: * non-compiling tracks: 17 before, 17 after, and the SETS ARE IDENTICAL — zero newly broken, zero newly fixed. All 17 predate this work. * silent orbits: the same 19, in the same tracks, only line numbers moved. * pvlint: 30 -> 26 errors, no new error signature. Warnings 996 -> 1041: the +45 are the annotated-but-live `$`-steps honestly reporting that they still reach another column. Errors down, honesty up. * fix-button-roles: 3 clean role rewrites applied, then 0 remaining. The role report is otherwise unchanged from HEAD, so the migration introduced no gate/gesture inversion — the class only PLN's ear catches. * migrate-columns --plan re-run: 0 moves. Converged. Left for a second pass, deliberately: 118 sites where an orbit's two own-column buttons carry the same role (e.g. two gestures, no gate). Those are not inversions — with no gate there is nothing to swap, and the grid gives d1-d3 one button anyway. The count rose from 74 because column-aligning the buttons is what made them VISIBLE; they were unevaluable before. gSel (#54) is the fix.
PLN (Algolia) authored -
PLN: "we gotta ensure these are unified proper gig single service or package easy to maintain/add things to." tools/rig_units.py is now THE inventory. Three consumers, no re-typing: the Bridge panel imports SERVICES, gig-up.sh reads --ensure, and parvagues-rig.target is GENERATED by --target. Same shape as lcxl_grid.py (#97), for the same reason: the list was previously a hand-typed table in bridge/rig.py (5 rows, missing parvagues-bridge), a hardcoded loop in gig-up.sh (4 names), and whatever [Install] each unit carried. Adding a unit to the rig is one row. Two real bugs closed: * gig-up.sh only ever ran `systemctl start`, never `enable`. A start fixes tonight and changes nothing about the next login — exactly how tidal-ardour-autoroute sat `disabled` while every pre-gig check passed. --ensure does both verbs, per-unit boot policy: the reconcilers are "login" (they idle, hold no ports, make no sound), parvagues-sc stays "on-demand" because a login must not start SuperDirt. * `is-enabled` returning "linked" means NOT enabled — it reads like a healthy word. --status says so in words instead of printing systemd term. Also learned the hard way while doing this: a unit file symlinked from ~/.config/systemd/user into a FEATURE branch vanishes on `git checkout`, and `systemctl disable` on a linked unit deletes the symlink itself. Live infra files belong on master; both units are there now. systemctl --user start parvagues-rig.target # the single handle
PLN (Algolia) authored -
tidal-ardour-autoroute.service was disabled+inactive after launch: orbits 5-9 (SuperCollider:out_9..18) had NO link to their "Tidal 05..09" tracks, while 1-4 and 10-12 survived as leftovers from an earlier session. Enabled it; all 24 outs reconcile every 2s again. Root cause of the regression is that both reconcilers — this one and midi-autoconnect — existed ONLY as plain files in ~/.local/bin and ~/.config/systemd/user, unlike every other unit here which symlinks into tools/. Nothing was version-controlled, so a `disable` (or a fresh systemd user config) was unrecoverable and invisible to git. Now in the repo, with $HOME pointing back at it. Also started parvagues-sc-watchdog, which was linked but dead.
PLN (Algolia) authored -
PLN (Algolia) authored
-
- 22 Aug, 2026 1 commit
-
-
PLN opened fred_angie_bass, the first kit he tried, and found two bad loops immediately: ":0 starts, then silence, then starts again" and ":2 why is this not trimmed, silence before silence after?". Both had shipped as tier S with zero flags, one at grade 0.991. He was right, and it is worse than two files. Binning each loop's envelope on its own beat grid says 12 of 111 loops carry dead air — and 5 of 14 bass loops, 36%, which is why he hit it on his first click. angie:0 has beats 7-9 of 32 at -88 dBFS; bighen_bass:1 has a five-beat hole and 62.5% occupancy at grade 0.983. This is the third time this session that silence beat the grader, and the reason is the same each time, one level further down. Silence-as-optimum was fixed at export, then again in the finder before ranking, but every one of those checks is an AGGREGATE: file peak, whole-file RMS, max of the beat table. An aggregate cannot see a hole. A loud loop with a silent bar inside it passes all three. And kitcheck's dead-bar rejector looks at BARS, so a 2.5-beat gap straddling a bar boundary is invisible — angie:0's two bars read -18.3 and -15.9 dBFS while the beats between them read -88. Resolution was a correctness input and I had treated it as a detail. So this commit does not fix it, it measures it. beatocc.py bins the envelope on the loop's own beat grid (bpm and bars from the manifest, never re-estimated) and reports SHAPE rather than level: head and tail dead beats, longest interior dead run, occupancy, plus a beat map under -v. It exits non-zero when any loop has dead air, so it can go straight into kitgate once the thresholds are known. Thresholds deliberately not chosen yet. Occupancy floors have to be per family like the periodicity floors — marea_vox/06_breaths_1b at 25% occupancy is a breath, not a defect, while a bass loop at 62% is broken — and the head-silence question has three plausible answers (trim, rotate, reject) with rotation ruled out already, since angie:2's offset is 1.17 beats and _rotate_to_downbeat snaps to beats. Placing those floors by guess is how silence got graded A in the first place. #24 on the board carries the open decisions and the data needed for each. The Fred pack section is now marked DEFECTIVE rather than SHIPPED. It needs a re-cut once the shape check exists, and PLN's verdict stands as written: "tbh i dont think the loops are usable."
PLN (Algolia) authored
-
- 21 Aug, 2026 20 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
-