89 std::vector<float> weights;
99 std::vector<float> window;
100 std::vector<float> ring;
103 int64_t total_samples{};
104 int64_t frame_count{};
106 std::vector<float> mags;
107 std::vector<float> whitening_peaks;
108 bool whitening{
false};
109 float whitening_floor{0.01f};
110 float whitening_relax{};
112 std::vector<filter_band> bands;
113 std::vector<float> band_weights;
115 std::vector<float> band_frames;
116 int band_frame_head{};
117 int band_frames_filled{};
119 band_mode mode{band_mode::full};
121 void configure(
double sample_rate)
124 fft_size = rate > 50000. ? 4096 : 2048;
125 hop = std::max(1, (
int)std::lround(rate / 200.));
129 window.resize(fft_size);
130 for(
int i = 0; i < fft_size; i++)
131 window[i] = 0.5f - 0.5f * std::cos(2.f *
float(M_PI) * i /
float(fft_size - 1));
133 ring.assign(fft_size, 0.f);
139 mags.assign(fft_size / 2 + 1, 0.f);
140 whitening_peaks.assign(fft_size / 2 + 1, whitening_floor);
142 whitening_relax = std::pow(10.f, -3.f /
float(25.6 * fps));
145 band_frames.assign(3 * bands.size(), 0.f);
147 band_frames_filled = 0;
151 void build_filterbank()
154 const double fmax = std::min(17000., rate * 0.45);
155 const double bin_hz = rate / double(fft_size);
158 std::vector<double> centers;
162 const double f = 27.5 * std::pow(2., i / 24.);
165 const int b = (int)std::lround(f / bin_hz);
166 if(b != last_bin && b >= 1)
168 centers.push_back(f);
172 if(centers.size() < 3)
175 for(std::size_t i = 1; i + 1 < centers.size(); i++)
177 const double lo = centers[i - 1] / bin_hz;
178 const double c = centers[i] / bin_hz;
179 const double hi = centers[i + 1] / bin_hz;
181 band.center_hz = float(centers[i]);
182 band.first_bin = std::max(1, (
int)std::ceil(lo));
183 const int end_bin = std::min(fft_size / 2, (
int)std::floor(hi));
184 if(end_bin < band.first_bin)
186 band.weights.resize(end_bin - band.first_bin + 1);
188 for(
int b = band.first_bin; b <= end_bin; b++)
190 double w = b <= c ? (b - lo) / std::max(1e-9, c - lo)
191 : (hi - b) /
std::max(1e-9, hi - c);
193 band.weights[b - band.first_bin] = float(w);
198 for(
auto& w : band.weights)
200 bands.push_back(std::move(band));
204 void set_band_mode(band_mode m)
207 band_weights.assign(bands.size(), 0.f);
208 auto in = [](
float f,
float lo,
float hi) {
return f >= lo && f <= hi; };
209 for(std::size_t i = 0; i < bands.size(); i++)
211 const float f = bands[i].center_hz;
214 case band_mode::full:
215 band_weights[i] = 1.f;
217 case band_mode::kick:
218 band_weights[i] = in(f, 30.f, 120.f) ? 1.f : 0.f;
220 case band_mode::snare:
221 band_weights[i] = in(f, 150.f, 400.f) ? 1.f : 0.f;
223 case band_mode::transient:
224 band_weights[i] = in(f, 2000.f, 5000.f) ? 1.f : 0.f;
226 case band_mode::kick_snare:
227 band_weights[i] = (in(f, 30.f, 120.f) || in(f, 150.f, 400.f)) ? 1.f : 0.f;
236 template <
typename F>
237 void process(
const float* in,
int n, F&& on_frame)
239 for(
int i = 0; i < n; i++)
241 ring[ring_pos] = in[i];
242 ring_pos = ring_pos + 1 == fft_size ? 0 : ring_pos + 1;
244 if(++hop_fill >= hop)
247 on_frame(compute_frame(), total_samples);
252 double compute_frame()
255 auto* input = fft.input();
257 for(
int i = 0; i < fft_size; i++)
259 input[i] = ring[idx] * window[i];
260 idx = idx + 1 == fft_size ? 0 : idx + 1;
262 auto* out = fft.execute();
264 const int nbins = fft_size / 2 + 1;
265 const float norm = 2.f / float(fft_size);
266 for(
int b = 0; b < nbins; b++)
268 const float re = float(out[b][0]), im = float(out[b][1]);
269 float m = std::sqrt(re * re + im * im) * norm;
272 float& p = whitening_peaks[b];
273 p = std::max({m, whitening_floor, p * whitening_relax});
279 const int nb = (int)bands.size();
282 float* cur = band_frames.data() + band_frame_head * nb;
283 for(
int k = 0; k < nb; k++)
285 const auto& band = bands[k];
287 for(std::size_t j = 0; j < band.weights.size(); j++)
288 acc += band.weights[j] * mags[band.first_bin + j];
290 cur[k] = std::log10(1.f + 20.f * acc);
294 if(band_frames_filled >= 2)
298 const int prev_head = (band_frame_head + 1) % 3;
299 const float* prev = band_frames.data() + prev_head * nb;
300 for(
int k = 0; k < nb; k++)
302 if(band_weights[k] <= 0.f)
306 ref = std::max(ref, prev[k - 1]);
308 ref = std::max(ref, prev[k + 1]);
309 const float d = cur[k] - ref;
311 flux += band_weights[k] * d;
315 band_frame_head = (band_frame_head + 1) % 3;
316 if(band_frames_filled < 3)
317 band_frames_filled++;
331 int min_lag{75}, max_lag{150};
332 static constexpr int window_size = 1024;
333 static constexpr int update_interval = 128;
335 std::vector<double> window;
340 std::vector<double> scratch;
341 std::vector<double> acf;
342 std::vector<double> comb;
343 std::vector<double> comb_raw;
350 bool context_locked{};
351 double context_period{};
352 int context_disagreements{};
355 double confidence{0.};
357 void configure(
double frames_per_second,
double min_bpm,
double max_bpm)
359 fps = frames_per_second;
360 min_bpm = std::clamp(min_bpm, absolute_min_bpm, absolute_max_bpm);
361 max_bpm = std::clamp(max_bpm, min_bpm + 1., absolute_max_bpm);
362 min_lag = std::max(4, (
int)std::lround(60. * fps / max_bpm));
363 max_lag = std::min(window_size / 2, (
int)std::lround(60. * fps / min_bpm));
364 if(max_lag <= min_lag)
365 max_lag = min_lag + 1;
368 window.assign(window_size, 0.);
369 scratch.resize(window_size);
370 acf.resize(window_size);
371 comb.resize(window_size);
372 comb_raw.resize(window_size);
379 context_locked =
false;
380 context_disagreements = 0;
384 void seed(
double period_frames)
386 period_frames = std::clamp<double>(period_frames, min_lag, max_lag);
387 period = period_frames;
388 context_period = period_frames;
389 context_locked =
true;
394 bool push(
double detrended_odf)
396 window[head] = detrended_odf;
397 head = head + 1 == window_size ? 0 : head + 1;
398 if(filled < window_size)
402 if(++since_update < update_interval || filled < window_size)
411 const int n = filled;
413 int idx = (head - n + window_size) % window_size;
414 for(
int i = 0; i < n; i++)
416 scratch[i] = window[idx];
417 idx = idx + 1 == window_size ? 0 : idx + 1;
421 const int max_acf_lag = std::min(n - 1, 4 * max_lag + 4);
422 for(
int lag = 1; lag <= max_acf_lag; lag++)
425 for(
int i = lag; i < n; i++)
426 acc += scratch[i] * scratch[i - lag];
427 acf[lag] = acc / double(n - lag);
431 double best = 0., best_lag = 0.;
432 double best_raw = 0., best_raw_lag = 0.;
433 for(
int lag = min_lag; lag <= max_lag; lag++)
437 for(
int a = 1; a <= 4; a++)
439 const int c = a * lag;
440 if(c + (a - 1) > max_acf_lag)
443 for(
int b = -(a - 1); b <= a - 1; b++)
445 sc += h / double(2 * a - 1);
465 if(best <= 0. || best_raw <= 0.)
477 double d = std::abs(best_raw_lag - context_period);
479 d = std::min({d, std::abs(2. * best_raw_lag - context_period),
480 std::abs(0.5 * best_raw_lag - context_period)});
481 if(d > 0.12 * context_period)
483 if(++context_disagreements >= 3)
486 period = best_raw_lag;
487 best_lag = best_raw_lag;
492 context_disagreements = 0;
497 int bl = (int)best_lag;
498 if(bl > min_lag && bl < max_lag)
500 const double ym = comb[bl - 1], y0 = comb[bl], yp = comb[bl + 1];
501 const double denom = ym - 2 * y0 + yp;
502 if(std::abs(denom) > 1e-12)
503 best_lag = bl + std::clamp(0.5 * (ym - yp) / denom, -0.5, 0.5);
510 const double base = comb_raw[std::clamp((
int)std::lround(best_lag), min_lag, max_lag)];
512 for(
int lag = min_lag; lag <= max_lag; lag++)
514 if(std::abs(lag - best_lag) < 0.15 * best_lag)
516 second = std::max(second, comb_raw[lag]);
518 confidence = base > 0. ? std::clamp(1. - second / base, 0., 1.) : 0.;
522 history[history_n % 3] = best_lag;
528 context_period += 0.2 * (best_lag - context_period);
530 else if(history_n >= 3)
532 const double a = history[0], b = history[1], c = history[2];
533 const double mx = std::max({a, b, c}), mn = std::min({a, b, c});
534 if(mx - mn < 0.075 * mx)
536 context_locked =
true;
537 context_period = (a + b + c) / 3.;
542 double prior(
int lag)
const
547 const double s2 = std::max(1., context_period / 8.);
548 const double d = lag - context_period;
549 return std::exp(-d * d / (2. * s2));
553 const double b = 0.5585 * fps;
554 const double l = lag;
555 return (l / (b * b)) * std::exp(-l * l / (2. * b * b));
1011 halp_meta(name,
"Beat Tracker")
1012 halp_meta(author,
"ossia team")
1013 halp_meta(c_name,
"avnd_beat_tracker")
1014 halp_meta(category,
"Timing/Audio")
1015 halp_meta(description,
1016 "Audio beat tracker / tempo follower: onset detection, tempo "
1017 "estimation and a phase-locked clock with confidence monitoring.")
1018 halp_meta(manual_url,
"https://ossia.io/score-docs/processes/beat-tracker.html")
1019 halp_meta(uuid,
"8c8c1855-b96f-4231-b40a-453468e7f9ec")
1021 using band_mode = btrk::spectral_flux_odf::band_mode;
1022 enum class clock_filter
1040 struct : halp::dynamic_audio_bus<
"In",
double>
1044 "Audio to track: a microphone on a drummer, a click, a DJ feed, a "
1045 "bus from elsewhere in the score. Channels are summed to mono. "
1046 "Ignored when Source is Events.")
1053 "Where beats come from. Audio: listen to the input and detect onsets. "
1054 "Events: ignore the audio entirely and take beats from the Beat inlet "
1055 "(OSC, a footswitch, a clock). Audio + Events: both, whichever is "
1059 std::string_view values[3]{
"Audio",
"Events",
"Audio + Events"};
1077 struct : halp::accurate<halp::val_port<
"Beat", ossia::value>>
1081 "One message per beat, used when Source is Events. Send an int or a "
1082 "float and the value is taken as the beat NUMBER, which also gives "
1083 "the tracker the position in the bar; send anything else (a bang, a "
1084 "string, a list) and it counts as a plain beat, giving tempo and "
1085 "phase only. Timestamps are sample-accurate within the buffer.")
1088 struct : halp::spinbox_i32<
"Events per beat", halp::irange{1, 24, 1}>
1092 "How many messages arrive per beat. 1 for one message per beat, 4 "
1093 "for sixteenth notes in 4/4, 24 for MIDI clock. Only whole beats set "
1094 "the phase; subdivisions only refine the tempo.")
1097 struct : halp::combobox_t<
"Band", band_mode>
1101 "Which part of the spectrum to listen to. Full works on a finished "
1102 "mix; Kick (30-120 Hz) and Snare (150-400 Hz) are for a miked drum "
1103 "and ignore everything else; Transient (2-5 kHz) suits clicks and "
1104 "sticks. Narrowing the band is the single most effective way to stop "
1105 "the tracker hearing things that are not the beat.")
1108 std::string_view values[5]{
"Full",
"Kick",
"Snare",
"Transient",
"Kick+Snare"};
1109 band_mode init{band_mode::full};
1116 struct : halp::toggle<
"Limit BPM range", halp::toggle_setup{.init =
true}>
1120 "Restrict the search to Min/Max BPM. This is the main defence "
1121 "against half- and double-time errors. Turn it off if you would "
1122 "rather the tracker never second-guessed you.")
1124 struct : halp::spinbox_f32<
"Min BPM", halp::range{30., 300., btrk::default_min_bpm}>
1128 "Slowest tempo considered. Set the pair to bracket the music you "
1129 "expect: a narrow range locks faster and is far less likely to "
1130 "settle on half or double the real tempo.")
1132 struct : halp::spinbox_f32<
"Max BPM", halp::range{30., 300., btrk::default_max_bpm}>
1134 halp_meta(description,
"Fastest tempo considered. See Min BPM.")
1139 struct : halp::toggle<
"Transport tempo hint", halp::toggle_setup{.init =
true}>
1143 "Start from the score's own tempo instead of from nothing. A tracker "
1144 "given a starting tempo locks in well under a second where a cold "
1145 "one needs several. Leave this on unless you are deliberately "
1146 "testing cold-start behaviour.")
1149 struct : halp::combobox_t<
"Clock filter", clock_filter>
1153 "The loop that turns detected beats into a smooth clock. 2nd order "
1154 "is the tested default. 3rd order can settle a little flatter on "
1155 "very steady material but has not been measured against it here.")
1158 std::string_view values[2]{
"DLL (2nd order)",
"DLL (3rd order)"};
1159 clock_filter init{clock_filter::dll_2nd_order};
1163 struct : halp::knob_f32<
"Lookahead (ms)", halp::range{0., 250., 0.}>
1167 "Emit each beat this early, so downstream processing and display "
1168 "latency land on time. Around 100 ms is nearly free in accuracy "
1169 "terms. Leave at 0 when driving from Events, which already arrive "
1172 struct : halp::knob_f32<
"Offset (ms)", halp::range{-250., 250., 0.}>
1176 "Fixed timing trim, applied after Lookahead. Positive delays the "
1177 "beat, negative advances it. Use it to compensate a mic distance or "
1178 "a converter's latency.")
1180 struct : halp::knob_f32<
"Gate (dB)", halp::range{-90., 0., -60.}>
1184 "Input level below which the signal counts as silence, at which "
1185 "point the clock holds its last tempo rather than chasing noise. "
1186 "Raise it if room tone or bleed is keeping the tracker awake.")
1188 struct : halp::toggle<
"Whitening", halp::toggle_setup{.init =
false}>
1192 "Continuously equalise the spectrum before onset detection, so quiet "
1193 "and loud passages contribute alike. Helps on a full mix or a room "
1194 "mic with a wide dynamic range; unnecessary on a close-miked drum.")
1196 struct : halp::spinbox_i32<
"Beats per bar", halp::irange{1, 16, 4}>
1200 "Time signature numerator, used to decide which beats are downbeats "
1201 "and to compute Bar phase. It does not affect tempo tracking.")
1205 struct : halp::impulse_button<
"Tap">
1207 halp_meta(description,
"When unlocked, tap in time to set the tempo (taps are averaged; pause for two seconds to start a fresh series). Once locked, a single tap resets the phase so the next beat is now.")
1210 struct : halp::impulse_button<
"Resync">
1212 halp_meta(description,
"Declare that now is beat one. Snaps the downbeat without touching the tempo.")
1213 void update(
BeatTracker& self) { self.on_resync(); }
1215 struct : halp::impulse_button<
"Nudge -">
1217 halp_meta(description,
"Momentarily slow down, to pull the clock back in line with a performer who is ahead. Returns to the tracked tempo on release.")
1218 void update(
BeatTracker& self) { self.on_nudge(-1); }
1220 struct : halp::impulse_button<
"Nudge +">
1222 halp_meta(description,
"Momentarily speed up, to push the clock forward towards a performer who is behind. Returns to the tracked tempo on release.")
1223 void update(
BeatTracker& self) { self.on_nudge(+1); }
1225 struct : halp::impulse_button<
"x2">
1227 halp_meta(description,
"Double the tempo without re-detecting, for when the tracker settled an octave low.")
1228 void update(
BeatTracker& self) { self.on_octave(0.5); }
1230 struct : halp::impulse_button<
"/2">
1232 halp_meta(description,
"Halve the tempo without re-detecting, for when the tracker settled an octave high.")
1233 void update(
BeatTracker& self) { self.on_octave(2.); }
1235 struct : halp::toggle<
"Hold", halp::toggle_setup{.init =
false}>
1239 "Freeze the tempo at its current value while still following the "
1240 "beat's phase. For a passage where you trust the tempo but not what "
1241 "the tracker is hearing - a breakdown, a solo, heavy bleed.")
1243 struct : halp::toggle<
"Follow", halp::toggle_setup{.init =
true}>
1247 "Master switch. Off freezes the clock entirely and stops analysing, "
1248 "leaving the last tempo and phase in place.")
1254 struct : halp::val_port<
"Tempo",
double>
1256 using halp::val_port<
"Tempo",
double>::operator=;
1259 "Detected tempo in BPM, smoothed for display. Cable this to an "
1260 "interval's Tempo inlet to drive the timeline.")
1262 struct : halp::val_port<
"Speed",
double>
1264 using halp::val_port<
"Speed",
double>::operator=;
1267 "Playback rate multiplier: 1 means the score's tempo already "
1268 "matches, above 1 means speed up to catch the performer. This is the "
1269 "port to feed a synchroniser or an interval's Speed inlet; unlike "
1270 "Tempo it corrects phase as well, gradually and within safe limits.")
1272 struct : halp::val_port<
"Phase",
double>
1274 using halp::val_port<
"Phase",
double>::operator=;
1277 "Position within the current beat, 0 at the beat and approaching 1 "
1278 "just before the next. Continuous, so it is safe to map to a "
1279 "parameter directly.")
1281 struct : halp::val_port<
"Bar phase",
double>
1283 using halp::val_port<
"Bar phase",
double>::operator=;
1286 "Position within the current bar, 0 at the downbeat approaching 1. "
1287 "Depends on Beats per bar and on the downbeat being correct - use "
1288 "Resync, or send beat numbers, to place it.")
1290 struct : halp::val_port<
"Beat index",
int>
1292 using halp::val_port<
"Beat index",
int>::operator=;
1295 "Beats counted since tracking started. Increments by one on every "
1296 "beat and never goes backwards.")
1298 struct : halp::val_port<
"Next beat (s)",
double>
1300 using halp::val_port<
"Next beat (s)",
double>::operator=;
1303 "Seconds until the next beat, already advanced by Lookahead. Use "
1304 "this to schedule something ahead of time instead of reacting to "
1307 struct : halp::val_port<
"Confidence",
double>
1309 using halp::val_port<
"Confidence",
double>::operator=;
1312 "How much the tracker trusts its own reading, 0 to 1. Falls during "
1313 "breakdowns, silence and material with no clear pulse. Worth mapping "
1314 "to a fallback so the score can react to the tracker losing the "
1315 "plot instead of following it blindly.")
1317 struct : halp::val_port<
"Locked",
bool>
1319 using halp::val_port<
"Locked",
bool>::operator=;
1322 "True once the clock has settled: either the evidence is strong or "
1323 "the tempo has simply held steady for a few seconds. While locked "
1324 "the tracker corrects gently; while unlocked it hunts.")
1326 struct : halp::val_port<
"Valid",
bool>
1328 using halp::val_port<
"Valid",
bool>::operator=;
1331 "False when there is nothing to track - the input is below the gate, "
1332 "or no events have arrived. The clock keeps running on its last "
1333 "tempo; this tells you not to trust it.")
1336 struct : halp::timed_callback<
"Beat">
1340 "Fires on every beat, timed to the sample. Advanced by Lookahead and "
1341 "trimmed by Offset.")
1343 struct : halp::timed_callback<
"Downbeat">
1347 "Fires on the first beat of each bar, according to Beats per bar.")
1349 struct : halp::timed_callback<
"Onset">
1353 "Fires on every detected attack, not only on beats. Useful for "
1354 "triggering off the raw playing rather than the inferred grid.")
1366 halp_meta(name,
"Beat Tracker")
1367 halp_meta(layout, halp::layouts::tabs)
1368 halp_meta(background, halp::colors::background_mid)
1372 halp_meta(name,
"Source")
1373 halp_meta(layout, halp::layouts::hbox)
1377 halp_meta(name,
"Input")
1378 halp_meta(layout, halp::layouts::vbox)
1379 halp::item<&ins::source> source;
1380 halp::item<&ins::events_per_beat> events_per_beat;
1383 halp::spacing sp1{.width = 12, .height = 1};
1387 halp_meta(name,
"Listening")
1388 halp_meta(layout, halp::layouts::vbox)
1389 halp::item<&ins::band> band;
1390 halp::item<&ins::whitening> whitening;
1391 halp::item<&ins::gate> gate;
1397 halp_meta(name,
"Tempo")
1398 halp_meta(layout, halp::layouts::hbox)
1402 halp_meta(name,
"Range")
1403 halp_meta(layout, halp::layouts::vbox)
1404 halp::item<&ins::limit_range> limit_range;
1405 halp::item<&ins::min_bpm> min_bpm;
1406 halp::item<&ins::max_bpm> max_bpm;
1409 halp::spacing sp1{.width = 12, .height = 1};
1413 halp_meta(name,
"Musical")
1414 halp_meta(layout, halp::layouts::vbox)
1415 halp::item<&ins::hint> hint;
1416 halp::item<&ins::beats_per_bar> beats_per_bar;
1422 halp_meta(name,
"Clock")
1423 halp_meta(layout, halp::layouts::hbox)
1427 halp_meta(name,
"Filter")
1428 halp_meta(layout, halp::layouts::vbox)
1429 halp::item<&ins::filter> filter;
1432 halp::spacing sp1{.width = 12, .height = 1};
1436 halp_meta(name,
"Timing")
1437 halp_meta(layout, halp::layouts::vbox)
1438 halp::item<&ins::lookahead> lookahead;
1439 halp::item<&ins::offset> offset;
1445 halp_meta(name,
"Performance")
1446 halp_meta(layout, halp::layouts::hbox)
1450 halp_meta(name,
"Sync")
1451 halp_meta(layout, halp::layouts::vbox)
1452 halp::item<&ins::tap> tap;
1453 halp::item<&ins::resync> resync;
1456 halp::spacing sp1{.width = 12, .height = 1};
1460 halp_meta(name,
"Nudge")
1461 halp_meta(layout, halp::layouts::vbox)
1462 halp::item<&ins::nudge_minus> nudge_minus;
1463 halp::item<&ins::nudge_plus> nudge_plus;
1466 halp::spacing sp2{.width = 12, .height = 1};
1470 halp_meta(name,
"Octave")
1471 halp_meta(layout, halp::layouts::vbox)
1472 halp::item<&ins::dbl> dbl;
1473 halp::item<&ins::hlv> hlv;
1476 halp::spacing sp3{.width = 12, .height = 1};
1480 halp_meta(name,
"Engage")
1481 halp_meta(layout, halp::layouts::vbox)
1482 halp::item<&ins::hold> hold;
1483 halp::item<&ins::follow> follow;
1496 ossia::moving_average_filter<double, 16> m_detrend;
1497 ossia::one_pole_filter<double> m_tempo_display;
1499 std::vector<float> m_mono;
1501 int64_t m_samples{};
1505 double m_odf_hist[3]{};
1507 int64_t m_last_onset_frame{-1000};
1510 double m_silence_time{1e9};
1511 bool m_gate_open{
false};
1514 btrk::event_estimator m_ev;
1515 double m_last_event_time{-1e9};
1516 int m_cfg_source{-1};
1519 double m_elevated_until{-1.};
1520 int m_locked_count{};
1531 static constexpr int tempo_hist_n = 64;
1532 double m_tempo_hist[tempo_hist_n]{};
1533 int m_tempo_hist_pos{}, m_tempo_hist_len{};
1534 double m_tempo_accum_t{};
1535 double m_tempo_rel_sd{1.};
1539 double m_prev_transport_tempo{-1.};
1540 double m_last_emitted_tempo{-1.};
1543 int64_t m_downbeat_origin{};
1544 int64_t m_last_emitted_beat{-1};
1547 double m_speed_trim{};
1548 static constexpr double sync_adjustment_cap = 0.05;
1549 static constexpr double sync_delta_cap = 0.02;
1550 static constexpr double sync_error_deadband = 0.01;
1551 static constexpr double sync_p_gain = 0.7;
1554 float m_cfg_min_bpm{-1.f}, m_cfg_max_bpm{-1.f};
1555 bool m_cfg_limit{
true};
1560 void prepare(halp::setup s)
1564 if(s.rate == m_rate && m_rate > 0)
1574 m_odf.whitening = inputs.whitening;
1575 m_odf.configure(m_rate);
1577 m_ct.beta = m_est.period > 0 ? m_est.period : 60. / 120. * m_odf.fps;
1579 m_ct.set_period(m_ct.beta);
1584 m_tempo_display.reset();
1585 m_mono.resize(4 * setup.frames + 16);
1589 m_last_onset_frame = -1000;
1590 m_silence_time = 1e9;
1591 m_gate_open =
false;
1592 m_elevated_until = -1.;
1594 m_tempo_hist_len = 0;
1595 m_tempo_hist_pos = 0;
1596 m_tempo_accum_t = 0.;
1597 m_tempo_rel_sd = 1.;
1598 m_is_locked =
false;
1600 m_prev_transport_tempo = -1.;
1602 m_downbeat_origin = 0;
1603 m_last_emitted_beat = -1;
1606 void apply_bpm_range()
1608 const double lo = inputs.limit_range ? inputs.min_bpm.value : 40.;
1609 const double hi = inputs.limit_range ? inputs.max_bpm.value : 240.;
1610 m_est.configure(m_odf.fps, std::min(lo, hi), std::max(lo, hi));
1611 m_cfg_min_bpm = inputs.min_bpm.value;
1612 m_cfg_max_bpm = inputs.max_bpm.value;
1613 m_cfg_limit = inputs.limit_range;
1616 void reconfigure_if_needed()
1618 if((
int)inputs.band.value != m_cfg_band)
1620 m_odf.set_band_mode(inputs.band.value);
1621 m_cfg_band = (int)inputs.band.value;
1623 if(inputs.min_bpm.value != m_cfg_min_bpm || inputs.max_bpm.value != m_cfg_max_bpm
1624 ||
bool(inputs.limit_range) != m_cfg_limit)
1627 m_est.reset_context();
1629 if((
int)inputs.source.value != m_cfg_source)
1634 m_last_event_time = -1e9;
1635 m_est.reset_context();
1636 m_elevated_until = m_now + 4.;
1637 m_cfg_source = (int)inputs.source.value;
1639 m_odf.whitening = inputs.whitening;
1640 m_dll.order = inputs.filter.value == clock_filter::dll_3rd_order ? 3 : 2;
1646 const double bpm = m_tap.tap(m_now);
1653 m_dll.t1 = m_now + m_dll.e2;
1659 const double period = 60. / bpm;
1660 m_dll.seed(m_now + period, period);
1661 seed_estimator_from_period(period);
1662 m_elevated_until = m_now + 4.;
1673 m_dll.t1 = m_now + m_dll.e2;
1674 m_downbeat_origin = m_dll.beat_index + 1;
1678 void on_nudge(
int direction)
1683 const double d = direction * 0.02 * m_dll.e2;
1689 void on_octave(
double factor)
1694 const double p = std::clamp(
1695 m_dll.e2 * factor, btrk::beat_dll::min_period, btrk::beat_dll::max_period);
1697 m_dll.t1 = std::min(m_dll.t1, m_dll.t0 + p);
1698 seed_estimator_from_period(p);
1702 void seed_estimator_from_period(
double period_seconds)
1704 const double frames = period_seconds * m_odf.fps;
1706 m_ct.set_period(std::clamp<double>(frames, m_est.min_lag, m_est.max_lag));
1710 using tick = halp::tick_flicks;
1711 void operator()(halp::tick_flicks tk)
1713 if(setup.rate <= 0 || tk.frames <= 0)
1715 const double rate = setup.rate;
1716 const int frames = tk.frames;
1717 const double block_start = m_samples / rate;
1718 const double block_dt = frames / rate;
1720 reconfigure_if_needed();
1721 handle_transport_hint(tk);
1723 const auto mode = inputs.source.value;
1724 const bool use_audio = mode != source_mode::events;
1725 const bool use_events = mode != source_mode::audio;
1736 if((
int)m_mono.size() < frames)
1737 m_mono.resize(frames);
1738 const int chans = inputs.audio.channels;
1740 for(
int i = 0; i < frames; i++)
1743 for(
int c = 0; c < chans; c++)
1744 acc +=
float(inputs.audio.samples[c][i]);
1746 acc /= float(chans);
1750 rms = std::sqrt(rms / std::max(1, frames));
1751 const double level_db = 20. * std::log10(rms + 1e-12);
1752 if(level_db > inputs.gate)
1754 m_silence_time = 0.;
1759 m_silence_time += block_dt;
1762 if(m_silence_time > 2.0)
1763 m_gate_open =
false;
1769 const bool events_alive = use_events && (m_now - m_last_event_time) <
event_hang();
1771 m_gate_open = events_alive;
1772 else if(events_alive)
1776 const bool holding = inputs.hold || !inputs.follow || !m_gate_open;
1779 else if(m_now < m_elevated_until)
1781 else if(m_mon.combined > 0.6)
1783 else if(m_mon.combined > 0.3)
1790 m_odf.process(m_mono.data(), frames, [&](
double odf_v, int64_t end_sample) {
1791 on_odf_frame(odf_v, end_sample, rate, block_start, frames);
1793 m_samples += frames;
1794 m_now = m_samples / rate;
1798 if(m_mon.reinduce_request && !holding)
1800 m_mon.reinduce_request =
false;
1801 m_est.reset_context();
1802 m_elevated_until = m_now + 4.;
1805 emit_beats(rate, block_start, frames);
1806 update_outputs(tk, block_dt);
1809 void handle_transport_hint(
const halp::tick_flicks& tk)
1811 if(!inputs.hint || tk.tempo <= 0.)
1817 m_prev_transport_tempo = tk.tempo;
1818 const double period = 60. / tk.tempo;
1819 m_dll.seed(m_now + period, period);
1820 seed_estimator_from_period(period);
1821 m_elevated_until = m_now + 4.;
1824 if(std::abs(tk.tempo - m_prev_transport_tempo) > 1e-9)
1830 if(m_last_emitted_tempo < 0.
1831 || std::abs(tk.tempo - m_last_emitted_tempo) > 0.5)
1833 seed_estimator_from_period(60. / tk.tempo);
1835 m_dll.seed(m_now + 60. / tk.tempo, 60. / tk.tempo,
false);
1836 m_elevated_until = m_now + 4.;
1838 m_prev_transport_tempo = tk.tempo;
1843 double odf_v, int64_t end_sample,
double rate,
double block_start,
int frames)
1846 const double mean = m_detrend(odf_v);
1847 const double d = std::max(0., odf_v - mean);
1850 m_odf_hist[0] = m_odf_hist[1];
1851 m_odf_hist[1] = m_odf_hist[2];
1852 m_odf_hist[2] = odf_v;
1853 if(m_odf_hist_n < 3)
1855 const int64_t cur_frame = m_odf.frame_count;
1856 if(m_odf_hist_n >= 3 && m_gate_open && m_odf_hist[1] > m_odf_hist[2]
1857 && m_odf_hist[1] >= m_odf_hist[0] && m_odf_hist[1] > 1.5 * mean + 1e-4
1858 && cur_frame - m_last_onset_frame > (int64_t)(0.03 * m_odf.fps))
1860 m_last_onset_frame = cur_frame;
1861 const int64_t onset_sample = end_sample - m_odf.hop;
1862 const int64_t off = onset_sample - (int64_t)(block_start * rate);
1863 outputs.onset(std::clamp<int64_t>(off, 0, frames - 1));
1876 if(m_est.push(d) && m_est.period > 0.)
1878 m_ct.set_period(m_est.period);
1883 if(!m_is_locked && m_est.confidence > 0.2 && m_dll.inited && m_dll.bw > 0.)
1885 m_dll.e2 = std::clamp(
1886 m_est.period / m_odf.fps, btrk::beat_dll::min_period,
1887 btrk::beat_dll::max_period);
1892 const int64_t predicted_frame = m_ct.push(d);
1893 if(predicted_frame >= 0 && !inputs.hold)
1898 const double tb = (predicted_frame - 3) * m_odf.hop / rate;
1899 m_dll.observe(tb, (
double)end_sample / rate);
1909 const double lo = inputs.limit_range ? inputs.min_bpm.value : 40.;
1910 const double hi = inputs.limit_range ? inputs.max_bpm.value : 240.;
1911 return {60. / std::max(1., hi), 60. / std::max(1., lo)};
1919 const double p = m_ev.period > 0. ? m_ev.period : m_dll.e2;
1920 return std::max(2.0, 4. * p);
1928 const int div = std::max(1, (
int)inputs.events_per_beat);
1929 const double block_end = block_start + frames / rate;
1932 for(
auto& [off, val] : inputs.beat_in.values)
1934 const int64_t o = std::clamp<int64_t>(off, 0, frames > 0 ? frames - 1 : 0);
1935 const double t = block_start + double(o) / rate;
1937 const std::optional<int> pending_number = btrk::beat_number_of(val);
1939 m_last_event_time = t;
1942 if(!inputs.follow || inputs.hold)
1945 if(!m_ev.push(t, div, min_p, max_p))
1950 const bool on_beat = (div == 1) || (pending_number && (*pending_number % div == 0));
1956 m_dll.seed(t + m_ev.period, m_ev.period);
1957 m_elevated_until = m_now + 4.;
1963 if(!m_is_locked && m_ev.confidence > 0.2 && m_dll.bw > 0.)
1964 m_dll.e2 = std::clamp(
1965 m_ev.period, btrk::beat_dll::min_period, btrk::beat_dll::max_period);
1967 m_dll.observe(t + m_dll.e2, std::min(t, block_end));
1974 const int bpb = std::max(1, (
int)inputs.beats_per_bar);
1975 const int beat_in_bar = ((*pending_number / div) % bpb + bpb) % bpb;
1976 m_downbeat_origin = m_dll.beat_index + 1 - beat_in_bar;
1981 void emit_beats(
double rate,
double block_start,
int frames)
1985 const double lookahead = inputs.lookahead * 1e-3;
1986 const double offset = inputs.offset * 1e-3;
1987 const double block_end = block_start + frames / rate;
1991 for(
int k = 0; k < 8; k++)
1993 const double tb = m_dll.t1 + k * m_dll.e2;
1994 const double emit_time = tb - lookahead - offset;
1995 if(emit_time >= block_end)
1997 const int64_t index = m_dll.beat_index + 1 + k;
1998 if(index <= m_last_emitted_beat)
2000 if(emit_time < block_start - 0.5 * m_dll.e2)
2002 const int64_t off = std::clamp<int64_t>(
2003 (int64_t)std::lround((emit_time - block_start) * rate), 0, frames - 1);
2004 m_last_emitted_beat = index;
2006 const int bpb = std::max(1, (
int)inputs.beats_per_bar);
2007 if(((index - m_downbeat_origin) % bpb + bpb) % bpb == 0)
2008 outputs.downbeat(off);
2012 m_dll.advance(m_now, [](
double, int64_t) {});
2015 void update_outputs(
const halp::tick_flicks& tk,
double block_dt)
2017 const bool valid = m_gate_open && m_dll.inited;
2022 const double rel_rms
2023 = m_dll.e2 > 0. ? std::sqrt(m_dll.innovation_rms) / m_dll.e2 : 1.;
2024 double contrast = m_ct.last_contrast;
2025 double est_conf = m_est.confidence;
2026 if(inputs.source.value != source_mode::audio)
2028 const double ev = (m_now - m_last_event_time) <
event_hang() ? m_ev.confidence : 0.;
2029 if(inputs.source.value == source_mode::events)
2036 contrast = std::max(contrast, ev);
2037 est_conf = std::max(est_conf, ev);
2040 m_mon.update(block_dt, contrast, est_conf, rel_rms);
2043 m_tempo_accum_t += block_dt;
2044 if(m_tempo_accum_t >= 1. / 16. && m_dll.inited)
2046 m_tempo_accum_t = 0.;
2047 m_tempo_hist[m_tempo_hist_pos] = m_dll.tempo();
2048 m_tempo_hist_pos = (m_tempo_hist_pos + 1) % tempo_hist_n;
2049 m_tempo_hist_len = std::min(m_tempo_hist_len + 1, tempo_hist_n);
2051 if(m_tempo_hist_len >= tempo_hist_n / 2)
2054 for(
int i = 0; i < m_tempo_hist_len; i++)
2055 mean += m_tempo_hist[i];
2056 mean /= m_tempo_hist_len;
2058 for(
int i = 0; i < m_tempo_hist_len; i++)
2059 var += (m_tempo_hist[i] - mean) * (m_tempo_hist[i] - mean);
2061 = mean > 1. ? std::sqrt(var / m_tempo_hist_len) / mean : 1.;
2068 const bool steady = m_tempo_rel_sd < 0.005 && rel_rms < 0.15
2069 && m_tempo_hist_len >= tempo_hist_n / 2;
2070 if((m_mon.combined > 0.6 || steady) && valid)
2071 m_locked_count = std::min(m_locked_count + 1, 1000);
2073 m_locked_count = std::max(m_locked_count - 2, 0);
2074 m_is_locked = m_locked_count > 20;
2076 const double raw_tempo = m_dll.inited ? m_dll.tempo() : 120.;
2077 const double alpha = ossia::lag_alpha(0.25, block_dt);
2078 const double display_tempo = m_tempo_display(raw_tempo, alpha);
2082 const double ref_tempo = (inputs.hint && tk.tempo > 0.) ? tk.tempo : 120.;
2083 const double tempo_ratio = raw_tempo / ref_tempo;
2084 double err_beats = 0.;
2085 if(m_dll.inited && valid)
2087 const double our_phase = m_dll.phase(m_now);
2088 const double transport_phase
2089 = tk.end_position_in_quarters - std::floor(tk.end_position_in_quarters);
2090 err_beats = our_phase - transport_phase;
2091 err_beats -= std::round(err_beats);
2092 if(std::abs(err_beats) < sync_error_deadband)
2096 = std::clamp(sync_p_gain * err_beats, -sync_adjustment_cap, sync_adjustment_cap);
2098 += std::clamp(trim_target - m_speed_trim, -sync_delta_cap, sync_delta_cap);
2100 outputs.tempo = display_tempo;
2101 m_last_emitted_tempo = display_tempo;
2102 outputs.speed = valid ? tempo_ratio * (1. + m_speed_trim) : 1.;
2103 outputs.phase = m_dll.phase(m_now);
2104 const int bpb = std::max(1, (
int)inputs.beats_per_bar);
2105 const int64_t idx = m_dll.beat_index < 0 ? 0 : m_dll.beat_index;
2106 const int64_t in_bar = ((idx - m_downbeat_origin) % bpb + bpb) % bpb;
2107 outputs.bar_phase = (in_bar + outputs.phase) / bpb;
2108 outputs.beat_index = (int)idx;
2109 const double lookahead = inputs.lookahead * 1e-3;
2111 = m_dll.inited ? std::max(0., (m_dll.t1 - m_now) - lookahead) : 0.;
2112 outputs.confidence = m_mon.combined;
2113 outputs.locked = m_is_locked;
2114 outputs.valid = valid;