jam-buddy / tools /spike_detect.py
salgadev's picture
Sync from GitHub 6feaf31d
b2e4883 verified
Raw
History Blame Contribute Delete
3.16 kB
#!/usr/bin/env python3
"""Feasibility spike: can we detect the tempo curve + onsets on a real DI track?
Runs librosa onset detection + frame-wise tempo on the DI WAV and compares
against the notated tempo from the MIDI time map (if given).
"""
import argparse
import sys
import librosa
import numpy as np
def main():
ap = argparse.ArgumentParser()
ap.add_argument("wav", help="DI track WAV path")
ap.add_argument("--sr", type=int, default=22050, help="resample target")
args = ap.parse_args()
print(f"Loading {args.wav} ...")
y, sr = librosa.load(args.wav, sr=args.sr, mono=True)
dur = len(y) / sr
print(f" duration {dur:.1f}s, sr {sr}, samples {len(y)}")
# --- 1. Onset strength envelope ---
print("\n[onset] detecting onset strength ...")
onset_env = librosa.onset.onset_strength(y=y, sr=sr)
onsets = librosa.onset.onset_detect(onset_envelope=onset_env, sr=sr,
units="time", backtrack=True)
print(f" {len(onsets)} onsets")
# onset rate (hits/sec) -> coarse BPM indicator
if len(onsets) > 1:
inter = np.diff(onsets)
inter = inter[inter > 0.08] # ignore sub-80ms jitter
if len(inter):
rate = 60.0 / np.median(inter)
print(f" median inter-onset {np.median(inter):.3f}s -> ~{rate:.1f} BPM")
# --- 2. Frame-wise tempo curve ---
print("\n[tempo] frame-wise tempo curve ...")
tempo_out = librosa.feature.tempo(
onset_envelope=onset_env, sr=sr, aggregate=None,
)
# some librosa builds return a scalar when aggregate=None; wrap if needed
if np.isscalar(tempo_out) or (np.ndim(tempo_out) == 0):
tempo_curve = np.full(onset_env.shape[-1], float(tempo_out))
else:
tempo_curve = np.asarray(tempo_out).reshape(-1)
hop = librosa.get_hop_length() if hasattr(librosa, "get_hop_length") else 512
frame_dur = hop / sr
n = len(tempo_curve)
# Robust per-window median in 4s windows
win = max(1, int(4.0 / frame_dur))
print(f" {n} frames, frame {frame_dur:.2f}s")
t = 0.0
i = 0
print("\n time(s) ~BPM(median 4s window)")
seen = []
while i < n:
w = tempo_curve[i:i + win]
med = float(np.median(w[np.isfinite(w)])) if np.any(np.isfinite(w)) else float("nan")
t = i * frame_dur
seen.append((t, med))
print(f" {t:7.1f} {med:6.1f}")
i += win
# distinct tempo regimes
uniq = sorted(set(round(m) for _, m in seen if np.isfinite(m)))
print("\n distinct ~tempo values:", uniq)
# --- 3. Beat tracking (librosa default) ---
print("\n[beat] beat_track ...")
tempo_est, beats = librosa.beat.beat_track(onset_envelope=onset_env, sr=sr)
if np.ndim(tempo_est):
tempo_est = float(np.median(np.asarray(tempo_est)))
beat_times = librosa.frames_to_time(beats, sr=sr)
print(f" est BPM {float(tempo_est):.1f}, {len(beat_times)} beats")
if len(beat_times) > 1:
ii = np.diff(beat_times); ii = ii[ii > 0]
print(f" median beat interval {np.median(ii):.3f}s -> ~{60/np.median(ii):.1f} BPM")
if __name__ == "__main__":
main()