Commit 470cc882 by PLN (Algolia)

feat(tide-table): recover a tracklist from one room mic, when every existing lens is blind

PLN downloaded the CosmicFest capture and called it "an interesting test". It is:
a 63-minute handheld stage-mic MP3, and all three boundary lenses the toolbox
owns need something it does not have. Orbit-activity needs stems, there are
none. The gig-log `track` events are the real tracklist, but the 1 Hz recorder
never ran that night — newest session is 2026-08-20. Gap detection assumes a
set stops between tracks, and a livecoded set does not.

What is left is audio, plus one large advantage: we know what was prepped, so
this is matching over ~23 candidates rather than open discovery. PLN warns "the
set was slightly altered", so candidates are treated as candidates — presence
is strong evidence, absence is weak.

Two lenses, because neither is sufficient. Tempo finds seams but cannot name
them: five candidates sit at 120 BPM and three at 124, and tempo from audio is
only ever determined up to a factor of two. Sample signature names a segment,
and PLN pointed at exactly the right prior art — sample_tfidf.py already
computes which sounds IDENTIFY a track. take5 and love_parade are df=1;
jungle_breaks is in 80 tracks and worthless. 15 of 23 candidates carry a sound
rare enough (df<=6) to fingerprint; 19 such banks, 533k reference hashes.

Constellation fingerprints rather than correlation, because the reference is a
dry one-shot off disk and the query is that sound through a PA, a room, a
limiter, "ppl messed with it", and a 320 kbps encode. Peak constellations encode
only which time-frequency points stick up locally, which survives all of that.

My first attempt at extracting banks was wrong and the output said so: 9 tokens
across 23 tracks, every count 1, and a label reading "shares every bank" that
actually meant "nothing parsed". The regex looked for `s "name"` while the
ParVagues idiom applies a bare string — `$ "meth_bass" # n "..."`. The fix was
not a better regex, it was using the parser that already existed.

The docstring carries the limits because they are easy to launder: a detection
means the SOUND occurred, not that the TRACK played, since banks get reused
live. Recall is asymmetric — a track whose signature never sounded is invisible.
And vote counts are not comparable between banks, because a 582-file bank offers
far more chances to match than a 4-file one.

Also adds `lcxl3.py sniff`: capture raw SysEx in full. The custom-mode format is
undocumented, and Components writes modes to the device over SysEx — capturing
those bytes is the difference between a layout authored in lcxl_grid.py and one
merely stored in a web app.
parent c628415e
......@@ -37,6 +37,7 @@ v1.0, not recalled:
from __future__ import annotations
import argparse
import pathlib
import sys
import time
......@@ -450,6 +451,53 @@ def cmd_bitmap(args) -> int:
return 0
def cmd_sniff(args) -> int:
"""Capture RAW SysEx, in full, to a file.
For reverse-engineering the CUSTOM MODE format, which the programmer's
reference does not document at all. Novation Components writes a custom mode
to the device over SysEx; if we capture those bytes we can generate modes from
lcxl_grid.py instead of hand-editing them in a web app — the difference
between a layout that is authored and one that is merely stored somewhere.
Listen on the MIDI port (Components talks to the device's main interface),
then save a mode in Components and watch the byte counts.
"""
s = L.Surface(args.port, need_input=True)
out = pathlib.Path(args.out)
print(f"sniffing {s.in_name!r} for {args.secs}s -> {out}")
print("Now do the thing in Components (save / load a Custom Mode).")
print("Every SysEx is logged with its length; the file gets the full bytes.\n")
end = time.time() + args.secs
n = 0
with out.open("w") as fh:
fh.write(f"# lcxl3 sniff: port={s.in_name} secs={args.secs}\n")
try:
while time.time() < end:
for msg in s.inp.iter_pending():
if msg.type == "sysex":
d = list(msg.data)
n += 1
hexs = " ".join(f"{b:02X}" for b in d)
fh.write(f"SYSEX len={len(d)} F0 {hexs} F7\n")
fh.flush()
cmd = d[5] if len(d) > 5 else None
print(f" #{n:<3} len={len(d):<6} cmd=0x{cmd:02X}" if cmd is not None
else f" #{n:<3} len={len(d)}")
else:
fh.write(f"MSG {msg}\n")
time.sleep(0.002)
except KeyboardInterrupt:
print("\ninterrupted")
s.close()
print(f"\n{n} SysEx message(s) captured -> {out}")
if n == 0:
print("Nothing arrived. Components may be talking to the DAW port instead —")
print("re-run with --port daw. Note also that only ONE app can hold the")
print("device: if Components has it open exclusively, we see nothing.")
return 0
def cmd_keystone(args) -> int:
"""THE question: is paint really DAW-mode-only, as the guide implies?
......@@ -576,6 +624,12 @@ def main(argv: list[str] | None = None) -> int:
q.add_argument("--temporary", action="store_true")
q.set_defaults(func=cmd_bitmap)
q = sub.add_parser("sniff", help="capture raw SysEx (to reverse the custom-mode format)")
q.add_argument("--port", choices=("midi", "daw"), default="midi")
q.add_argument("--secs", type=float, default=60.0)
q.add_argument("--out", default="armada/tide-table/lcxl3_sniff.txt")
q.set_defaults(func=cmd_sniff)
q = sub.add_parser("keystone", help="does paint need DAW mode? the 169-file question")
q.add_argument("--stay", action="store_true", help="leave DAW mode on at the end")
q.set_defaults(func=cmd_keystone)
......
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