1. 05 Sep, 2026 16 commits
    • feat(tracks): veridis_quo jam tweaks + rose_rouge notes from the validated set · e1b75e28
      veridis_quo (PLN, first jam): hats ply gate moved to ^43 (E3, its own column),
      bass line up an octave. rose_rouge: comments from the night it re-validated the
      rebuilt rig — a sample-kit idea and the bassline's new name.
      PLN (Algolia) authored
    • fix(lcxl3): OLED home repaint — dedup on what was SENT, reassert every 20s · d3f58c6e
      The footer froze at '?REC 00:10' on the glass during the first real take. No
      code-level freeze reproduced; what WAS wrong: the ghost mirror recomputed state
      instead of mirroring device writes (so diagnosis lied), and the 2s tick wrote
      unconditionally with no dedup. Now: _home_cache records the string actually
      sent, the tick writes only on change (repaint ≤2s, revert ≤2s after stale), and
      the 20s label-reassert forces a full repaint so dedup can never mask a device-
      side reset. Hardware verdict awaits the next recorded take.
      PLN (Algolia) authored
    • fix(gig-log): three lessons from the first real take · 7e64736b
      Bug 1: Ardour flushes stems in ~12MB bursts every ~3s — a 2s stop-grace read
      every gap as stop/start and reset the clock eleven times in one 20s take.
      Grace is now 6s, with the measured cadence in the comment.
      Bug 2: transport-button feedback IS bit-gated — /set_surface feedback 8→9
      (button-status + heartbeat); the 'not bit-gated' reading died on the live rig.
      Bug 3: a mute lens is not a dissenting lens — combine_rec only counts OSC when
      arm AND roll are concrete, so 'connected but silent' can no longer stamp
      conflict:true on ground truth. src labeling now names actual contributors.
      Regression tests drive RecPass through the real flush cadence; 169 green.
      PLN (Algolia) authored
    • feat(lcxl3): the OLED footer becomes a REC clock while Ardour records · d1947eef
      Third line of the home screen: 'ParVagues' at rest; 'REC mm:ss' while the
      gig-log k:'rec' stream says audio is landing on disk (tail-read of the newest
      gig jsonl, same style as last_eval); '?REC' when the files and OSC lenses
      disagree — a timer the performer must not trust blindly is marked, not hidden.
      Stale or absent signal degrades to the plain footer, never crashes a redraw.
      Pure decision function rec_line() + tests.
      PLN (Algolia) authored
    • fix(gig-log): resolve the Ardour session without argv — a 3-rung evidence ladder · abad7515
      A GUI-launched Ardour has a BARE argv (only gig-up passes the session as an
      argument), so argv-matching alone left the files lens blind on the real rig.
      Resolution now climbs: argv → /proc/<pid>/cwd holding a *.ardour → open-fd
      symlink targets (most-fds wins on ambiguity, logged once). Injectable proc_root
      makes each rung testable against a fake /proc; 9 new tests reproduce the exact
      live gap (163 green). check-mix.py shares the latent argv-only bug — flagged in
      the sync comment, not fixed this pass.
      PLN (Algolia) authored
    • feat(gig-log): k:'rec' — two lenses on Ardour actually recording · 33897540
      'Recording X had track Y playing at time Z' becomes answerable. Files lens =
      ground truth (stems growing in interchange/, stat-only, 2s grace, RecPass state
      machine); OSC lens = Ardour's own word (/set_surface feedback=8 handshake to
      :3819, listens :3820, /rec_enable_toggle + transport — the manual's real name
      for master arm, not /rec_enabled). Records on transitions + every tick while
      recording; lens disagreement ships as conflict:true, never smoothed over.
      44 new tests (155 total green), stdlib-only, --no-rec escape hatch.
      PLN (Algolia) authored
    • fix(tools): parvagues-gear ships executable · 619bbae5
      The straggler from 3fdf5b97's sweep: with core.fileMode=false a PATHSPEC commit
      reads modes from the working tree (where they're invisible) and skips a staged
      mode-only flip — index-only commits are how mode fixes land here.
      PLN (Algolia) authored
    • chore(rig): regenerate parvagues-rig.target from the inventory · 1667bb02
      lcxl-leds-watch (manual policy) leaves the target; lcxl3-driver and perf-tray
      join it. Generated by rig_units.py --target — the ~/.config copy is a symlink
      into the repo, so the regen IS this diff.
      PLN (Algolia) authored
    • perf-tray + gig-up: default-on 'Cut GPU at gig launch' (kill TuParles, verify… · 90afa239
      perf-tray + gig-up: default-on 'Cut GPU at gig launch' (kill TuParles, verify dGPU RTD3, name holders if it won't sleep)
      PLN (Algolia) authored
    • fix(lcxl3): lap 1 starts at first track load, not process construction · da1c7c73
      The OLED's track clock resets 'a new track is a new lap' — but skipped the
      FIRST load, so lap 1 inherited the constructor's zero. Invisible while the
      driver restarted with every session; under the persistent unit (log 039) it
      counted the idle hours before the first load: 24:27 on a fresh rose_rouge.
      PLN (Algolia) authored
    • feat(rig): the LCXL3 driver joins the supervised rig — one inventory, one painter · 96f13223
      The night's lesson, converged: the driver only ran when a session remembered to
      start it, then the session's background task got OOM-reaped and the surface sat
      untranslated with every check green.
      
      - tools/lcxl3-driver.service: repo-owned unit like its siblings; retry-forever
        hotplug posture; Conflicts=lcxl-leds-watch so exactly one painter owns the
        board; follow-mode (no --track) so paint tracks the loaded file.
      - rig_units.py: lcxl3-driver + perf-tray rows (THE list is complete again),
        new 'manual' boot policy so --ensure can never start the v2 painter into a
        Conflicts= kill; --list --boot filter for shell consumers.
      - parvagues-gear: moved into the repo; derives its sets from rig_units --list
        instead of being the fourth hardcoded copy; autostart-on now defers to
        --ensure (it used to enable parvagues-sc at login against policy).
      - gig-up.sh leds(): detects the plugged surface generation and hands the LCXL3
        to the driver unit; v2 path invoked via python3 (exec-bit-proof).
      PLN (Algolia) authored
    • fix(tools): restore the exec bit on 63 scripts, in the INDEX where it counts · 3fdf5b97
      core.fileMode=false means chmod fixes your tree and nothing else — the 203/EXEC
      class (log 032) recurs on every fresh checkout because the index said 644.
      git update-index --chmod=+x is the repo-level fix; every shebang'd script under
      tools/ + gig-up.sh + perf.sh now ships executable.
      PLN (Algolia) authored
    • feat(tools): gig-preflight — assert the rig is ready BEFORE you play · 774087a0
      The rig is heavily instrumented and nothing ever looked. gig-log has been
      sampling freq, per-core cpu, throttle deltas, fan rpm, per-process cpu/RSS and
      xruns at 1 Hz for weeks (512k samples in the current file), yet every failure
      this session was discovered by feel, mid-session, at the worst moment. This is
      the looking.
      
      Every check exists because it silently cost a real session on 2026-09-04/05:
      
        * Composed an hour at 800 MHz because the Bridge's perf-watch reasserted
          `silent` every 30 s, undoing each fix within half a minute. Cost 2671
          xruns/hour. Every throttle counter read zero — with HWP, epp=power makes the
          CPU *choose* its floor, so the failure has no error message.
            -> check_perf, check_regime_agreement
        * `gluck` refused to load for 90 minutes. The symlink was perfect; sclang had
          been up 6 days and loadSoundFiles scans folder NAMES once, at startup, so a
          bank added later is invisible. Pulsar's "restart SuperDirt" button is a no-op
          here (SuperDirt is a systemd service, not Pulsar's child) and honestly
          reported 142 h uptime while looking fine.
            -> check_superdirt_fresh
        * Samples/crutch was a symlink pointing at ITSELF since Oct 2025. Dead bank,
          no complaint from anything.
            -> check_sample_integrity
        * A new LCXL3 showed default LEDs because the resolver name-matches the mk2
          product string, while the watcher logged "-> 27 controls" and looked healthy.
            -> check_midi_surface
      
      Three design rules, all learned the hard way:
      
        1. Green is not evidence. `active`, exit 0 and silence all lied during this
           session, so every check reads the LIVE value from sysfs/procfs/the device
           and prints the number it saw. No check trusts a status.
        2. Every FAIL carries its fix as a runnable command. A preflight that reports
           a problem without the remedy just adds a step between you and playing.
        3. It never mutates the rig. Read-only by construction — a preflight that
           writes is one more un-arbitrated writer, the exact bug class it detects.
      
      16 checks. First run found 1 fail + 3 warns, including a genuine xrun rate of
      2224/hour with every CPU line green — a problem we had not noticed and which the
      clock fix does not explain.
      
      Known over-sensitivity: check_xruns uses the raw total, and two of the top three
      sources (input.hydra_in, the built-in Speaker sink) may not be in the live
      monitoring path at all. Xruns on an idle sink inflate the count without being
      audible; it should weight by nodes actually in the path.
      
        gig-preflight.py            human report, exit 1 on any FAIL
        gig-preflight.py --quiet    only WARN/FAIL, for gig-up.sh
        gig-preflight.py --json     for the tray/Bridge
      PLN (Algolia) authored
    • fix(lcxl): find the mk3 — "LCXL3 1" is not "Launch Control XL" · f7d38407
      A new Launch Control XL 3 sat there showing factory-default LEDs. Not autostart
      (lcxl-leds-watch was active), not wiring (aconnect showed the surface connected
      to Midi Through): the port resolver matches the product name "Launch Control XL",
      and the mk3 enumerates as "LCXL3 1". So _find_hw_port()/_find_seq_port() both
      returned None and every frame went into the void — while the parse stage kept
      logging "-> 27 controls" and looking perfectly healthy. A failure with no error
      message, which is the worst kind and the whole reason gig-preflight now exists.
      
      NAME_RE also matches \blcxl\d*\b. The inline HUI exclusion becomes SKIP_PORT_RE,
      which additionally drops the mk3's DAW and "To DIN" ports — the mk2 exposed one
      non-LED port, the mk3 exposes three, and sending a frame to any of them is a
      silent no-op.
      
      Validated: resolver now returns hw:1,0,0 and seq 20:0; it returned None before.
      
      NOT fixed here — the LED dialect. The device answered a Universal Device Inquiry
      with:
      
        F0 7E 00 06 02  00 20 29  48 01  00 00  01 01 0B 39  F7
                        Novation  family        firmware 1.1.11
      
      and per Novation's programmer's reference the mk3 lights controls in RGB, one
      message per control:
      
        F0 00 20 29 02 15 01 53 <control index> <R> <G> <B> F7
      
      versus this file's mk2 frame: device id 0x11, command 0x78, a User-1 template,
      batched (index, value) pairs, and a bicolor 2-bit red x 2-bit green = 16-state
      colour model. So mk3 support is a second dialect plus a replacement colour model
      (RGB doesn't map onto 16 bicolor states), and the per-control message form means
      a full 40-control repaint costs 40 SysEx messages where the mk2 cost one — the
      paint loop will need rate limiting. Tracked in SRE TODO.d.
      PLN (Algolia) authored
    • fix(perf): Pulsar stays fast in every mode — the editor IS the instrument · e42586b8
      perf-audio deprioritized Pulsar to nice 10 in cool/extreme and nice 5 in
      standard, on the theory that the editor should yield to audio. That trade buys
      nothing. The entire audio chain runs SCHED_FIFO *above* it — scsynth 90,
      sclang 85, PipeWire 95, WirePlumber 95, Ardour 80 — and a realtime policy
      preempts every CFS nice level unconditionally. So Pulsar at -5 cannot take a
      single cycle from SuperDirt; it only wins against ordinary processes (browsers,
      indexers, builds), which is exactly what we want during a composing session.
      
      Now nice -5 in every mode, overridable with PULSAR_NICE=NN (0 = neutral,
      positive = the old behaviour). -5 rather than 0 because the renderer is a single
      JS thread and its work is bursty: it has to win the CPU on the tick after a
      keystroke, then yield. No root needed — @audio's `nice -19` grant in
      /etc/security/limits.conf is what makes the same renice work for pln.
      
      Validated: installed to /usr/local/sbin/perf-audio, re-applied via
      `bridge.py perf standard`, all Pulsar processes confirmed at nice -5 live.
      PLN (Algolia) authored
  2. 04 Sep, 2026 1 commit
  3. 02 Sep, 2026 9 commits
    • merge: tempo_timeline, the confirmed gig facts, and the board · 6850da97
      Two commits, 4 files, reviewed as a diff against master.
      
        tempo_timeline.py       tempo per window in a rendered track, to test whether
                                a planned track was actually played. Answered WAP: not
                                played (median 124.5 over segment 1, nothing at 133
                                above the confidence floor), calibrated first on a
                                declared-160 track which returned 161.5.
        gigmeta sidecar         PLN's confirmed set time (~20:30), his verbatim
                                approval of "Le Jardin", and the WAP question moved
                                from open to resolved with both sources named.
        build_gig_tracksjson    resolve the frontmatter-only fields before writing
                                _provenance, so their origin is recorded whether or
                                not --md-out was passed.
        TASKS_DUMP.md           the board after the release session: six resume
                                points, two of which are the kind that look done when
                                they are not.
      
      Two bugs of mine are in the branch rather than tidied out of it: the octave fold
      was an infinite loop for any value a sub-octave band cannot represent, and the
      per-frame FFT loop was ~100k Python-level calls for a five-minute stem.
      PLN (Algolia) authored
    • docs(board): CosmicSet is on the shelf — the board after the release session · b3279c8d
      The two-set release board from 2026-09-01 is half discharged. CosmicFest-26 went
      from "finished on disk with nowhere to go" to a private 15-item release behind a
      canonical www gig page; OPAL-26's remaining upload work is now the only release
      thread left, and it is unblocked rather than in flight.
      
      Six resume points, each with the exact command and the wanted output, because
      two of them are the kind that look done when they are not: the uploader saying
      "N uploaded" is not evidence that N tracks exist (two were accepted, verified,
      and then vanished), and renaming the mix's permalink without regenerating the
      plan would make the next run re-upload 693 MB rather than skip it.
      
      Also recorded: the www branch is committed but unpushed because that repo is on
      github and no key was loaded, the CosmicFest plan still carries `_UNSIGNED` so
      nothing may be flipped public, and the two levers left on the record are the LOW
      end (the real harmony lever, much bigger than the air shelf) and the deferred
      club variant. Plus a note that piment_bresilien.tidal is PLN's own edit.
      PLN (Algolia) authored
    • feat(tide-table): tempo_timeline — check whether a planned track was played · 3ed1bc2d
      PLN's set plan lists WAP [133] second at CosmicFest and the recording has no WAP
      in it. He remembered dropping it — "didnt play WAP i think in the end [...]
      direct from Bombe DJ to Something about Drums, its what i chose that day" — and
      asked me to check rather than take his word, which is the right instinct: the
      rival explanation is that WAP IS in the recording, inside segment 1, and the
      boundary pass read two tracks as one. Duration cannot separate those (track 1 is
      6.8 min, an ordinary length here for one track and also for two short ones).
      
      Tempo can, because the two candidates differ: Bombe DJ is 124 and WAP is 133,
      which is 0.484 s against 0.451 s per beat — 7% apart, well inside what an
      inter-onset histogram resolves. So this walks a rendered track in windows and
      reports what tempo each window is actually playing, on the demucs DRUMS stem
      rather than the master, because the question is about kick timing and a stem is
      far cleaner to find onsets in than a stage-mic mix.
      
      Method deliberately copied from tools/tempo-lens.py, which measures a live orbit
      and cannot read a file: spectral-flux onsets, then the MODE of the inter-onset
      intervals — never the mean, which over a pattern with ghost notes lands between
      two real values and reports a tempo nothing is playing.
      
      Calibrated before it was trusted, on There's Something About Drums (declared
      160): median 161.5. Then the answer, on segment 1 with --expect 124 --alt 133:
      median 124.5 over 26 windows with a steady pulse, and NO window at 133 above the
      steadiness floor. One 20 s window reads 132.5 at confidence 0.11, below the
      floor and with no neighbour agreeing — WAP as a played track would occupy twelve
      to twenty-four consecutive windows, not one. So: not played. PLN's memory and
      the audio agree, and the boundary pass did not miss a track.
      
      Two bugs of my own on the way here, both worth the record:
      
        * the octave fold was an INFINITE LOOP. Folding into a band of [100, 180) by
          doubling-and-halving never terminates for a value like 99: 99 -> 198 (out
          the top) -> 99 -> 198, because no power of two lands it inside a band
          narrower than an octave. Two processes sat at 98% CPU while I assumed the
          machine was merely busy with the upload. The fold is now closed-form
          (`bpm * 2**-floor(log2(bpm/lo))`) into [X, 2X), and the CLI takes
          --octave-from because an octave is the only honest width: tempo from audio
          is determined up to a factor of two and no further.
        * the per-frame FFT loop was ~100k Python-level calls for a five-minute stem.
          One strided view and one batched rfft instead.
      
      Also, since the meta sidecar is where facts live: PLN's confirmed set time
      (~20:30), his verbatim approval of "Le Jardin", and the WAP question moved from
      open to resolved with both sources named. And build_gig_tracksjson now resolves
      the frontmatter-only fields (time, location, CTA, tags) BEFORE writing
      `_provenance`, so their origin is recorded in tracks.json whether or not
      --md-out was passed. tracks.json is the file that outlives the invocation.
      
      Side finding for PLN's ears, reported rather than acted on: over the last ~80 s
      of segment 1 the drums stem reads 161.5 BPM — the tempo of the NEXT track. Two
      readings, and I cannot choose between them from here. Either the segue is a long
      livecoded blend with Something About Drums layered in early (which is what
      "direct" looks like in TidalCycles), or the boundary at 6:46.6 sits later than
      the music does. His ear owns that one.
      PLN (Algolia) authored
    • docs(log 038): part III — the record reaches the shelf, and three notes I had already written · 89f9d712
      PLN asked three questions this morning and each one landed on a different
      failure of mine, so the log gets a part III rather than a tidy epilogue.
      
      The demucs OOM he spotted was a fossil: the killed first attempt (8.2 GB, 2 of
      14 sections) sits in the journal forever beside the rerun that succeeded at
      7.6 GB. A unit's history is not its state — verify the artefacts.
      
      The +5-vs-+8 recommendation is corrected. I had called +8 "the conservative end"
      against a generic genre norm; against OPAL v4, the master he actually signed off,
      CosmicFest is already 6.6-8.1x brighter above 4 kHz on a third of the sub. So
      +5, and the lesson is about naming the reference: a genre norm and the artist's
      own shelf can point opposite ways, and only one of them answers "does it sit with
      my other release". Recorded with the caveat that most of that table is the
      recording, not the master — and that the unspent lever for harmony is the LOW end,
      not the shelf we A/B'd all night.
      
      And the gig metadata was never missing. His own repo held the date, the location,
      the stage and the lineup; his backlog held the five movements. The blocker was
      that I had not looked, and in that gap I had written an unsourced 2026-08-23 into
      the spec. Wrong by a day, with the answer two files away.
      
      Plus the three things that only running the pipeline could show: POST /tracks is
      dead AND burns the S3 upload first, --force never worked for the case it was
      named after, and the catalog gap was a cycle rather than a stale cache.
      PLN (Algolia) authored
    • merge: the CosmicFest release pipeline, and a shelf that cannot collide · b747cc70
      PLN: "lets merge on main all these tooling improvements and go on".
      
      17 commits, 16 files, all reviewed as a diff against master before merging and
      not just at the tip — the lesson from the 200-file merge in ea52a32c..2f947c2c.
      Twelve are new files, four are edits: build_release_plan.py, catalog_view.json
      (a regeneration, 73 -> 81 tracks, dropping none), the CosmicFest spec, and
      TASKS_DUMP.md.
      
      What it adds, in one line each:
      
        master_stemless.py     a bus-only master for a gig with no stems
        demucs_sections.py     separation in bounded chunks, sections and global
        eda_stems.py           the required stem EDA, from separations instead
        setlist_to_segments.py an ear-called setlist becomes segments
        compare_masters.py     an A/B in numbers, so the ear spends itself on taste
        build_gig_tracksjson.py  a gig page generated from sourced facts
        check_permalinks.py    the shelf is one namespace; refuse a collision
        finish_cosmic.sh       everything downstream of the masters, unattended
      
      Delivered: two masters in spec (-14.0 LUFS, -0.9 dBTP, 44.1/24, duration exact
      to a microsecond against the source), a 14-track split that verifies clean, 14
      section stem sets plus a global pass, and three EDA questions answered — the
      missing air is cymbals, the kick is present 44.8% of the set against OPAL's
      59%, and the global separation pass is a measured waste of time (corr
      0.943-0.977, level deltas under a quarter of a dB).
      
      Four things I got wrong are recorded in the branch rather than tidied out of it,
      because three shared one shape worth naming: a check whose failure looks exactly
      like success. A dropped rtk grep plus a block-buffered log made a live render
      look dead, and two processes wrote the same paths for forty minutes. A speed
      claim counted section directories, which appear when a writer OPENS. An edit
      no-op'd while printing that it had worked. The fourth was not new — the first
      full render came out at 192 kHz, a loudnorm trap this project had already
      documented and I did not read before writing a new chain.
      
      Not merged and still PLN's: the +5 vs +8 air-shelf call, and a release_signoff.
      The CosmicSet upload on the shelf right now is PRIVATE and its plan says
      _UNSIGNED.
      PLN (Algolia) authored
    • fix(release): the shelf is one namespace and a live catalogue reuses titles · 34b02925
      Caught before --go, on the last check I nearly skipped. The CosmicSet plan
      resolved four of its fifteen permalinks onto tracks Opal had already uploaded:
      /you-my-sunshine, /take-five-drops, /piment-bresilien, /eh-ouais-je-funk. Of
      course it did — those are the same scores, played at both gigs a fortnight
      apart, and a permalink is derived from the title.
      
      What makes this worth a gate rather than a fix is the failure mode. SoundCloud
      is idempotent by permalink and the uploader treats a taken permalink as "already
      done" — correct for resuming an interrupted 1.4 GB run, catastrophic for a
      different recording of the same track. So the upload would NOT have errored. It
      would have reported success with four tracks missing from the release, or, with
      --overwrite, replaced Opal's audio with CosmicFest's. Neither is visible from
      the terminal that ran it. `feedback_absence_needs_proof`: a release that is
      quietly short looks exactly like a release that is complete.
      
      check_permalinks.py resolves a plan against the live account and sorts every
      permalink into FREE / already-up-as-this-same-audio / COLLISION, using duration
      as the discriminator because it is the one property that survives SoundCloud's
      transcode and is already in the plan. A collision exits 1. Verified both ways:
      it fails the un-suffixed plan naming all four real collisions, and passes the
      suffixed one 15/15 free. Its shelf query carries access=playable,preview,blocked
      because scform.py's own notes record that private tracks are omitted without it
      — and an audition release is private by definition, so the gate would have
      called every permalink free.
      
      build_release_plan.py gains --permalink-suffix to fix the cause, so a gig's
      permalinks are scoped to the gig and no future pair of sets can collide.
      
      Two things about --force, which this release is the first to actually need (the
      signoff cannot precede the upload when SoundCloud is where PLN listens):
      
        * it never worked. The no-signoff branch did `return problems` before reaching
          the override, so the flag documented as "emit even without a valid ear
          signoff" could only override a STALE signoff, never a missing one. The early
          return is gone and both problems now reach the same override.
        * it now demands --why, and records the reason, the timestamp and a
          do-not-flip-public warning in the plan's own `_UNSIGNED` block. A terminal
          warning scrolls away; the JSON is what the uploader reads. An override with
          no stated reason is indistinguishable from an approved release two weeks
          later, and the gate exists precisely so nobody has to remember.
      
      Left alone deliberately: the plan cannot tell private from public — the uploader's
      --sharing flag decides that — so _UNSIGNED documents rather than enforces.
      PLN (Algolia) authored
    • feat(tide-table): a gig page can be generated without inventing anything · c5aaf892
      The CosmicSet release was finished on disk and could not move, because every
      upload adapter goes through build_release_plan.py and that refuses to run
      without a canonical www gig page. Writing the page by hand was never an option —
      `feedback_metadata_vs_mastering` exists because a previous release copied a wrong
      ALBUM string out of an intermediate script. So the generator learned to take
      supplied facts instead.
      
      --meta takes a JSON sidecar where every field is {value, source, locator, date}.
      The builder reads the values, carries the provenance into `_provenance` in the
      output, and prints "nothing left to invent" ONLY when no non-derivable field is
      still empty. A bare value is accepted — PLN often just says a thing in chat —
      but it is recorded as unsourced, so the gap stays visible instead of becoming
      indistinguishable from a researched fact.
      
      Which mattered immediately, because ONE OF MY OWN STRINGS WAS WRONG. The gig
      spec said 2026-08-23. I had written that with no source. This machine held the
      answer in two places all along: www/PRODUCT.md:21 says "cosmicfest_v2.67Hz
      (20-23 August 2026, jour J Saturday 22)", and the invite's own lineup section is
      headed "le 22 août, dans le jardin". PLN confirmed: "yea it was samedi 22
      indeed". The spec is corrected and now says where the date comes from. That is
      the second unsourced string of mine to survive into a spec, hence a sidecar with
      a locator column rather than one more hand-filled field.
      
      The set's five movements are not invented either: Ouverture / SUNSET / We call it
      NuJazz / NUIT / FINALE come from PLN's own plan in backlog.md, which
      `reference_gig_tracklist_sources` names as canonical. The sidecar records the one
      discrepancy rather than smoothing it: the plan plays WAP second and the recording
      has no WAP, so every section from track 2 on shifts up by one. Track 1 is 6.8
      min, in line with other single tracks (3 is 6.6, 7 is 6.3), so it does not look
      like two merged — but that is an inference and it is labelled as one.
      
      Sample coverage went 7 empty lists -> 0, and the cause was not staleness. The
      first two tracks I chased (Rose Rouge, LiveCode Parade) were absent from
      catalog_view even after a rebuild, because catalog_view's track universe comes
      from `load_gigs()` globbing the ALREADY-PUBLISHED content/lives/**/tracks.json.
      A track's first gig page can therefore never find itself in the view: the file
      that would add it is the one being written. That is a cycle, not a stale cache,
      and rebuilding forever would not have fixed it. The builder now falls back to the
      same `tidal_score.orbit_sounds` the view itself calls — one parser, reused, so a
      new track's sample list cannot disagree with a catalogued one. With the page in
      place the cycle closes: the rebuilt view goes 73 -> 81 tracks, dropping none, and
      both tracks are catalogued.
      
      Also: "collab" joins STYLE_DIRS, because OPAL already published it that way for
      every live/collab/* score. style_of takes the last matching path part, so
      live/collab/nova/techno/x still reads techno.
      PLN (Algolia) authored
    • fix(finish): the global-stem gate compares against the master now, not a magic number · 696ba12a
      A threshold of 3700 seconds against an expected 3779 accepted exactly the state
      the pass sits in for minutes: it writes in 120 s chunks, so 31 chunks is 3720 s —
      one short — and "basically done" sails through a loose comparison. My own monitor
      fired a premature COMPLETE on that very number, which is how I noticed: the check
      and the bug had the same blind spot because I wrote both.
      
      It now reads the expected duration from the master itself and allows two seconds,
      so the check cannot drift from the thing it checks.
      
      The fallback message says what a short global file would actually cost, which is
      the part worth naming: it does not FAIL the comparison, because compare_global
      takes min(len(a), len(b)). It compares fewer seconds per track and reports thinner
      evidence under identical column headings. Silent degradation in a summary nobody
      watches being written is worse than an error.
      
      Second attempt at this commit. The first edit silently no-op'd — a scripted
      replacement whose search string carried escaped line-continuations that did not
      match the file, while the script printed "gate tightened" unconditionally, and I
      reported success on the strength of that print. Caught by grepping the file. This
      version asserts the anchor exists before replacing and asserts the content changed
      after, then proves it by grep rather than by message. That is the third instance
      tonight of the same failure shape — a check whose failure mode is indistinguishable
      from success — so the assertions are the actual fix, not the gate.
      
      Held until now deliberately: finish_cosmic.sh was mid-execution, and bash reads
      scripts incrementally, so editing a running script can corrupt the run.
      PLN (Algolia) authored
    • docs(log 038): the results — two masters, 14 tracks, and a clean negative on the global pass · 5885da5f
      Everything landed. Two full masters differing only in air shelf, both at -14.0 LUFS
      / -0.9 dBTP / LRA 7.5-7.6 / 44100 Hz 24-bit, and 14 split tracks verifying ALL OK.
      Duration is 3779.317551 s against a source of 3779.317550 — the load-bearing number,
      because it means every one of PLN's playhead calls stays valid and the split lands
      exactly where he put the cuts rather than approximately there.
      
      Q1: the missing air is CYMBALS. Drums hold 0.367% of their own energy above 8 kHz
      against 0.084% for the runner-up, a 4.4x concentration where evenly-spread energy
      would have meant codec hiss. So opening the shelf is licensed by measurement, and
      how far is not — hence an A/B rather than a number I liked. The framing that
      matters: both candidates are still BELOW typical brightness for electronic masters
      (8-16 kHz at 0.40% and 0.63% against a 1-3% norm), so the choice is "quite dark vs
      slightly less dark", not "safe vs aggressive". The A/B is also confound-free:
      identical loudness, identical true peak, range within 0.1 LU, only the tilt differs.
      
      Q2: the floor is worse here than OPAL. Kick active time averages 44.8% against the
      59% project_floor_problem measured for OPAL-26 from real orbit stems, worst at
      #2 22.8%, #3 33.4%, #8 36.9%. A stage mic in a room explains it on its own, so this
      is a lead rather than a verdict — but it is the first time this gig's floor has been
      measurable at all.
      
      Q3, PLN's own question, and the answer is a clean negative: the global pass was not
      worth running. Correlations 0.943-0.977 against the per-section stems with level
      deltas of at most 0.21 dB. The two separations are the same stems. The prediction
      held exactly — htdemucs' 7.8 s receptive field means a global pass cannot give the
      model more musical context, so only normalisation differs, and it barely moved. It
      cost ~2.5 h of CPU competing with the master render to establish a quarter of a dB.
      Sections only next time; the question was worth asking once, not twice.
      
      Side finding: bass is the least stably separated of the four sources (0.9432,
      lowest), consistent with the same run putting PunkAChien's bass into `other` — bass
      stem at -81.0 dBFS while `other` was the loudest stem in the set.
      
      The log also records three self-inflicted errors that share one shape: checks whose
      failure mode was indistinguishable from success. A dropped rtk grep plus a
      block-buffered logfile that made a working job look dead, so two renders raced on
      the same paths for forty minutes. A speed claim built on counting directories that
      appear when writers open. And a scripted edit that silently no-op'd while printing
      success unconditionally. Plus the 192 kHz master, which was not a new bug at all —
      topic_postprod_mastering has carried that trap since tidal-ears hit it, and I wrote
      a new mastering chain without re-reading the omnibus that exists to prevent it.
      PLN (Algolia) authored
  4. 01 Sep, 2026 13 commits
    • feat(armada): compare_masters — put an A/B in numbers, so the ear judges taste and not arithmetic · 0a123d3b
      PLN has to choose between two masters that differ only in a high-shelf setting.
      The ear is the authority on which one is better and this does not try to replace
      it — but "which do you prefer" is a much easier question when you also know WHAT
      differs and by how much, because a shelf change does not stay in the band it
      names. It moves true peak, it changes how hard the limiter works, and it shifts
      where the loudness normaliser lands.
      
      So: integrated loudness, true peak, loudness range, L/R correlation and the
      band-energy distribution for each file, then a delta table against the first,
      because two absolute tables side by side is exactly how you miss a change that
      matters. Band deltas are reported as RATIOS rather than differences in
      percentage points: a band going from 0.12% to 0.24% is a doubling, and "+0.12
      points" hides that entirely.
      
      Measured on the RENDERED files, never predicted from the filter settings. A shelf
      set to +3 dB does not necessarily put +3 dB in the master, since the limiter
      downstream has opinions — feedback_verify_own_renders, the machine's job is the
      objective facts so PLN's ears are spent on taste.
      
      The smoke test proved the point immediately. Comparing the trial streaming master
      against the harder-pushed club one, the loud version shows 0.92x the 60-120 Hz
      energy and 1.08x the 300-1000 Hz: that is the limiter eating bass transients and
      relatively lifting the mids, at a cost of 1.4 LU of range for 3.5 dB of loudness.
      A measurable signature of what loudness costs, rather than a vague sense that the
      loud one sounds smaller.
      
      Wired into the finisher, so the summary PLN reads over coffee has the two masters
      with their differences quantified next to the files themselves.
      PLN (Algolia) authored
    • chore(spec): defer the club variant instead of letting it fail loudly at 3am · 19957cf5
      The spec still listed a -9 LUFS club variant that the master unit no longer
      renders, so render_release would have printed MISSING into the summary PLN reads
      over coffee — an error that looks like a broken pipeline but is actually a
      decision nobody wrote down. So it is written down now.
      
      `club` moves to `variants_deferred`, carrying the reasoning rather than just the
      path. It is not a failure on two counts. First, the axes were conflated: PLN's
      "one live club mix" means the CONTINUOUS set as opposed to the album split, which
      is a FORM, while this entry is a LOUDNESS target of our own convention. No
      CosmicFest track is cut, so the continuous mix simply IS the master file, and both
      of his forms are already covered by the streaming master plus tracks_v1_streaming/.
      Second, it would have missed anyway: reaching -9 from a -25.6 LUFS source with
      4.7 dB of linear headroom tripped the LRA floor at -10.5 on the trial, meaning the
      target costs more dynamics than it is worth on this particular tape. Moving the key
      back into `variants` renders it, so the decision is reversible in one edit.
      
      Added `abVariants` alongside, recording what the freed CPU bought and why: the stem
      EDA proved the 8-16 kHz deficit is cymbals rather than codec noise — drums hold
      0.367% of their own energy above 8 kHz against 0.084% for the runner-up, a 4.4x
      concentration — which licenses opening the air shelf but says nothing about how
      far. Two full masters at +5 and +8, identical otherwise, judged in place rather
      than on a clip, per the lesson OPAL's reverb A/B taught.
      
      The finisher now iterates only the variant that exists, so its split, verify and
      on-disk census all line up with what was actually rendered.
      PLN (Algolia) authored
    • fix(finish): gate the global EDA on stem LENGTH, not existence — and report the A/B · 565db062
      The finisher decided whether to run the local-vs-global comparison by testing
      whether the global drums stem exists. A soundfile writer creates its file the
      instant it opens, so that test is true from the first second of an hour-long pass.
      This is the same trap that had me reading "4 of 14 sections done" off a directory
      count earlier tonight, and it would have been worse here than a wrong progress
      number: the comparison seeks into the global stems by ABSOLUTE TIME, so a
      truncated global file does not fail, it silently mis-aligns every later track and
      returns plausible correlations for the wrong audio.
      
      Now it probes the duration and requires ~3700 of 3779 seconds before attempting
      the global half, falling back to the section-only EDA with the shortfall printed.
      In practice the unit is ordered After= the demucs producer so the stems should be
      complete anyway — but "should be" is exactly the assumption that a demucs failure
      mid-global would have quietly violated, and the finisher runs unattended while PLN
      sleeps.
      
      Also made the summary report the air-shelf A/B explicitly, since that pair is the
      thing he has to judge: both masters listed with size, sample rate, channels and
      duration, and a loud MISSING line if either did not render. The EDA settled that
      the 8-16 kHz deficit is cymbals rather than codec noise (drums hold 0.367% of
      their own energy up there against 0.084% for the runner-up, a 4.4x concentration),
      which licenses opening the shelf but says nothing about how far — so the summary
      puts the two candidates side by side rather than announcing a winner.
      PLN (Algolia) authored
    • feat(cosmicfest): the EDA answered — the missing air is CYMBALS, so render an A/B · 6411504d
      The stem EDA ran on all 14 sections and settled the one parameter that was an
      admitted guess. The air shelf sat at +5 dB against a measured 8-16 kHz share of
      0.12%, held back because a 320 kbps MP3 puts codec hiss in exactly that band and a
      broadband boost cannot tell hiss from hats.
      
      Per stem, mean share of each stem's OWN energy above 8 kHz: drums 0.367%, vocals
      0.084%, other 0.033%, bass 0.0%. A 4.4x concentration in drums. Evenly-spread
      energy would have meant noise and a shut shelf; concentration means cymbals the
      room ate, and opening up is justified by measurement.
      
      HOW FAR is not justified by measurement — that is taste. So --air-gain exists and
      the master unit now renders TWO full streaming masters, identical but for the
      shelf (+5 and +8), because OPAL already taught us that an effect judged on a clip
      is not the same effect judged in place. PLN compares two records tomorrow instead
      of ratifying a number I happened to like.
      
      The -9 LUFS "club" target is deliberately dropped, and the reasoning is a
      correction to my own earlier conflation of two axes. PLN's "one live club mix"
      means the CONTINUOUS set as opposed to the album split — a FORM. Our club variant
      is a LOUDNESS. Since no CosmicFest track is cut, the continuous mix simply IS the
      master file, so both of his forms were already covered and the -9 render was
      optional polish that would have missed anyway (the LRA floor tripped at -10.5 on
      the trial). That CPU now buys the A/B, which answers a question he actually has.
      
      Q2, unasked for and worth having: kick presence per track, 40-120 Hz active time
      against each track's own p95. Mean 43.4%, against the 59% project_floor_problem
      measured for OPAL-26 from real orbit stems. Worst real offenders are #2 There's
      Something About Drums at 22.8% and #3 Am i Doing it Right at 33.4%. A stage mic in
      a room is a plausible cause on its own, so this is a lead rather than a verdict —
      but it is the first time this gig's floor has been measurable at all.
      
      Two things the lens caught that no label would have, both vindicating
      feedback_label_and_lens applied to the separator's own names. #14's drums stem is
      -84.9 dBFS RMS, essentially empty, which confirms PLN's note that the outro is
      "pure dubsiren noise and fun" — so its kick percentage was measuring a noise floor
      and reading as a real number. kick_activity is now gated on presence and returns
      None with a reason instead, because a percentage of nothing is not a percentage
      (presence_is_a_precondition). And #10 PunkAChien's bass stem reads -81.0 dBFS
      while its `other` stem is the loudest in the whole table at -26.8: demucs put the
      bass INTO other. Anyone reading stem filenames as roles would have concluded
      PunkAChien has no bass.
      
      Also fixed the JSON writer, which died on numpy's bool_ after printing every
      useful number, and a headline ratio that divided by an epsilon guard when one stem
      measured a legitimate 0.000% and announced a 367000x concentration. It now reports
      against the runner-up.
      PLN (Algolia) authored
    • fix(master): the first full render was 192 kHz — a bug this project already fixed once · 9ab93ffb
      The CosmicFest streaming master completed in spec on every number I had thought
      to check: I = -14.0 LUFS, LRA 7.5 from a source 10.6, true peak -1.0, duration
      matching the source to the microsecond. It was also 192 kHz and 1.32 GB, from a
      44.1 kHz source. 4.3x the size it should be, at a rate no platform wants.
      
      ffmpeg's `loudnorm` oversamples 4x for true-peak detection and leaves the graph at
      192 kHz without resampling back. Any chain ending in loudnorm inherits that. So
      every chain here now ends `aresample=<source rate>:resampler=soxr`, with the rate
      PROBED from the source rather than assumed, and `-ar` set on the output too.
      Validated on the 90 s slice: 44100 Hz / 2 ch, duration exact, loudness unchanged.
      
      The part worth writing down is that this is not a new bug. topic_postprod_mastering
      has carried a section titled "Every master was 192 kHz (fixed c652d16)" since the
      tidal-ears chain hit it, describing this precise failure and its cause. I wrote a
      brand-new mastering tool without re-reading the omnibus that exists to prevent
      exactly this, and reproduced it. The memory now records the recurrence and two
      structural fixes rather than a note-to-self:
      
      Put the resample in the chain BUILDER, so no future chain can omit it. And make
      the in-spec gate assert FORMAT, not just loudness — sample rate and channel count
      now sit in the pass/fail beside I/TP/LRA, and the output line prints them. A gate
      checking only loudness is what certified a 4.3x-oversized master as ✓, and I found
      it by ffprobing for an unrelated reason. That is luck, and luck is not a check.
      
      Also fixed an indentation error I introduced while threading the rate through with
      a scripted replacement whose search string matched inside deeper indentation than
      intended — the kind of mistake that is instant to catch with a parse check and
      invisible without one.
      
      Cleaned up: the 192 kHz master deleted, the master unit restarted on the fixed
      chain, and cosmic-finish re-queued behind it so the split cannot run against a
      file that no longer exists.
      PLN (Algolia) authored
    • fix(log 038): retract the nohup diagnosis — two false negatives, and two renders racing · fdd1418b
      I wrote in 038 that `nohup … &` inside a tool call does not survive, on the
      evidence of an empty logfile and no matching process. Both were artefacts, the
      diagnosis was wrong, and acting on it cost considerably more than the dead job
      I thought I was recovering from.
      
      The log was empty because Python block-buffers stdout to a file. No TTY means a
      ~8 KB buffer, and PYTHONUNBUFFERED=1 was set in the systemd unit but not on the
      nohup line, so forty minutes of genuine work produced a zero-byte log. The process
      appeared missing because rtk silently dropped the grep; `rtk proxy ps` showed it
      immediately, reparented to PID 1553 — the user systemd manager — and well into its
      render. RTK.md says to fall back to `rtk proxy` whenever output looks suspiciously
      empty, and I skipped that on the one check the entire conclusion rested on.
      
      So I started the same 63-minute render a second time. For roughly forty minutes two
      processes wrote the same output paths, racing on Cosmic26_v1_streaming.flac and
      master_report.json. Nothing warned me; it surfaced only because two ffmpeg
      processes turned up at different pipeline stages, which is luck rather than a
      designed tell. Killed the unsupervised duplicate by PID, removed its 102 MB partial
      FLAC so cosmic-finish could not split a truncated file, and let the journaled unit
      carry on. The unit has since reported attempt 1 cleanly: pre-gain +11.6 dB giving
      I=-15.94, TP=-1.00, LRA 8.30 down from 10.60 — dynamics substantially intact.
      
      Two rules earned the hard way, both recorded in
      reference_durable_background_jobs alongside the retraction. An empty logfile is
      not evidence of no progress: use `python -u` or PYTHONUNBUFFERED on anything whose
      log you intend to read. And before starting a replacement for a job you believe is
      gone, PROVE it is gone — via `rtk proxy ps` or the unit's own cgroup.procs —
      because a duplicate writing the same paths is a corruption risk, not merely wasted
      CPU. feedback_check_the_instrument_first and feedback_absence_needs_proof both
      applied here and I honoured neither.
      
      What the episode does not undermine is the house rule, which stands on its own
      evidence from the same night: systemd --user units outlive the session. Every
      harness background waiter and monitor I had armed was killed at a between-turn
      teardown, while all three cosmic-* units kept running untouched. That is the reason
      to use them. The nohup story was never the reason.
      PLN (Algolia) authored
    • docs(board): a resume point for an unattended run — and two progress metrics that lie · f87c8ead
      PLN went to bed with the CosmicFest pipeline in flight. This is the block a cold
      session needs to pick it up without reconstructing anything, written because the
      run outlives the session that started it.
      
      The teardown test came back the way the design hoped: every harness background
      waiter in that session was killed between turns, while all three systemd --user
      units kept running untouched. So the resume instructions lead with systemctl and
      journalctl rather than with anything session-local, and the expected end state is
      listed in full — masters, both split directories, eda_stems.json,
      master_report.json, FINISH_SUMMARY.txt, and the section plus global stems — because
      cosmic-finish.service is ordered After= both producers and gets there on its own.
      
      Two progress metrics are recorded as WRONG, since I used both and both fooled me.
      Counting directories under stems_demucs/sections/ counts sections STARTED, not
      finished: the directory appears the instant chunked_separate opens its four
      writers. And libsndfile buffers, so a stem file sits at 44 bytes — a bare WAV
      header — through minutes of genuine work. I read "4 of 14 done" off the first and
      put a speed claim in commit 37eb401f on the strength of it. Judge by mtime and
      flushed bytes, or simply by whether the unit is still activating.
      
      The board now also names the first question to ask of the EDA, because it decides
      a parameter that is currently an admitted guess. The air shelf sits at +5 dB
      against a measured 8-16 kHz share of 0.12%, which on its face justifies far more;
      it is held back because a 320 kbps MP3 puts codec hiss in exactly that band. The
      EDA prints the verdict: HF concentrated in drums by 2x or more means cymbals and
      the shelf can open, HF spread evenly across all four stems means noise and it
      stays shut. Re-rendering on that basis costs nothing, since PLN has not yet heard
      any of it — which is the one moment such a change is free.
      
      Blockers are ordered by what actually gates a shipment. First is the canonical www
      gig page, because build_release_plan refuses without it and every upload adapter
      reads that plan; the derivable half is already drafted, so PLN owes four
      frontmatter fields and the set's movements. Then the tone, which no measurement
      settles. Then three smaller calls, and the still-unreviewed merge 2f947c2c.
      
      Recorded as ready-but-deliberately-undone: OPAL's SoundCloud is 7 of 15 tracks
      short and the re-run is idempotent, but PLN was sharing OPAL links that evening
      and mutating the account under him was the wrong risk to take unattended. And a
      standing warning against uploading CosmicFest's split before he approves the tone —
      SoundCloud is idempotent by permalink, so a v2 re-render is silently skipped
      rather than replaced, which is the take-five-drops defect waiting to happen
      fourteen more times.
      PLN (Algolia) authored
    • docs+feat(cosmicfest): log 038, the stem EDA, and a finisher that does not need my session · b40c7fb5
      Log 038 — "A record from one microphone" — is the night CosmicFest went from
      "educated guess game", PLN's phrase, to a pipeline. It carries the measurements
      that reshaped the job and a correction to my own previous commit.
      
      eda_stems.py is the mastering EDA a stemless gig could not otherwise have, and it
      exists to answer three specific questions rather than to print statistics.
      
      Is the 8-16 kHz deficit cymbals or codec noise? The master measured 0.12% of
      total energy up there and the air shelf was deliberately held to +5 dB instead of
      the +12 the deficit suggests, because a 320 kbps MP3 puts hiss in that band
      alongside hats. A broadband boost cannot tell the two apart; a per-stem view can.
      If the HF lives in `drums` it is cymbals and the shelf can open, and if it is
      spread evenly across all four it is noise and the shelf stays shut.
      
      Where is the kick? project_floor_problem measured the kick audible in only 59% of
      OPAL-26, from real orbit stems. This asks the same question of a gig that has
      none, via 40-120 Hz activity over time, thresholded relative to each track's own
      p95 rather than an absolute dBFS — a fixed threshold on a quiet room-coloured
      source measures the recording level, not the kick. Reported per track so a floor
      that drops out gets named instead of averaged away.
      
      And how much does local-versus-global separation actually differ? It compares
      each section's stem against the same span cut out of the global stem:
      correlation says whether the model made the same decision, the RMS delta says
      whether normalisation changed the level. That comparison is the reason the
      chunked writer had to be length-exact.
      
      Presence is gated before any stem is compared to another, and demucs' own source
      names are treated as hypotheses — feedback_label_and_lens applies to a
      separator's labels as much as to a sample's filename.
      
      finish_cosmic.sh + cosmic-finish.service exist because of a structural mistake I
      nearly shipped. The master and demucs runs are systemd units so they outlive the
      session, but every step AFTER them — split, verify, EDA — was sitting in my
      session. PLN said "your autonomous now going to bed tell me results tomo", so a
      teardown would have left him with rendered masters and nothing else. The unit is
      ordered After= both producers, so systemd does the waiting instead of a polling
      loop, and it leaves FINISH_SUMMARY.txt on disk that stands on its own. Wants=
      rather than Requires=, deliberately: a club-target miss or a demucs failure must
      not cancel the split of a perfectly good streaming master.
      
      The log also corrects commit 37eb401f, which claimed the chunked demucs path was
      about twice as fast. It is not, and the claim came from a broken metric: I counted
      directories under stems_demucs/sections/, but a directory appears the moment
      chunked_separate opens its writers, so the count measures sections STARTED, not
      finished. libsndfile buffers on top of that, so three of four in-flight sections
      sat at 44 bytes — a bare WAV header — after nine minutes of genuine work.
      Measured properly by mtime and flushed bytes, a worker runs at about 4.3x
      realtime under four-way contention plus the master render. Chunking fixed the
      OOM kill; it did not demonstrably make anything faster.
      PLN (Algolia) authored
    • feat(armada): draft a gig's tracks.json — derive what is derivable, refuse to guess the rest · 6ae2b820
      build_release_plan.py refuses to emit anything without the canonical www gig
      page, with the right message: "Gig metadata is never invented here — create it
      there first." Every upload adapter reads that plan, so one missing file blocks
      SoundCloud, YouTube and Bandcamp at once. CosmicFest 2026 has no page, which
      makes that gate the single thing standing between a finished master and PLN
      hearing it on the platform he asked to hear it on.
      
      The gate is correct and this does not weaken it. It splits the file along the
      line the gate cares about instead of blurring it:
      
        derivable   name, file, bpm, start/end/duration, style, samples
        PLN only    the gig's title, venue, stage, description, and each track's
                    `section` (the movements of the set)
      
      Everything in the second group is emitted as null with a `_needs_pln` list, never
      as a plausible guess. The output is a DRAFT at whatever path you pass; promoting
      it into the www repo stays PLN's call. So tomorrow is four fields and a review
      rather than an afternoon of reconstruction.
      
      Derivations reuse what already parses the corpus rather than re-reading it:
      `style` comes from the score's own directory, which is how OPAL's published
      styles line up with the tree, and `samples` comes from catalog_view.json's
      `score_sounds`. A score absent from that view is REPORTED, not handed an empty
      list — an empty sample list is invisible once it is on the shelf, which is the
      whole lesson of feedback_parsers_over_copy. Seven of CosmicFest's fourteen are
      currently absent because the view is stale; re-running build_catalog_view fills
      them.
      
      The derived timecodes then validated themselves against PLN's own ear notes,
      which is the reassuring part. Piment Bresilien lands at 0:35:46 against his
      playhead comment "starting at 35:45.6 i hear proper only piment start sound",
      and LiveCode Parade at 0:54:58 against the 54:59 note where crossmatch had
      guessed gimme_acid and the declared 130 BPM broke the tie. 14 tracks, 63.0 min,
      30 sample packs, 89-170 BPM.
      PLN (Algolia) authored
    • fix(demucs): the OOM killer ended it after 2 of 14 sections — one bounded path for both modes · 37eb401f
      The first run died at 8.2 GB peak, killed by the kernel, having finished two
      sections in 32 minutes of wall clock and nearly five hours of CPU. The arithmetic
      was there to be done beforehand and I did not do it: a 532-second section is
      188 MB of input, 750 MB of output across four sources, and apply_model's overlap
      accumulator wants that again — call it 2 GB, times four workers, on a machine
      already holding 18 GB of someone else's Gradle builds.
      
      The fix was already written, in the other half of the same file. `global_pass`
      chunked its work specifically because a 63-minute tensor would need ~12 GB, and
      sections needed exactly the same treatment for exactly the same reason. So the
      two modes now share one applier and differ only in which normalisation statistics
      they are handed — which is the entire local/global distinction anyway, since
      htdemucs' receptive field is 7.8 s and neither mode can give the model more
      musical context than that. Sections pass their own stats; the global pass passes
      the whole recording's.
      
      Chunking turned out to be faster as well as smaller: four sections landed in the
      time the unchunked version took to finish two, presumably from allocating a few
      hundred MB repeatedly instead of gigabytes once. Memory peak is now 5.45 GB.
      
      The unit also grew MemoryMax=10G / MemoryHigh=8G. A bounded algorithm should not
      need it, but a cap turns any future regression into a failed unit rather than the
      kernel choosing a victim on PLN's desktop while he sleeps.
      
      Two details kept from the global implementation because they are load-bearing:
      the chunk seam is joined with an equal-power ramp, since it sums two estimates of
      the same audio and a linear fade would dip 3 dB right at the join; and the output
      is length-exact, verified at 3969000 frames on all four stems for a 90 s input,
      zero drift. That second one matters beyond tidiness — the EDA pass seeks into the
      global stems by absolute time to compare them against each section, so any
      accumulated drift would silently misalign every later track.
      
      Also discarded the two sections the old code had produced. They were valid
      separations, but they were made by a different code path, and a local-versus-
      global comparison built on a mixed dataset would have a confound in it that no
      amount of care downstream could remove.
      PLN (Algolia) authored
    • feat(cosmicfest): two tools for a gig with no stems — separation as a lens, and a bus-only master · b8059f81
      Both exist because CosmicFest is one stage-mic MP3, and the house pipeline
      assumes twelve Ardour orbit-stems.
      
      demucs_sections.py — per-track stems plus a global pass, PLN's ask: "to see how
      both are interpreted locally and globally". One measured fact decides what that
      comparison can show. htdemucs' receptive field is 7.8 s (model.segment = 39/5)
      and it always splits internally, so a 63-minute file gives the model no more
      musical context than a 4-minute one. The local/global difference is therefore
      NOT context — it is input normalisation, since demucs normalises by the mean/std
      of whatever you hand it. On this gig that is a real lever rather than a nitpick:
      the set sits at -25.2 LUFS with 11.1 LU of range, so quiet sections get far less
      gain under whole-set statistics than under their own.
      
      So global mode computes the normalisation over the entire recording and then
      applies the model in bounded chunks with those statistics. That isolates the one
      variable that can actually differ, in about 1 GB instead of the ~12 GB a single
      63-minute tensor needs (four sources x 1.33 GB of output, plus apply_model's
      overlap accumulator, against 12 GB free). Chunks overlap and are crossfaded with
      an equal-power ramp — the seam is two estimates of the same audio summed, so a
      linear fade would dip 3 dB in the middle of it.
      
      Written against demucs' Python API rather than its CLI, because the CLI cannot
      save here at all: torchaudio 2.10 routes writes through TorchCodec, which is not
      installed, so `python -m demucs` separates for 87 seconds and then dies on
      ImportError. Going through apply_model and soundfile also avoids installing
      anything into PLN's venv to work around it.
      
      Validated before launching a six-hour job: 30 s slice, four stems written, all
      populated, and sum-minus-original sits 27.4 dB below the signal. Vocals came
      back at -50.6 dBFS RMS on an instrumental passage — correctly near-empty, which
      is the kind of agreement that makes later disagreements worth trusting. Runs as
      a systemd --user unit per reference_durable_background_jobs, niced with idle IO
      so it can never be why the rig stutters.
      
      master_stemless.py — MASTERING.md's chain with the per-stem half removed and one
      stage added, because the measurement demanded it. Full-file analysis first:
      L/R correlation 0.833 (genuinely stereo, so bass-mono is safe), sub-30 Hz is
      0.07% of total energy (the HPF is free headroom), and then the finding that
      reshapes the job — 300-1000 Hz carries 34.75% while 4-8 kHz has 1.75% and
      8-16 kHz has 0.12%. That is a microphone in a room, not a desk feed: the PA's
      treble never reaches the mic, the room absorbs what does, mid-bass piles up. The
      tape is dark and boxy, not merely quiet, so the dominant move is a broadband
      tilt rather than gain.
      
      Restraint where the numbers cannot justify enthusiasm: the air shelf is +5, not
      the +12 the deficit suggests, because a 320 k MP3 puts codec residue in that band
      alongside cymbals and a big boost lifts hiss into the master. The demucs stems
      are the lens that can tell those apart; until they answer, the shelf stays modest.
      
      Bass-mono runs in mid/side, not by splitting and re-summing bands: a lowpass(120)
      summed with a highpass(120) leaves a phase notch at the crossover, whereas
      high-passing the side channel alone never filters the mid path.
      
      Two failures worth keeping. First, the club target with the limiter placed before
      any gain: the signal reaching it still peaked at -9.5 dBTP, so a -1 dBFS ceiling
      never engaged, and loudnorm's linear=true — correctly refusing to breach TP —
      capped the master at -11.2 LUFS against a -9 target. The gain has to come first
      so the limiter has something to catch. Second, that fix alone still undershot,
      because every dB the limiter absorbs is a dB the final loudnorm cannot add; the
      shortfall IS the missing gain, so feeding it back converges. Bounded at three
      attempts and guarded by an LRA floor, since a loudness target reached by
      flattening the music is not reached — presence_is_a_precondition, applied to
      dynamics.
      
      On the 90 s trial the streaming target lands at -14.0 LUFS, peak -1.0, with
      1.8 LU of range lost. The club target stops at -10.5 with the floor tripped, and
      reports the miss instead of clipping its way to a number. Whether -9 is
      reachable on the full set, whose LRA is 11.1 rather than the slice's 4.6, is a
      question the full render answers.
      PLN (Algolia) authored
    • feat(cosmicfest): the first stemless gig reaches segments — 14 tracks, 63.0 min · d97dda3f
      R-0 and C-0, the two moves the release board put ahead of everything else.
      
      R-0. The CosmicFest master existed in exactly one place: ~/Downloads. It is now
      in Prod/Cosmic26_master/ and mirrored to the freebox, sha256 7a197589…d671b
      identical on both, and the Downloads original was removed only after both copies
      were verified — a move, not a hopeful mv. feedback_freebox_ssot names the freebox
      as the SSOT for audio, and an irreplaceable 63-minute gig in a scratch directory
      was the cheapest large risk on the board. The spec records the naming trap that
      nearly cost an hour: Prod/cosmicfest/ and Prod/cosmicfestv0.live1.* are the 2025
      edition, files dated 2025-06-27/28, and nothing in them belongs to this release.
      
      C-0. POSTPROD.md's rule is that a new gig is a copy of judge_specs/<gig>.json and
      never a copy of a script — so the question was whether a gig with NO stems can
      use the pipeline at all. Answered by reading the code, not by trying it and
      hoping: apply_boundaries and render_release touch no stem key whatsoever, only
      segments/master/variants/releaseRoot/releaseTag. The single tool that genuinely
      needs stems is build_judge_set, which derives nominal segments from per-orbit
      activity. That is the one hole, and setlist_to_segments.py fills it.
      
      The converter turns judge_specs/<gig>_setlist_ear.json into the two inputs
      apply_boundaries wants, and its real job is keeping two grades of truth apart.
      The setlist silently mixes them: some starts are playhead calls PLN made while
      listening, others were reconstructed from his notes on boundaries he SKIPPED.
      Only the former go into `verified`, so apply_boundaries prints ear or nominal per
      row and asserts every playhead call reached the output. An inferred edge can
      therefore never be mistaken later for one he actually heard.
      
      That distinction immediately corrected the board. It had named #10 and #12 as the
      soft edges, taken from the recovered tracklist's unresolved list. The setlist ear
      file supersedes that: ten starts (#2-#11) are playhead calls, #1 is the origin,
      and the inferred ones are #12 LiveCode Parade, #13 Vague de CRIME and #14
      Outro: La Dub Sirène. #10 PunkAChien was ear-called all along — "perfect cut at
      48:27.4 into PunkAChien!". Three tracks to audition, not two, and a different
      three. The board also claimed 11 decided boundaries; there are 10.
      
      Titles resolve authored -> OPAL-release -> catalog -> NEEDS_PLN, and never by
      title-casing a slug. The authoritative source for a shared track is what actually
      SHIPPED: OPAL-26's ear-signed segments_v4.json joined to that gig's tracks.json
      on performance order, since neither file alone holds both the score path and the
      released title. Four tracks were in no shipped release and no catalog entry, so
      the first run emitted them as NEEDS_PLN rather than inventing anything; PLN then
      gave all four, and they live in judge_specs/cosmicfest-2026_titles.json with
      value+source+locator+date per feedback_metadata_provenance. The same file carries
      a `score` alias, because the gig slug "mafia" is mafia_sans_serif.tidal — and
      that alias then cross-checked itself, yielding a declared 160 BPM that matches
      OPAL's Mafia exactly. Outro: La Dub Sirène has no .tidal at all, being a played
      outro rather than a composed track, which is also why the catalog can never hold
      its title: the catalog is keyed by score path.
      
      `album` is deliberately absent from the spec. CosmicFest 2026 has no page under
      content/lives/2026/, so there is no canonical set title to copy, and inventing
      one would be exactly what feedback_metadata_vs_mastering forbids.
      
      The source measures better than "raw stage mic with FOH effects" suggests:
      I = -25.2 LUFS, true peak -5.1 dBFS, LRA 11.1 LU over the full 62:59. Not
      clipped, not crushed, just quiet — reaching -14 LUFS needs about +11 dB and
      therefore limiting, but that is a gain problem rather than a repair job. Recorded
      in the spec's sourceProvenance so the next pass starts from the number.
      
      apply_boundaries output: 14 tracks, 63.0 min kept of a 63.0 min master, zero
      gaps, all assertions green. variants still point at mastered files that do not
      exist; the mix is the next step and render_release will say MISSING until it runs.
      PLN (Algolia) authored
    • docs(board): the two-set release board — and CosmicFest is a better tape than we thought · 39ca4def
      PLN reframed the north star: not one release but four deliverables — {OPAL,
      COSMIC} x {continuous club mix, per-track album split}. The board only tracked
      OPAL, so this amendment measures both sets against the same scorecard instead
      of recalling their state.
      
      The four questions he asked, answered from the filesystem rather than memory:
      boundaries are DONE for BOTH sets (OPAL ear-signed 2026-08-16, CosmicFest
      2026-08-29 — 11 playhead calls plus 8 skip notes). The mix is done and
      ear-reviewed for OPAL only. CosmicFest has had zero mastering: its ear pass
      settled WHERE the cuts are, never HOW it sounds — there is no mix to review.
      
      Two findings worth more than the status table.
      
      The CosmicFest master has exactly ONE copy, and it is in ~/Downloads. 151 MB,
      not on the freebox, not in Prod/. feedback_freebox_ssot says the freebox is the
      SSOT for audio; an irreplaceable gig recording in a scratch directory is the
      highest-risk, cheapest-to-fix item on the whole board. Filed as R-0, ahead of
      all processing. Adjacent trap recorded too: Prod/cosmicfest/ and
      Prod/cosmicfestv0.live1.* are the 2025 edition (files dated 2025-06-27/28) —
      nothing there belongs to this release, and the names invite exactly that error.
      
      And the tape is far better than "raw stage mic with FOH effects" implies.
      Measured with ebur128 over the full 62:59: I = -25.2 LUFS, true peak -5.1 dBFS,
      LRA 11.1 LU. The peak says it was recorded conservatively and is NOT clipped;
      the 11.1 LU range says the desk did NOT crush the dynamics (a mangled feed lands
      under 6). It is simply quiet — reaching -14 LUFS needs ~+11 dB and therefore
      limiting, but that is a gain problem, not a repair job. PLN's "80/20 likely" is
      reachable; what is genuinely impossible is a premix, because one stereo file has
      no stems and none of OPAL's per-orbit surgery has an analogue.
      
      The keystone follows from POSTPROD.md's own rule — "a new gig is a copy of
      judge_specs/<gig>.json, never a copy of a script". That file does not exist for
      CosmicFest, and BOTH of its deliverables sit downstream of it. One wrinkle to
      settle while writing it: OPAL's spec assumes stems (stemsDir, stemmap, keeps,
      clipsDir) and CosmicFest has none, so the stemless path must be verified rather
      than assumed.
      
      Six decisions are logged as PLN's, not the tooling's: the still-unreviewed merge
      2f947c2c; whether the A2 REVOLUTION fix is worth invalidating the A1.7 signoff it
      would force a re-render of; whether "Outro Dub Siren" is an album track or
      mix-only; the two soft CosmicFest edges (#10, #12) inferred from skip notes
      rather than called on the playhead; the rights ledger re-measured at 103 banks
      (43 third_party, 31 unknown, 19 dirt_samples, 10 cleared), which gates the split
      but not the mix; and artwork, still unchosen for both.
      
      Also traced the collab thread: piment_bresilien is in BOTH sets — OPAL track 07,
      already on SoundCloud, and CosmicFest #7 at 2145.6-2525.4 on a playhead call.
      Its score carries one uncommitted edit, line 33, |+ note 12 becoming |- note 12.
      PLN (Algolia) authored
  5. 29 Aug, 2026 1 commit
    • merge: the LCXL3 surface becomes the rig's default state · 2f947c2c
      Brings the v3 control surface work onto master, as PLN called it: "this is the
      new tooling/state". The arc is one idea repeated — the driver owns the truths
      the hardware refuses to keep.
      
      - it plays: four defects between the driver and a live SuperCollider, all of
        them identity bugs (a port's NAME is not the port)
      - it speaks a language: role hue x value lightness, the DJF's diverging
        blue -> breathing-green-zero -> red, glowing at the track's tempo
      - it holds state the v3 board dropped: offset-64 relative values, row-E
        latches, paint reasserted every two seconds
      - it says what each control DOES, in PLN's own words parsed from his .tidal
        ("Le Delay rose!!!", "HANDS IN THE AIR"), across all 34 display targets
      - it follows the track: the editor already published it, the driver only had
        to read — remap, relabel, repaint and reset the lap clock on a change, and
        re-read on save so mapping a control lights the board within a second
      - the tempo can be a knob, with landmark detents replacing the notch the v3
        encoders lack, and a bar countdown for the mute-drop transition
      - and the rig around it: Ardour hears the surface again, RIG UP owns the
        ordering SuperDirt needs, midiviz replaced the transcript with a picture
      
      Also aboard: a display-semantics probe that asks the device instead of us
      guessing, realtime.tidal for composing without the crossfade, and the corpus
      marked for quantise at all 14 tempo-knob sites.
      
      Backward compatibility is the standing constraint and it holds: the corpus
      speaks v2 forever, everything v3 lives in the driver, and a classic Launch
      Control XL still works with nothing running.
      PLN (Algolia) authored