Commit 588d0a41 by PLN (Algolia)

docs(armada): logs 032, 033, 034 — the surface, the microphone, the recorder

Three threads worth their own entries in the documentary trail.

032 — the LCXL3. Closes the board's twelve-day-old OLED question (arbitrary
128x64 bitmaps, ACKed per frame for animation), records the keystone result that
paint is DAW-mode-only, and the sixteen-index CC collision whose decisive case is
v3 #41 = button E5 against v2 #41 = the kick gate log 031 had just aligned 169
files onto. Plus two instruments that lied: aseqsend -s takes a FILENAME and exits
0 on 32 silent no-ops, and pgrep -c -f matched its own command line.

033 — fourteen tracks from one microphone. 12 of 14 named correctly off a
handheld stage mic with no stems and no log. Includes the three lenses, the three
failed tempo estimators, the same reference-size bias appearing in three separate
costumes, and PLN's ears beating my reporting twice because I printed 4 of 86
detections.

034 — the recorder nobody enumerated. Why CosmicFest has no session log: the unit
was installed 2026-07-29, never enabled, and absent from the authored inventory,
so nothing could assert it. Paid in three places, with a gate proved capable of
failing.
parent cca62851
# 032 — The surface that spoke a different language
**Landed 2026-08-28/29.** The LCXL3 arrived. PLN, unpacking it: *"we have now infinite
knobs so always at 0 init ✨ We have colors and brightness, true colors all 3 leds
combinations, under each button and knob! we have a OLED screen with true pixels we could
display ANYTHING there! OMG so exciting wdyt?"*
Mostly yes. But the device that can paint anything could not, at first, play a single one
of his 169 tracks.
## The board's biggest unknown, answered
`reference_lcxl_v3_ordered` had carried a flagged question for twelve days: *can
third-party SysEx paint arbitrary text to the OLED?* It mattered because HUD v2's alarms
are DERIVED state, not control values — if the OLED only rendered parameter values, the
alarms would stay stuck on screen.
The official programmer's reference settles it: `F0 00 20 29 02 15 09 <target> <1216
bytes> 7F`, nineteen bytes per row, sixty-four rows, seven pixels per byte. And the
firmware **ACKs every frame** *"suitable for timing fluid animations"* — so animation is
a designed-in feature, not a hack. DESIGN.md's "alarms need motion, not numbers" is
therefore satisfiable on the device itself.
Two more open items closed the same way. LED brightness is CC 111 and non-volatile, so
open taste call #11 — should a bypassed filter be bright or dim — stops being an argument
and becomes a knob. And endless encoders send relative deltas, so pot-pickup does not
exist to solve.
## The keystone: three steps, PLN's eyes, ten minutes
The guide files "Colouring the surface" and "Controlling the screen" inside the DAW mode
chapter, prefaced *"only available once DAW mode is enabled"*. That is a document claim,
and this project has scar tissue about believing those. So it got tested.
Magenta on the MIDI port in standalone: nothing. Same paint on the DAW port with DAW mode
off: nothing. `daw on`, same bytes: all 24 rings and 16 buttons lit. PLN: *"ok now i see
purple ABC blue EF"*.
Hardware fact, not documentation artefact. Which created the real problem.
## Sixteen collisions, and one of them is the kick
DAW mode's CC map is fixed, and it was read off the hardware rather than off a
low-resolution diagram — one control per row, watching what arrived. It overlaps v2's map
with **different meanings**:
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
The decisive one: v3 `#41` is button **E5**, while v2 `#41` is button **E1** — the kick
gate that log 031's 169-file migration had just finished aligning. Renumbering would slide
the kick gate four buttons to the right, silently, with nothing erroring. That is the
exact failure class the migration existed to end.
So: software translates, the corpus does not move again. `tools/lcxl3-driver.py` reads the
v3 DAW port and emits v2 numbers on a virtual port, deriving the mapping from
`lcxl_grid.ROW_CCS` positionally rather than retyping it. Three facts make it cheap —
SuperCollider's `MIDIFunc.cc` binds with no cc, channel or src filter so only the number
matters; every encoder row is still in absolute mode, so a v3 encoder already behaves like
a v2 pot; and endless mode would need an integrator anyway, which is where paint-by-value
belongs later.
## Two silent breakages found on the way
**Both rig reconcilers had been dead since 23 August.** `203/EXEC`: when log 030 rescued
them from `$HOME` they were committed as **644**. The recurrence has a repo-level cause —
`core.fileMode = false` here, so git ignores exec-bit changes from the working tree
entirely, and `chmod` can never fix the index. `git update-index --chmod=+x` is the tool.
Worse, an auto-restarting unit reports `activating`, which reads as busy rather than
broken, so `rig_units --status` showed a yellow that nobody chased for five days.
**`midi-autoconnect` hardcoded "Launch Control XL"**, which matches neither v3 port — and
it routes `aconnect`'s complaints to `/dev/null`, so Ardour silently stopped hearing the
surface with every check green. It now resolves the name, preferring the driver's
translated port, and logs its choice once per change rather than every two seconds.
## Instruments that lied, and one that lied about the instrument
`aseqsend -s "B0 25 05"` treats its argument as a **filename**: it printed `cannot open
B0 25 05` and exited **0**. Thirty-two paint attempts, silent no-op, exit success. That is
how a transport bug can look like a hardware answer. `lcxl-leds.py` shells out that way
today.
And `pgrep -c -f sclang` returned 2 while `ps -C sclang` returned nothing — it was
matching its own command line. Reading that as "SC is running" produced a confidently
wrong picture of the rig for several minutes. `pgrep -x` says the truth.
## Firmware, and a decode worth keeping
The device inquiry version field is **7-bit packed**: `01 00 0A 54` = 1.0.(10<<7|84) =
**1.0.1364**, which PLN confirmed off the bootloader screen. Updated to **1.1.1465.0** via
web Components in Chromium — WebMIDI works on Linux, despite Novation documenting only the
Mac/Windows app. v1.1 adds two extra USB→DIN output ports, Mode-as-momentary for fast
custom-mode switching, and encoder acceleration (which CC 121 had refused to answer on
1.0, independently confirming the version).
## A claim retracted with evidence
The board twice asserted that the v3's DIN path mitigates the USB-hub single point of
failure. It does not: `aconnect -l` shows only Midi Through and the controller. **This
machine has no MIDI DIN input at all** — the UMC202HD has no MIDI jacks. The controller
could speak DIN all night and nothing host-side would hear it. So DAW mode disabling DIN
out costs nothing today, and #154 needs a different answer.
## Left open, honestly
The driver has never run against a live SuperCollider, because sclang, scsynth and Ardour
were all at zero when it was written. The whole "PLN can play his tracks on the new
surface" claim rests on that one untested step.
# 033 — Fourteen tracks from one microphone
**Landed 2026-08-29.** PLN dropped a file in chat: *"boom downloaded cosmic set!
ZOOM0067.MP3 can you find the tracks back? its an interesting test actually :P"*
A 63-minute handheld stage-mic MP3 of a gig with no stems and no session log. Every
boundary lens the toolbox owned needed something that recording did not have.
Orbit-activity needs stems. The gig-log's `track` events ARE the tracklist, and the
recorder never ran. Gap detection assumes a set stops between tracks, and a livecoded set
does not.
Final score against PLN's ears: **12 of 14 tracks named correctly**, 81% of the set
labelled, 85% of the labelled overlap right.
## The first pass measured loudness
I built Shazam-style landmark fingerprints of rare sample banks and reported the results,
and every bank occupied 375 of 378 time bins. Three things were wrong, and none of them
was a threshold.
**Offset consistency was missing entirely — and it is half the algorithm.** Wang's 2003
paper is explicit: real matches agree on a time offset, so the scatterplot shows a
diagonal and the offset histogram spikes. Adding it took the top hit from noise to **85%
of 1244 hashes agreeing on one offset**.
**Raw counts measure loudness, not similarity.** Measured, not assumed: 8 of 18 banks had
their global peak in the final minute at 1.3-2.5x density. A loud broadband finale makes
more spectral peaks, so it matches *everything* better.
**And fixed offset is wrong for this material anyway.** Sonnleitner, Arzt & Widmer's
ISMIR 2016 DJ-mix work allows the offset line a free slope, because a DJ plays material at
a different rate — and a livecoder changing `setcps` across 80 to 170 BPM does the same.
## PLN's reframe, which was the actual unlock
*"so it meas comparign to prior recs, of each track of the opal songs for example (almost
the same lineup in the end!), can be fertile?"*
Yes, and it reframes the task. A 0.4 s dry one-shot through a PA and a 320 kbps encode is
below any landmark method's detection floor by construction. But `Prod/Opal26_master/tracks`
holds fifteen ear-verified per-track renders of 154-524 s each, played by the same
performer from the same sample library — and Montreuil-26 adds fifteen more, six of them
tracks OPAL never had. That is minutes of reference per track instead of fractions of a
second, and it turns sample detection into **version identification**, where the
literature is unambiguous: match sequences of features, not exact time-frequency points.
`punkachien` landed at margin 1.06 purely because Montreuil supplied that render.
## Three attempts at tempo, because the first two measured nothing
1. `librosa.feature.tempo` returned 107-130 for every segment of an 80-170 BPM set. That
is its log-normal prior centred on 120. A lens whose output is its prior measures
nothing.
2. Mode of inter-onset intervals on the full mix: 233-648 BPM — it locks onto the fastest
subdivision, the hats. Its apparent matches were hollow too: five octave multiples at
5% tolerance passes roughly a quarter of *random* values.
3. Autocorrelation of the **40-120 Hz kick band**. Validated on six cases chosen because
their declared tempos are known and well separated, so the test could fail: 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 licensed the overrides. An instrument that agrees where it should
is one whose **disagreements carry information**.
## The same bias, three times, in three costumes
`locate` normalised by reference size. `crossmatch` did not, and the number said so:
correlation(reference length, seconds won) = **+0.586**. Ceci (524 s) and Piment (506 s)
won most of the set; WAP and REVOLUTION won nothing. Fixed by standardising each track's
score column over the whole query — "how UNUSUALLY similar" rather than "how similar".
Then the fusion grew it a third time: 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.
## PLN's ears beat my reporting, twice
He heard a rose_rouge sample inside a seam I had labelled `quand_on_decolle`, and said he
was sure he had played `something_about_drums` in a stretch I reported as unclaimed. Both
were **already in my data**: I had 86 offset-consistent detections and had printed 4,
because the report showed each bank's single best window and discarded temporal extent —
which for this question is the informative part.
rose -> rose_rouge 22:35-24:00 (max cons 0.81) ... 31:30
movie_paris -> something_about_drums 6:15 (0.62), 10:30 (0.49)
His diagnosis of the fix was also right: *"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."* With one correction from measuring it — plain log-IDF is too
flat, giving `jungle_breaks` (df=80) 0.50x the weight of `rose` (df=2), a 2x penalty for
being 40x more common. **1/df** gives 0.0125 versus 0.50.
## A negative result, and a hypothesis that died
The weighting let MAX_DF rise from 6 to 40, taking fingerprint coverage from 15/23 to
**23/23** candidate tracks — and it added **zero** evidence. I guessed reference duration
was the binding constraint. 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
cause is recorded as unestablished rather than explained away.
## Both remaining failures are one failure
`perfect` was called gimme_acid; the improvised outro was called REVOLUTION. All five
ghosts — blue_gold, desire, gimme_acid, quand_on_decolle, REVOLUTION — are the same
mechanism: crossmatch can only choose among tracks it holds a prior render of, so audio
from a track it has never heard lands on the nearest thing it has.
Structural, not a threshold. **The fix is one render per track**, and it compounds: every
future set recovery gets easier.
## And a track that only exists live
The final 103 seconds, per PLN: *"thissegment is pure dubsiren noise and fun ahahah it
could be called 'Outro Dub Siren' ahah"*. No reference-based lens could ever have named
it. Worth cataloguing under its own name if the set is released.
# 034 — The recorder nobody enumerated
**Landed 2026-08-29.** PLN, on learning why CosmicFest's tracklist had to be recovered
with DSP at all: *"what gig-log didnt run that night? HOW COME? 😁 glad we have belt and
suspenders from that cosmicfest, but thats sad and needs proper tech debt payment! We cant
afford to miss that"*.
He is right, and the answer is not a crash.
## The cause
`gig-log.service` was installed **2026-07-29** and left `disabled`. And
`tools/rig_units.py` — the one authored inventory that log 030 created precisely so this
class of thing could not happen — mentioned it **zero times**.
So `--ensure` could not enable it. `gig-up.sh`, which gates on the inventory, never
asserted it. It ran only when somebody remembered to start it by hand, and the sessions
that survive, through 2026-08-20, are exactly those manual runs.
Two failure shapes already named in this repo, stacked:
The unit lived **only in `$HOME`**, never in the repo. That is log 030 again — the two
reconcilers rescued from a home directory 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**. An inventory only prevents the class of
bug it actually enumerates. gig-log was left off, so it inherited none of the protection.
## What was actually lost
Not just thermals and xruns. The gig log records a `track` event per evaluation and
coalesces the MIDI CC stream — so it holds both the tracklist and a fingerprint of which
controls each loaded track touched. Recovering the set afterwards took two DSP lenses,
thirty reference renders, three attempts at a tempo estimator, and still left two
mislabels and five ghosts. One always-on 1 Hz recorder costing 0.4% of a core would have
made all of it unnecessary.
The HUD's `eval-events.jsonl` missed the set too, which is why "belt and suspenders" did
not save us: 21 events on 2026-08-23 from 18:31 to 18:59 (prep at home — `rose_eternelle`,
`quand_on_decolle`, `because_its_there`, `sept1`), then a **3h15m hole** across the gig,
then four `pluie` evals at 22:14 with zero orbits. Pulsar was running, so the likely cause
is the lazy-activation trap, but that is a hypothesis and is recorded as one.
## Paid in three places
* `tools/gig-log.service` — versioned in the repo, symlinked into `~/.config/systemd/user`
* `gig-log` in `rig_units.py` SERVICES, so `--ensure` enables it and the generated
`parvagues-rig.target` Wants it. **Seven units, zero problems.**
* a **HARD** gate in `gig-up.sh`
## The gate does not ask whether the unit is green
A green unit is not a recording — the process can be up while the writer is wedged. So it
asserts the property that matters: a session file whose mtime is within the last 20
seconds, i.e. it is writing *now*.
And it was verified to be capable of failing, because this repo already has a log titled
"the checks that could not fail": stopped the recorder, waited 25 s, gate failed at age
25 s; restarted, age 1 s, passed.
## A footnote on how the fix nearly shipped incomplete
The commit that explains this fix and the commit that performs it were briefly different
commits, and only one existed. `30fceb4`'s message said the debt was *"paid in three
places"* and contained a single file; the inventory entry, the regenerated target and the
gate sat in the working tree until the pre-compact tree check caught them. Second
occurrence in one session of a message written from intent rather than from
`git show --stat`.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment