feat(tide-table): tempo_timeline — check whether a planned track was played
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.
Showing
armada/tide-table/tempo_timeline.py
0 → 100644
Please
register
or
sign in
to comment