Improve Beat Detection

This commit is contained in:
Kai Vogelgesang 2021-11-12 16:37:04 +01:00
parent 25f5229afe
commit bf62ceb98f
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
4 changed files with 104 additions and 75 deletions

View File

@ -3,30 +3,15 @@ import { Pattern, PatternOutput, Time } from './proto';
export class ChaserPattern implements Pattern { export class ChaserPattern implements Pattern {
lastBeat: number;
lastTime: number;
constructor() {
this.lastBeat = 0;
this.lastTime = 0;
}
render(time: Time): PatternOutput { render(time: Time): PatternOutput {
if (time.beatRelative === null) { if (time.beatRelative === null) {
this.lastBeat = 0;
return null; return null;
} }
let t = time.beatRelative; let t = time.beatRelative;
if (t < this.lastTime) { let head_number = Math.floor(t % 4);
this.lastBeat += 1;
}
this.lastTime = t;
let head_number = this.lastBeat % 4;
let template: MovingHeadState = { let template: MovingHeadState = {
startAddress: 0, startAddress: 0,
@ -44,7 +29,7 @@ export class ChaserPattern implements Pattern {
let result = []; let result = [];
for (let [i, startAddress] of [1, 15, 29, 43].entries()) { for (let [i, startAddress] of [1, 15, 29, 43].entries()) {
result[i] = { ...template}; result[i] = { ...template };
result[i].startAddress = startAddress; result[i].startAddress = startAddress;
if (i === head_number) { if (i === head_number) {

View File

@ -20,7 +20,7 @@ const BUFFER_SIZE: usize = SAMPLE_RATE / PULSE_UPDATES_PER_SECOND;
const POINTS_PER_SECOND: usize = SAMPLE_RATE / 200; const POINTS_PER_SECOND: usize = SAMPLE_RATE / 200;
pub const MILLIS_PER_POINT: usize = 1000 / POINTS_PER_SECOND; pub const MILLIS_PER_POINT: usize = 1000 / POINTS_PER_SECOND;
const POINT_MIN_COUNT: usize = 2 * POINTS_PER_SECOND; const POINT_MIN_COUNT: usize = 2 * POINTS_PER_SECOND;
const POINT_BUFFER_SIZE: usize = POINT_MIN_COUNT.next_power_of_two(); pub const POINT_BUFFER_SIZE: usize = POINT_MIN_COUNT.next_power_of_two();
pub struct AudioCaptureThread { pub struct AudioCaptureThread {
join_handle: Option<JoinHandle<Result<()>>>, join_handle: Option<JoinHandle<Result<()>>>,

View File

@ -105,7 +105,7 @@ pub fn get_progress(mut cx: FunctionContext) -> JsResult<JsObject> {
let obj = cx.empty_object(); let obj = cx.empty_object();
let type_string; let type_string;
match boxed_tracker.borrow().current_beat_progress() { match boxed_tracker.borrow_mut().current_beat_progress() {
Some(progress) => { Some(progress) => {
type_string = cx.string("some".to_string()); type_string = cx.string("some".to_string());

View File

@ -3,6 +3,8 @@ use num::Complex;
use rustfft::FftPlanner; use rustfft::FftPlanner;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::beat_tracking::audio::POINT_BUFFER_SIZE;
use super::audio::{AudioCaptureThread, MILLIS_PER_POINT}; use super::audio::{AudioCaptureThread, MILLIS_PER_POINT};
use super::metronome::Metronome; use super::metronome::Metronome;
@ -22,6 +24,9 @@ pub struct BeatTracker {
metronome: Metronome, metronome: Metronome,
pub config: TrackerConfig, pub config: TrackerConfig,
audio_capture_thread: AudioCaptureThread, audio_capture_thread: AudioCaptureThread,
current_beats: Option<(Instant, Instant)>,
beat_count: usize,
} }
impl BeatTracker { impl BeatTracker {
@ -34,6 +39,9 @@ impl BeatTracker {
zero_crossing_beat_delay: 0, zero_crossing_beat_delay: 0,
}, },
audio_capture_thread: AudioCaptureThread::new(), audio_capture_thread: AudioCaptureThread::new(),
current_beats: None,
beat_count: 0,
} }
} }
@ -101,9 +109,8 @@ impl BeatTracker {
(points, autocorrelation) (points, autocorrelation)
} }
pub fn current_beat_progress(&self) -> Option<f64> { // (period_length [ms], crossing [point index, timing relative to last_update])
match self.config.mode { fn get_correlation_data(&self) -> Option<(usize, usize)> {
TrackerMode::AUTO => {
let (points, autocorrelation) = self.get_graph_points(); let (points, autocorrelation) = self.get_graph_points();
if autocorrelation if autocorrelation
@ -134,8 +141,6 @@ impl BeatTracker {
let period_length = i.unwrap(); let period_length = i.unwrap();
// println!("predicted period length: {}", period_length);
// find zero crossing // find zero crossing
let mut crossing = None; let mut crossing = None;
@ -151,31 +156,70 @@ impl BeatTracker {
} }
let crossing = crossing.unwrap(); let crossing = crossing.unwrap();
// println!("index of last positive zero crossing: {}", crossing);
let last_update_timestamp = self.audio_capture_thread.get_last_update(); Some((period_length, crossing))
}
pub fn current_beat_progress(&mut self) -> Option<f64> {
let now = Instant::now(); let now = Instant::now();
match self.config.mode {
TrackerMode::AUTO => {
// println!("reee");
let should_update = {
match self.current_beats {
None => true,
Some((_, next)) if now > next => {
self.beat_count += 1;
true
}
_ => false,
}
};
if should_update {
if let Some((period_length, crossing)) = self.get_correlation_data() {
let last_update_timestamp = self.audio_capture_thread.get_last_update();
let mut dt = (now - last_update_timestamp).as_millis() as i64; let mut dt = (now - last_update_timestamp).as_millis() as i64;
dt += ((points.len() - crossing) * MILLIS_PER_POINT) as i64; dt += ((POINT_BUFFER_SIZE - crossing) * MILLIS_PER_POINT) as i64;
dt += self.config.zero_crossing_beat_delay;
let dt = dt as f64; let mut prev = now - Duration::from_millis(dt as u64);
let period_millis = (period_length * MILLIS_PER_POINT) as f64; let period_millis = (period_length * MILLIS_PER_POINT) as u64;
let relative_time = (dt % period_millis) / period_millis; let period = Duration::from_millis(period_millis);
println!("relative time: {}", relative_time); let mut next = prev + period;
Some(relative_time) if let Some((_, old_next)) = self.current_beats {
while next < old_next + Duration::from_millis(period_millis / 2) {
prev += period;
next += period;
}
} }
TrackerMode::MANUAL => { self.current_beats = Some((prev, next));
let now = Instant::now(); } else {
self.metronome.get_beat_progress(now) self.beat_count = 0;
return None;
} }
} }
let (prev, next) = self.current_beats.unwrap();
let fractional = if prev < now {
(now - prev).as_millis() as f64 / (next - prev).as_millis() as f64
} else {
-1.0 * (prev - now).as_millis() as f64 / (next - prev).as_millis() as f64
};
Some(self.beat_count as f64 + fractional)
}
TrackerMode::MANUAL => self.metronome.get_beat_progress(now),
}
} }
pub fn stop(&mut self) -> Result<()> { pub fn stop(&mut self) -> Result<()> {