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
  • tempo-lens.py
Find file
BlameHistoryPermalink
  • PLN (Algolia)'s avatar
    feat(tempo-lens): measure the tempo the room FEELS, not the one setcps declares · 156444d6
    #12 (order the set into a BPM arc) has been blocked on three tempo questions, and
    they were framed as things PLN had to decide. Two of them are measurable.
    
    FIRST, ONE DISSOLVED ON INSPECTION. you_my_sunshine was recorded as "144 measured vs
    166 written", a 22 BPM gap big enough to INVERT the planned rising finish
    Mafia(160)->Sunshine(166). The file has two setcps lines and the 144 one is COMMENTED
    OUT. The active tempo is 166, the backlog was right all along, and the "mismatch" was
    a grep reading a comment. That is the parsers-over-copy lesson again: a parser miss
    must never masquerade as a data conflict. (My own first re-extraction then mangled
    every number by using `tr -d '/60'`, which deletes the digits 6 and 0 — 160 became 1.
    Two parsing bugs in one sitting, on four-line shell one-liners.)
    
    SECOND, THE REAL QUESTION NEEDS AUDIO. gimme_acid declares 80 while the backlog calls
    it 160, and `setcps` cannot settle it: setcps(80/60/4) declares four beats per cycle
    at 80, but whether that is HEARD as 80 or 160 depends on what the patterns put inside
    the cycle. Half-time notation is standard in dnb. This decides whether the track sits
    beside the 160 BPM peak or is the second-slowest thing in the set.
    
    So: spectral-flux novelty curve -> autocorrelation -> the fastest pulse that explains
    the signal, reported together with its half/double family, because tempo from audio
    is only ever determined up to a factor of two and pretending otherwise is the error.
    
    BUILT THE KATANA FIRST, and it needed four passes — every one caught on synthetic
    click tracks of KNOWN tempo before the tool informed any decision:
     1. Inter-onset-interval MODE. Failed on the first real track: gimme_acid's d1 is a
        continuous 303 line whose flux peaks every ~80 ms of internal movement, so the
        modal gap gave "750 events/min". Counting gaps between events cannot find a beat
        when the events are not beats. -> autocorrelation, which asks about PERIODICITY
        and is unbothered by extra onsets inside each period.
     2. Octave errors: a clean 124 click read 61.9, a clean 160 read 80.0 — exactly half,
        correlation 0.85+, confidently wrong. Cause: at 100 Hz frames the true lag 48.39
        must round to 48, which misaligns every later beat, while exactly 2x landed on a
        whole frame. -> 200 Hz frames + 15 ms Gaussian smoothing, so a fractional period
        still matches itself.
     3. A uniform +1.8% bias (~2 BPM at techno tempo — enough to swap a 124 and a 127 in
        a set order). Suspected the unnormalised overlap in np.correlate, fixed that too,
        and the bias did not budge. The actual cause: the shortest-lag-within-12% rule,
        written to choose between octaves, was also sliding down the LEFT FLANK of the
        correct peak — comparing points on one hill as if they were different hills.
        -> candidates restricted to local maxima, plus parabolic interpolation.
    Result: 13/13 synthetic cases within 0.03%, including jittered, noisy, offbeat-8ths,
    and the half-time case (80 BPM with 16th kicks correctly reads 160 felt).
    
    A tempo number that is 2% wrong looks perfectly reasonable, which is exactly why this
    had to be calibrated against known truth instead of eyeballed against real audio.
    PLN (Algolia) authored Jul 28, 2026
    156444d6
tempo-lens.py 10.2 KB
EditWeb IDE
×

Replace tempo-lens.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.