Commit 59f6a20a by PLN (Algolia)

master Take103 (AI ENGINEER set) and Take104 (Rose Rouge club mix)

parent 084e692b
---
log: n/a
title: "Take103 (AI ENGINEER set) + Take104 (Rose Rouge club mix) exported and mastered"
date: 2026-09-26
task: n/a
tags: [mastering, postprod, tooling]
shareable: true
---
Both takes from `Tidal Live` exported straight from the Ardour capture files —
no Ardour export pass needed: the interchange `%L/%R` mono pairs were joined
into 12 stereo orbit stems with ffmpeg (`join=inputs=2`), then the
`tidal-ears master` chain (analyze → gain-trim → HPF/LPF → sum → glue comp →
two-pass loudnorm) rendered four masters in
`~/Work/Sound/Prod/AIEngineer2026_master/`:
- `AIEngineer2026_set_streaming.flac` — **-14.0 LUFS** on target, LRA 8.4
- `AIEngineer2026_set_club.flac` — -11.5 (single loudnorm can't reach -9
against the -1 dBTP ceiling; staged chain is the v2 item)
- `RoseRouge_clubmix_take104_{streaming,club}.flac` — -13.9 / -10.7
- `tracks_bandcamp/` — 13 per-track FLACs split with segments authored from
the gig-log track-switch timeline (second-precision), titled and dated
"AI Engineer, 2026-09-24 (live)".
Honest edges: rec joined *piment_bresilien* 3 min in (take is 76 min, set was
~95); **d8 was never armed at the gig** (the "Arm Tidal 08" fix happened the
next day), so Take103 has 11 stems; d9 is digitally silent for the whole Rose
Rouge take. The track switch log in `gig-20260924-005926.jsonl` proved to be a
better boundary source than silence detection — instant transitions leave no
gaps to find.
Found and fixed in tidal-ears (`3bafd03`): `split --segments` formatted the
slug key as `%02d` and crashed; play-order numbering is now the enumerate
index. 76 tests pass.
**Shareable bit:** the Ardour capture directory is already the export — mono
L/R pairs + a JSON timeline of what was played, and the master is two ffmpeg
passes away. The "export from the DAW" step is optional when the DAW never
stopped recording.
[ [
{ {
"date": "2026-09-17", "date": "2026-09-17",
"task": "GUI launcher did not spawn the MIDI lens or the tray", "task": "GUI launcher did not spawn the MIDI lens or the tray",
"learning": "The launcher (gig-up.sh) and the unit converger (rig_units.py --ensure) disagreed on SuperDirt topology — raw sclang vs parvagues-sc.service — so the launcher never called ensure; the fix is ensure()'s proof-process skip, not another hand-copied unit list." "learning": "The launcher (gig-up.sh) and the unit converger (rig_units.py --ensure) disagreed on SuperDirt topology \u2014 raw sclang vs parvagues-sc.service \u2014 so the launcher never called ensure; the fix is ensure()'s proof-process skip, not another hand-copied unit list."
}, },
{ {
"date": "2026-09-17", "date": "2026-09-17",
"task": "perf-tray double-click mystery + menu restyle", "task": "perf-tray double-click mystery + menu restyle",
"learning": "The double click was the appindicator extension's, not ours: it reserves double-click for SNI Activate (instant) and delays single-click 400 ms. The menu got the Ship's Bridge QSS with measured contrast, and '&' in Qt labels exports as '_' over DBusMenu, so mnemonics survive into the shell-drawn twin; literal & does not survive, so the copy avoids it." "learning": "The double click was the appindicator extension's, not ours: it reserves double-click for SNI Activate (instant) and delays single-click 400 ms. The menu got the Ship's Bridge QSS with measured contrast, and '&' in Qt labels exports as '_' over DBusMenu, so mnemonics survive into the shell-drawn twin; literal & does not survive, so the copy avoids it."
}, },
{ {
"date": "2026-09-18", "date": "2026-09-18",
"task": "tray GIG UP did nothing + midimon silent", "task": "tray GIG UP did nothing + midimon silent",
"learning": "The Bridge ran 'gig-up.sh --converge --quiet' but gig-up.sh's arg guard only knew --headphones/--help, so every tray GIG UP died on flag 1 (exit 2, output captured). Flags now parsed: --converge boots SuperDirt + converges units with NO app windows, --quiet mutes ok/info. midimon watches 132:0 (the lcxl3-driver's translated virtual port), not the raw surface — silence there just means the driver had no input yet." "learning": "The Bridge ran 'gig-up.sh --converge --quiet' but gig-up.sh's arg guard only knew --headphones/--help, so every tray GIG UP died on flag 1 (exit 2, output captured). Flags now parsed: --converge boots SuperDirt + converges units with NO app windows, --quiet mutes ok/info. midimon watches 132:0 (the lcxl3-driver's translated virtual port), not the raw surface \u2014 silence there just means the driver had no input yet."
}, },
{ {
"learning": "Two gig-up.sh exist: root (1-click, owns leds()) and tools/ (the GO/NO-GO gate the Bridge calls, where lcxl3-driver is boot=manual so nobody starts it). The v3 LCXL3 enumerates as 'LCXL3 1', breaking every v2 'Launch Control XL' grep — present/LEDs/path checks all silently misread. lcxl-path must match the driver's virtual PORT name 'ParVagues LCXL3' (under RtMidiOut client), never chase the PipeWire relay mesh (everything reaches everything via 143)." "learning": "Two gig-up.sh exist: root (1-click, owns leds()) and tools/ (the GO/NO-GO gate the Bridge calls, where lcxl3-driver is boot=manual so nobody starts it). The v3 LCXL3 enumerates as 'LCXL3 1', breaking every v2 'Launch Control XL' grep \u2014 present/LEDs/path checks all silently misread. lcxl-path must match the driver's virtual PORT name 'ParVagues LCXL3' (under RtMidiOut client), never chase the PipeWire relay mesh (everything reaches everything via 143)."
}, },
{ {
"learning": "WirePlumber 0.4.17 ignores wireplumber.conf.d SPA-JSON rules (0.5 syntax) — this box reads Lua from main.lua.d only, and a partial alsa_monitor.rules reassignment clobbers the built-in table by index (dropped the card to profile off). Every wireplumber restart tears down Ardour's JACK client; recovery needed a full Ardour relaunch. pactl/wireplumber restart restored the missing HiFi profile after a UCM probe failure (card showed only off/pro-audio)." "learning": "WirePlumber 0.4.17 ignores wireplumber.conf.d SPA-JSON rules (0.5 syntax) \u2014 this box reads Lua from main.lua.d only, and a partial alsa_monitor.rules reassignment clobbers the built-in table by index (dropped the card to profile off). Every wireplumber restart tears down Ardour's JACK client; recovery needed a full Ardour relaunch. pactl/wireplumber restart restored the missing HiFi profile after a UCM probe failure (card showed only off/pro-audio)."
}, },
{ {
"learning": "A generated boot file is only as good as the list it is generated FROM. gig-up.sh rewrote preload.scd on every launch from a default naming the PREVIOUS gig (setlist_opal2026.txt): measured 132 banks -> 45, leaving 41 of Thursday's banks to be read off disk mid-set — the exact 'crackle at the venue' failure the check was built for, re-armed by a stale default. Two structural lessons. (1) Name the set ONCE and have every consumer read that; the plan generator and the gate disagreeing about 'the set' is the whole bug. (2) An unconditional write is destructive in the quiet direction: the plan in place was a deliberate 132-bank UNION and regeneration could only narrow it, so gen_preload now asks check-preload first and leaves a covering plan alone. Corollary from the same pass: merging two tools that share a name means diffing what they DO, not just what they check — the root launcher span raw sclang while the other started parvagues-sc.service, so repointing the Bridge at 'one launcher' would have silently taken it off systemd (losing rlimits, the journal, supervision and OOM protection)." "learning": "A generated boot file is only as good as the list it is generated FROM. gig-up.sh rewrote preload.scd on every launch from a default naming the PREVIOUS gig (setlist_opal2026.txt): measured 132 banks -> 45, leaving 41 of Thursday's banks to be read off disk mid-set \u2014 the exact 'crackle at the venue' failure the check was built for, re-armed by a stale default. Two structural lessons. (1) Name the set ONCE and have every consumer read that; the plan generator and the gate disagreeing about 'the set' is the whole bug. (2) An unconditional write is destructive in the quiet direction: the plan in place was a deliberate 132-bank UNION and regeneration could only narrow it, so gen_preload now asks check-preload first and leaves a covering plan alone. Corollary from the same pass: merging two tools that share a name means diffing what they DO, not just what they check \u2014 the root launcher span raw sclang while the other started parvagues-sc.service, so repointing the Bridge at 'one launcher' would have silently taken it off systemd (losing rlimits, the journal, supervision and OOM protection)."
}, },
{ {
"learning": "A launcher's exit code cannot tell 'it worked' from 'it did nothing'. gig-up.sh --help prints its usage and exits 0, and the window wrapper closed on that zero exactly as it closes on a good launch -- so the rig had never once been started from the icon, and the only evidence was the LCXL staying dark. The log had been written faithfully all along and held nothing but the usage text. Structural lesson: for any wrapper that closes on success, the success test must be a POSITIVE marker the wrapped thing prints on its real path ('gig-up done'), never the absence of an error. And hold the coupling from both ends in tests, because the day someone rewords that last line, every real launch starts reporting a failure." "learning": "A launcher's exit code cannot tell 'it worked' from 'it did nothing'. gig-up.sh --help prints its usage and exits 0, and the window wrapper closed on that zero exactly as it closes on a good launch -- so the rig had never once been started from the icon, and the only evidence was the LCXL staying dark. The log had been written faithfully all along and held nothing but the usage text. Structural lesson: for any wrapper that closes on success, the success test must be a POSITIVE marker the wrapped thing prints on its real path ('gig-up done'), never the absence of an error. And hold the coupling from both ends in tests, because the day someone rewords that last line, every real launch starts reporting a failure."
}, },
{ {
"learning": "A viewer reading the wrong port draws a coherent, confident, WRONG picture. With lcxl3-driver down, midiviz fell through its port preference to the raw v3 DAW stream, where row C's knobs carry row B's v2 numbers and the faders carry CCs in no cell at all -- so four separate 'bugs' got reported (D dead, EF misplaced, only ABC moving, rain only on the faders) and every one of them was the same missing translation. The only thing on screen that said so was the port label in the header. When a translated stream and a raw stream can both satisfy a name match, the viewer owes the user a pip for WHICH ONE it is reading, next to the paused and stream-broken pips." "learning": "A viewer reading the wrong port draws a coherent, confident, WRONG picture. With lcxl3-driver down, midiviz fell through its port preference to the raw v3 DAW stream, where row C's knobs carry row B's v2 numbers and the faders carry CCs in no cell at all -- so four separate 'bugs' got reported (D dead, EF misplaced, only ABC moving, rain only on the faders) and every one of them was the same missing translation. The only thing on screen that said so was the port label in the header. When a translated stream and a raw stream can both satisfy a name match, the viewer owes the user a pip for WHICH ONE it is reading, next to the paused and stream-broken pips."
}, },
{ {
"learning": "A fix that trades a wrong setting for no function is not a fix, and the fence belongs in the tool rather than in the judgement of whoever runs it. check-audio's textbook repair -- clear the stale pin, take WirePlumber's own pick -- would have switched off the only four sinks on the box, because the laptop codec was pinned off and the USB interface unplugged. So --fix counts the sinks it would leave behind and refuses at zero. Also: a config fragment in a neighbouring version's format (0.5 .conf.d on an 0.4.17 install) is worse than a missing one, because it looks exactly like protection that is in force -- the July DMIC guard had never applied, and what had kept the box safe was the card being switched off." "learning": "A fix that trades a wrong setting for no function is not a fix, and the fence belongs in the tool rather than in the judgement of whoever runs it. check-audio's textbook repair -- clear the stale pin, take WirePlumber's own pick -- would have switched off the only four sinks on the box, because the laptop codec was pinned off and the USB interface unplugged. So --fix counts the sinks it would leave behind and refuses at zero. Also: a config fragment in a neighbouring version's format (0.5 .conf.d on an 0.4.17 install) is worse than a missing one, because it looks exactly like protection that is in force -- the July DMIC guard had never applied, and what had kept the box safe was the card being switched off."
}, },
{ {
"date": "2026-09-23", "date": "2026-09-23",
"task": "LCXL3 name-resolver sweep", "task": "LCXL3 name-resolver sweep",
"learning": "A resolver that matches hardware BY PRODUCT NAME outlives the hardware it names, and it fails silently in every tool that copied it. 'Launch Control XL' -> 'LCXL3 1' broke three independent things the same way: the driver crash-looped 1077 times, midiviz drew v3 numbers into v2 cells, and gig-log's MIDI leg never bound a port in nine recorded sessions. The generation change was one event; the breakage was three, because the string was in three files. Grep the product name across the tree the day the hardware changes, and put the translation table in the GRID rather than in one of its readers -- then a reader is right whether or not the translator is running." "learning": "A resolver that matches hardware BY PRODUCT NAME outlives the hardware it names, and it fails silently in every tool that copied it. 'Launch Control XL' -> 'LCXL3 1' broke three independent things the same way: the driver crash-looped 1077 times, midiviz drew v3 numbers into v2 cells, and gig-log's MIDI leg never bound a port in nine recorded sessions. The generation change was one event; the breakage was three, because the string was in three files. Grep the product name across the tree the day the hardware changes, and put the translation table in the GRID rather than in one of its readers -- then a reader is right whether or not the translator is running."
}, },
{ {
"date": "2026-09-23", "date": "2026-09-23",
"task": "gig-log MIDI capture", "task": "gig-log MIDI capture",
"learning": "A capability check that asks 'is the tool installed' instead of 'did it bind' produces a feature that reports itself ON while doing nothing. MidiReader.available() returned shutil.which('aseqdump'), so the header wrote midi:true for nine sessions that contain zero cc records. The honest test is always the OUTPUT, not the precondition: count what landed. And record WHICH source was bound (an 'mbind' line) -- the nine silent sessions were unexplainable precisely because nothing wrote down that the answer was 'none'." "learning": "A capability check that asks 'is the tool installed' instead of 'did it bind' produces a feature that reports itself ON while doing nothing. MidiReader.available() returned shutil.which('aseqdump'), so the header wrote midi:true for nine sessions that contain zero cc records. The honest test is always the OUTPUT, not the precondition: count what landed. And record WHICH source was bound (an 'mbind' line) -- the nine silent sessions were unexplainable precisely because nothing wrote down that the answer was 'none'."
}, },
{ {
"date": "2026-09-23", "date": "2026-09-23",
"task": "the gate's own blind spots", "task": "the gate's own blind spots",
"learning": "A probe gated on a precondition it does not need skips exactly when it matters. check-gig's painter probe carried needs=('scsynth',), so the one check that asks 'is the surface actually lit' stayed silent through every cold run -- which IS the setup window. Ask of every precondition: does the answer become MEANINGLESS without this, or merely inconvenient to obtain? The painter has nothing to do with the audio engine." "learning": "A probe gated on a precondition it does not need skips exactly when it matters. check-gig's painter probe carried needs=('scsynth',), so the one check that asks 'is the surface actually lit' stayed silent through every cold run -- which IS the setup window. Ask of every precondition: does the answer become MEANINGLESS without this, or merely inconvenient to obtain? The painter has nothing to do with the audio engine."
}, },
{ {
"date": "2026-09-23", "date": "2026-09-23",
"task": "the sink that went nowhere", "task": "the sink that went nowhere",
"learning": "pro-audio reports available:yes unconditionally, because it maps raw PCMs and asks no questions about cables. So a GPU HDMI card pinned to pro-audio offered four healthy-looking sinks while every ELD read monitor_present 0 and every connector read disconnected -- audio converted and discarded, for months. When a subsystem's own availability flag is structurally incapable of being false, go around it to the kernel (ELD, /sys/class/drm) rather than trusting the layer that has an opinion." "learning": "pro-audio reports available:yes unconditionally, because it maps raw PCMs and asks no questions about cables. So a GPU HDMI card pinned to pro-audio offered four healthy-looking sinks while every ELD read monitor_present 0 and every connector read disconnected -- audio converted and discarded, for months. When a subsystem's own availability flag is structurally incapable of being false, go around it to the kernel (ELD, /sys/class/drm) rather than trusting the layer that has an opinion."
}, },
{ {
"date": "2026-09-23", "date": "2026-09-23",
"task": "stale unit builds", "task": "stale unit builds",
"learning": "A systemd unit holds whatever the interpreter read at ExecStart, so from outside a unit running three-day-old code is indistinguishable from a healthy one: same active(running), same journal. A pass was lost debugging a midiviz that predated the fixes being debugged. The only honest test is the unit's ActiveEnterTimestamp against its ExecStart file's mtime -- deliberately NOT a git comparison, because the question is 'is the process executing what I can read', so an uncommitted edit counts." "learning": "A systemd unit holds whatever the interpreter read at ExecStart, so from outside a unit running three-day-old code is indistinguishable from a healthy one: same active(running), same journal. A pass was lost debugging a midiviz that predated the fixes being debugged. The only honest test is the unit's ActiveEnterTimestamp against its ExecStart file's mtime -- deliberately NOT a git comparison, because the question is 'is the process executing what I can read', so an uncommitted edit counts."
}, },
{ {
"date": "2026-09-24", "date": "2026-09-24",
"task": "midiviz: Tidal HL feed + ParVagues wave backdrop, column glow slowed 30%", "task": "midiviz: Tidal HL feed + ParVagues wave backdrop, column glow slowed 30%",
"learning": "Tidal already ships 4 OSC targets in BootTidal.hs — mirroring superdirtShape to a 5th port (oscfeed.VIZ_PORT 57130) gives the HL trigger feed without touching SuperDirt's port; and an OSC name/value fold must compare str(k+1) keys, not int, or every named arg silently vanishes." "learning": "Tidal already ships 4 OSC targets in BootTidal.hs \u2014 mirroring superdirtShape to a 5th port (oscfeed.VIZ_PORT 57130) gives the HL trigger feed without touching SuperDirt's port; and an OSC name/value fold must compare str(k+1) keys, not int, or every named arg silently vanishes."
}, },
{ {
"date": "2026-09-24", "date": "2026-09-24",
"task": "HL feed wired end-to-end; wordmark, single-instance, tray-via-systemctl", "task": "HL feed wired end-to-end; wordmark, single-instance, tray-via-systemctl",
"learning": "Tidal 1.9 addresses superdirtShape packets /dirt/play, not /play — captured off the wire, the documented address silently dropped every real packet while every synthetic test passed; and font metrics lie about letter-spacing, so a bake must scan its own ink. Synthetic tests share the author's assumptions — one real wire capture beats a hundred self-tests." "learning": "Tidal 1.9 addresses superdirtShape packets /dirt/play, not /play \u2014 captured off the wire, the documented address silently dropped every real packet while every synthetic test passed; and font metrics lie about letter-spacing, so a bake must scan its own ink. Synthetic tests share the author's assumptions \u2014 one real wire capture beats a hundred self-tests."
} },
{
"task": "midiviz extracted to standalone opensource repo",
"date": "2026-09-25",
"where": "~/Work/Sound/midiviz (github.com/PLNech/midiviz pushed; git.nech.pl/pln/midiviz pending repo creation)",
"learning": "A branding override is a fuzzing harness in disguise: exposing the wordmark to arbitrary strings immediately found an UnboundLocalError in _word_pixmap's fit branch (pad used before assignment) that PARVAGUES could never reach \u2014 the default input only exercises the author's assumptions."
},
{
"date": "2026-09-26",
"task": "Export + master Take103 (AI ENGINEER set, partial: rec joins 3 min in, d8 never armed) and Take104 (Rose Rouge club mix) from Ardour interchange mono pairs; 4 masters (-14/-9) + 13-track split in ~/Work/Sound/Prod/AIEngineer2026_master; segments authored from gig-log track switches; fixed tidal-ears split --segments slug crash (3bafd03)",
"learning": "The Ardour interchange capture IS the export: mono %L/%R pairs + gig-log track timeline replace the DAW export pass and silence-based boundary detection entirely."
}
] ]
\ No newline at end of file
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