Skip to content

  • Projects
  • Groups
  • Snippets
  • Help
  • This project
    • Loading...
  • Sign in / Register
T
Tidal
  • Overview
    • Overview
    • Details
    • Activity
    • Cycle Analytics
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Charts
  • Issues 0
    • Issues 0
    • List
    • Board
    • Labels
    • Milestones
  • Merge Requests 0
    • Merge Requests 0
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
    • Charts
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Charts
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • PLN
  • Tidal
  • Repository

Switch branch/tag
  • Tidal
  • tools
  • lcxl-init.py
Find file
BlameHistoryPermalink
  • PLN (Algolia)'s avatar
    feat(rig): measure the chain, manage the server, seed the surface — three tools… · 3f81e14d
    feat(rig): measure the chain, manage the server, seed the surface — three tools that end the silent-orbit era
    
    Tonight cost an evening to "d4/d5/d9-d12 make no sound", and the reason it cost
    an evening is that every layer was inspected by READING and none by MEASURING.
    PLN's correction — "this is a great tidal-ears case, observe the audio streams"
    — was right, and the measurement took six seconds.
    
    ## tools/probe-chain.py — where does the signal stop?
    
    Taps the live chain at three points at once and reports peak+rms dBFS per orbit:
    
        SuperCollider:out_N/N+1   the orbit as SuperDirt renders it
        ardour:Tidal NN/out       after Ardour's fader/mute/plugins
        ardour:Master/out         the mix
    
    Taps are ADDITIVE PipeWire links into `pw-record --target 0` capture streams, so
    nothing existing is unlinked and the audio path is untouched — safe mid-set. The
    `--target 0` is the trick that makes it possible to tap a mid-chain node instead
    of only a device.
    
    Orbit->port mapping is DERIVED, not hardcoded: `~dirt.start(57120, [0,2,4,...])`
    means orbit k writes bus 2k, and PipeWire exposes bus b as out_(b+1)/out_(b+2).
    
    First run settled in one shot what four sessions of hypothesising could not:
    
        d1  -28.6 rms   d2  -42.5   d3  -37.4   d7  -21.5   d8  -29.8   <- audible
        d4 d5 d6 d9 d10 d11 d12 = -inf, absolute digital silence
    
    Exact match with what PLN could hear. **Ardour fully exonerated** — faders,
    mutes, routing, all of it. The signal never existed. Every hypothesis that had
    pointed downstream was wrong, and one reading killed them all.
    
    It reports peak AND rms deliberately: a lone peak can be one stray sample or a
    click, and judging an orbit "working" on peak alone is how you mistake a
    transient for audio.
    
    ## parvagues-sc.service — SuperCollider becomes visible
    
    The reason the real error was unreadable all evening: sclang's post window was a
    bare /dev/pts/3. Not readable by tooling, gone when the terminal closes, gone on
    a crash. The process was equally fragile — owned by a terminal, and killable by
    a stray probe (which is exactly what happened: a probe script of mine attached to
    the DEFAULT server, i.e. PLN's live one, and its `0.exit` sent `/quit`. `s` in
    sclang is the default server; changing the SuperDirt OSC port isolates nothing).
    
    As a systemd --user unit we get restart-by-command, survival across terminal
    close and sandbox teardown, one place for The Bridge to show status — and the
    post window in the JOURNAL, where it can be PARSED into readiness state instead
    of squinted at. Within a minute of it landing:
    
        like_sugar (22)   the_revolution (36)   diams_dj (16)
    
    — the "missing" banks were registered correctly all along, plus 106 real
    `File reading failed` warnings on `zz._`-prefixed files that had never once been
    visible. That is the #45 lead, handed over by a log we could finally read.
    
    `Restart=no` on purpose: a failing boot script under Restart=always would spam
    the MIDI graph and hide the failure. Audio gear should fail loudly, never flap.
    `QT_QPA_PLATFORM=offscreen` is load-bearing, not cosmetic — a Qt-linked sclang
    dies with the display stack, which is how an overnight suspend used to kill the
    music (d8346672).
    
    ## tools/lcxl-init.py — fix the mute-bomb at its actual root
    
    Verified against tidal-1.9.5 source rather than guessed: `"^51"` is `cF_ "51"` is
    `_cX_ getF "51"`, and `_cX_` returns **`silence`** on a missing key. `#` cannot
    emit without a right-hand value, so any stream touching an unmoved control is
    silent — and `dN` being `xfade N` means you hear the PREVIOUS pattern's 4-cycle
    tail dying, which reads as a filter closing. That illusion is what cost the
    evening.
    
    BootTidal.hs's two mitigations are both weaker than this one. `orDef` reaches
    only the ~13 g* helpers — never the ~1860 inline `^NN` refs across 343 track
    files, nor arithmetic sites like `midiOn ("^34" - "^18")` where
    touched-minus-untouched is itself empty. The `setF` seed block is correct in
    principle (same `sStateMV` the OSC `/ctrl` handler writes — traced in Stream.hs)
    but depends on boot order and on that ghci having actually run it.
    
    lcxl-init instead emits real CC messages into the identical path a physical knob
    takes: CC -> SC's `MIDIFunc.cc` -> `/ctrl` -> Tidal's map. By construction
    indistinguishable from PLN sweeping the whole surface by hand — the one thing
    already known to work. It cannot diverge from reality because it IS the reality
    path, and it fixes helpers, inline refs and arithmetic sites at once, whatever
    state ghci is in. PLN's framing: "most things that are templated lack an init
    value" — so give them one, from the hardware side.
    
    Encodes the corpus convention `range <neutral> <extreme>`, so 0 is neutral for
    everything except the three DJ filters, whose neutral is the centre detent
    (64/127 -> lpf 20000 / hpf 20 = true bypass).
    
    D-row faders (77-84) are deliberately NOT seeded: they are MIDI-learned to Ardour
    TRACK GAINS, so blasting values would move Ardour's faders while the physical
    faders stay put — a surface/mix desync worse than the problem. `^77` is already
    covered by `orDef 0.769`.
    
    Two bugs found and fixed by verifying rather than assuming:
      * port resolution used `aconnect -i` (READABLE ports) and so returned SC's
        `out0` — a source. Sending to it goes nowhere. It must be `-o`, the writable
        destinations. Resolved BY NAME on every call, never cached: the LCXL has
        already moved 20:0 -> 24:0 across a replug, and a binding resolved once at
        boot then silently invalidated is this rig's most expensive bug class.
      * the first verification attempt tapped the destination port with aseqdump and
        saw 0 messages — but you cannot SUBSCRIBE to a destination. Delivery was
        then proven properly through Midi Through (which echoes in to out):
        controller 49 value 64 and controller 50 value 64 observed arriving. The
        send was always fine; the observation was broken. Report which one you
        proved.
    PLN (Algolia) authored Jul 27, 2026
    3f81e14d
lcxl-init.py 8.78 KB
EditWeb IDE
×

Replace lcxl-init.py

Attach a file by drag & drop or click to upload


Cancel
A new branch will be created in your fork and a new merge request will be started.