midmid3 / midmid /tempo_map.py
markury's picture
Initial commit
d171350
"""Tempo map derivation from beat tracker output."""
import numpy as np
from midmid.beat_tracker import BeatData
def derive_tempo_map(
beat_data: BeatData, change_threshold: float = 0.08,
) -> list[tuple[float, float]]:
"""Derive a tempo map from beat data.
Returns list of (time_seconds, bpm) tuples, sorted by time.
"""
beats = beat_data.beats
if len(beats) < 2:
return [(0.0, 120.0)]
intervals = np.diff(beats)
bpms = 60.0 / intervals
median_bpm = np.median(bpms)
valid = (bpms > median_bpm * 0.6) & (bpms < median_bpm * 1.6)
if not np.any(valid):
return [(0.0, float(median_bpm))]
valid_bpms = bpms[valid]
if np.std(valid_bpms) / np.mean(valid_bpms) < change_threshold:
avg_bpm = float(np.mean(valid_bpms))
return [(0.0, _round_bpm(avg_bpm))]
tempo_map = []
current_bpm = float(bpms[0]) if valid[0] else float(median_bpm)
tempo_map.append((0.0, _round_bpm(current_bpm)))
window = 4
for i in range(window, len(bpms) - window + 1, window):
chunk = bpms[i : i + window]
chunk_valid = chunk[(chunk > median_bpm * 0.6) & (chunk < median_bpm * 1.6)]
if len(chunk_valid) == 0:
continue
local_bpm = float(np.mean(chunk_valid))
if abs(local_bpm - current_bpm) / current_bpm > change_threshold:
current_bpm = local_bpm
tempo_map.append((float(beats[i]), _round_bpm(current_bpm)))
return tempo_map
def get_median_bpm(beat_data: BeatData) -> float:
if len(beat_data.beats) < 2:
return 120.0
intervals = np.diff(beat_data.beats)
bpms = 60.0 / intervals
return float(_round_bpm(np.median(bpms)))
def estimate_time_signature(beat_data: BeatData) -> int:
if len(beat_data.downbeats) < 2:
return 4
beats = beat_data.beats
downbeats = beat_data.downbeats
counts = []
for i in range(len(downbeats) - 1):
start, end = downbeats[i], downbeats[i + 1]
n = np.sum((beats >= start) & (beats < end))
if 2 <= n <= 7:
counts.append(n)
if not counts:
return 4
values, freq = np.unique(counts, return_counts=True)
return int(values[np.argmax(freq)])
def _round_bpm(bpm: float) -> float:
return round(float(bpm), 2)