Commit 22a9fc46 by PLN (Algolia)

feat(tide-table): the first pass measured loudness, not similarity — three fixes and a reframe

The first scan reported raw hash collisions and I then analysed the noise. Every
bank occupied 375 of 378 time bins. Three things were wrong, each traceable to
prior art rather than to tuning:

Offset consistency was missing entirely, and it is half the algorithm. Wang's
2003 Shazam paper is explicit: real matches agree on a time offset, so the
(query_t, ref_t) scatterplot shows a diagonal and the offset histogram spikes.
Counting collisions without that step counts coincidences.

Fixed offset is wrong anyway for this material. Sonnleitner, Arzt & Widmer
(ISMIR 2016, DJ mix monitoring) allow the offset line a free slope, because a DJ
plays material at a different rate — and a livecoder changing setcps does the
same, across 80 to 170 BPM in one set.

Raw counts measure loudness. Measured, not assumed: 8 of 18 banks had their
global peak in the final minute, at 1.3-2.5x density. The finale is loud and
broadband, so it makes more spectral peaks, so it matches everything better.
Scores are now normalised by each window's own query-hash count and by each
bank's reference size.

But the real problem was the reference unit, and PLN named the fix: "comparing
to prior recs, of each track of the opal songs for example (almost the same
lineup in the end!)". A 0.4s dry one-shot cannot survive a PA and a 320 kbps
encode — too few hashes, below the detection floor by construction. Meanwhile
Prod/Opal26_master/tracks holds FIFTEEN ear-verified per-track renders, 154 to
524 seconds each, same performer, same sample library, same patches.

That changes the task from sample detection to VERSION identification, where the
literature is unambiguous: when audio is related but not identical you stop
matching exact time-frequency points and match sequences of features instead
(Serra et al. on covers, McFee & Ellis on structure). So `crossmatch` embeds
15s windows as MFCC mean+std, chroma and spectral contrast, matches by cosine
against every reference window, and median-filters over 45s because a track
lasts minutes and a one-window flicker is noise by definition.

Standardising before the cosine is not cosmetic: raw MFCC coefficients differ in
scale by orders of magnitude, so without it the first two — level and spectral
tilt — decide everything, and level is exactly what a room mic gets wrong.

The docstring says what it cannot do. The three cafe_* tracks are the same
instruments at nearly the same tempo and no timbral profile will separate them,
so the output reports a runner-up margin and flags thin ones: a win by 0.01 is
not a win.

Also included, from the custom-mode thread: the generated Components entry table
(40 of 48 controls need changing, all of row A already matches), and the sniff
that caught Components driving the device with documented feature controls —
9F 0B 7F then CC 30 = 6 — plus an undocumented cmd 0x05 whose read/write forms
match the audiocontrol project's reverse-engineered protocol.
parent 470cc882
# ParVagues v2 map for a Launch Control XL 3 Custom Mode
Generated from `tools/lcxl_grid.py` — the one authored table. Do not hand-edit;
regenerate. Enter these CC numbers in Novation Components, per control,
left to right. Channel 1 for everything (SuperCollider's MIDIFunc.cc binds
with no channel filter, so only the number matters).
| physical row | col | factory CC | **set CC to** | changed? |
|---|---|---|---|---|
| Encoder row 1 (top) | 1 | 13 | **13** | |
| Encoder row 1 (top) | 2 | 14 | **14** | |
| Encoder row 1 (top) | 3 | 15 | **15** | |
| Encoder row 1 (top) | 4 | 16 | **16** | |
| Encoder row 1 (top) | 5 | 17 | **17** | |
| Encoder row 1 (top) | 6 | 18 | **18** | |
| Encoder row 1 (top) | 7 | 19 | **19** | |
| Encoder row 1 (top) | 8 | 20 | **20** | |
| Encoder row 2 (middle) | 1 | 21 | **29** | **yes** |
| Encoder row 2 (middle) | 2 | 22 | **30** | **yes** |
| Encoder row 2 (middle) | 3 | 23 | **31** | **yes** |
| Encoder row 2 (middle) | 4 | 24 | **32** | **yes** |
| Encoder row 2 (middle) | 5 | 25 | **33** | **yes** |
| Encoder row 2 (middle) | 6 | 26 | **34** | **yes** |
| Encoder row 2 (middle) | 7 | 27 | **35** | **yes** |
| Encoder row 2 (middle) | 8 | 28 | **36** | **yes** |
| Encoder row 3 (bottom) | 1 | 29 | **49** | **yes** |
| Encoder row 3 (bottom) | 2 | 30 | **50** | **yes** |
| Encoder row 3 (bottom) | 3 | 31 | **51** | **yes** |
| Encoder row 3 (bottom) | 4 | 32 | **52** | **yes** |
| Encoder row 3 (bottom) | 5 | 33 | **53** | **yes** |
| Encoder row 3 (bottom) | 6 | 34 | **54** | **yes** |
| Encoder row 3 (bottom) | 7 | 35 | **55** | **yes** |
| Encoder row 3 (bottom) | 8 | 36 | **56** | **yes** |
| Faders | 1 | 5 | **77** | **yes** |
| Faders | 2 | 6 | **78** | **yes** |
| Faders | 3 | 7 | **79** | **yes** |
| Faders | 4 | 8 | **80** | **yes** |
| Faders | 5 | 9 | **81** | **yes** |
| Faders | 6 | 10 | **82** | **yes** |
| Faders | 7 | 11 | **83** | **yes** |
| Faders | 8 | 12 | **84** | **yes** |
| Button row E (upper) | 1 | 37 | **41** | **yes** |
| Button row E (upper) | 2 | 38 | **42** | **yes** |
| Button row E (upper) | 3 | 39 | **43** | **yes** |
| Button row E (upper) | 4 | 40 | **44** | **yes** |
| Button row E (upper) | 5 | 41 | **57** | **yes** |
| Button row E (upper) | 6 | 42 | **58** | **yes** |
| Button row E (upper) | 7 | 43 | **59** | **yes** |
| Button row E (upper) | 8 | 44 | **60** | **yes** |
| Button row F (lower) | 1 | 45 | **73** | **yes** |
| Button row F (lower) | 2 | 46 | **74** | **yes** |
| Button row F (lower) | 3 | 47 | **75** | **yes** |
| Button row F (lower) | 4 | 48 | **76** | **yes** |
| Button row F (lower) | 5 | 49 | **89** | **yes** |
| Button row F (lower) | 6 | 50 | **90** | **yes** |
| Button row F (lower) | 7 | 51 | **91** | **yes** |
| Button row F (lower) | 8 | 52 | **92** | **yes** |
40 of 48 controls need changing; 8 already match.
## Why bother, when the driver also works
Two different jobs. A custom mode plays the corpus with NOTHING running —
no process to die mid-set — but cannot paint: colour and the OLED exist only
in DAW mode (tested). The driver gets paint but is one more moving part.
Firmware 1.1 makes them complementary rather than exclusive: *"The Mode
button can now be held to function as a momentary button, allowing for faster
Custom Mode switching."* So the custom mode is the fallback you reach for when
the instrumented path fails, and it costs one Components session to have.
# lcxl3 custom-mode sniff
[DAW] note_on channel=15 note=11 velocity=127 time=0
[DAW] control_change channel=6 control=30 value=6 time=0
[DAW] note_on channel=15 note=11 velocity=0 time=0
[MIDI] SYSEX len=10 cmd=0x05 00 20 29 02 15 05 00 15 00 06
[MIDI] SYSEX len=10 cmd=0x05 00 20 29 02 15 05 00 15 03 06
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