Make beat tracking configurable

This commit is contained in:
Kai Vogelgesang 2021-11-12 13:49:17 +01:00
parent 41b54f6209
commit 25f5229afe
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
5 changed files with 130 additions and 67 deletions

View File

@ -30,12 +30,19 @@ declare module rust_native_module {
close: () => Result<never>, close: () => Result<never>,
} }
type TrackerConfig = {
mode: "auto" | "manual",
acThreshold: number,
zeroCrossingBeatDelay: number,
}
type GraphPoints = { type GraphPoints = {
bassFiltered: Array<number>, bassFiltered: Array<number>,
autoCorrelated: Array<number>, autoCorrelated: Array<number>,
} }
type BeatTrackerHandle = { type BeatTrackerHandle = {
setConfig: (config: TrackerConfig) => void,
tap: () => void, tap: () => void,
getProgress: () => Option<number>, getProgress: () => Option<number>,
stop: () => Result<never>, stop: () => Result<never>,

View File

@ -53,11 +53,6 @@ impl AudioCaptureThread {
} }
} }
pub fn read_state(&self) -> (Vec<f32>, Instant) {
let state = self.shared_state.lock().unwrap();
(state.point_buf.to_vec(), state.last_update)
}
pub fn get_points(&self) -> Vec<f32> { pub fn get_points(&self) -> Vec<f32> {
let state = self.shared_state.lock().unwrap(); let state = self.shared_state.lock().unwrap();
state.point_buf.to_vec() state.point_buf.to_vec()

View File

@ -1,5 +1,4 @@
use std::{ use std::{
ops::RangeInclusive,
time::{Duration, Instant}, time::{Duration, Instant},
}; };

View File

@ -8,6 +8,8 @@ use tracker::BeatTracker;
use neon::prelude::*; use neon::prelude::*;
use self::tracker::TrackerMode;
type BoxedTracker = JsBox<RefCell<BeatTracker>>; type BoxedTracker = JsBox<RefCell<BeatTracker>>;
impl Finalize for BeatTracker {} impl Finalize for BeatTracker {}
@ -18,6 +20,9 @@ pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult<JsObject> {
let boxed_tracker = cx.boxed(RefCell::new(BeatTracker::new(Duration::from_secs(2)))); let boxed_tracker = cx.boxed(RefCell::new(BeatTracker::new(Duration::from_secs(2))));
value_obj.set(&mut cx, "_rust_ptr", boxed_tracker)?; value_obj.set(&mut cx, "_rust_ptr", boxed_tracker)?;
let set_config_function = JsFunction::new(&mut cx, set_config)?;
value_obj.set(&mut cx, "setConfig", set_config_function)?;
let tap_function = JsFunction::new(&mut cx, tap)?; let tap_function = JsFunction::new(&mut cx, tap)?;
value_obj.set(&mut cx, "tap", tap_function)?; value_obj.set(&mut cx, "tap", tap_function)?;
@ -38,6 +43,46 @@ pub fn get_beat_tracker(mut cx: FunctionContext) -> JsResult<JsObject> {
Ok(obj) Ok(obj)
} }
pub fn set_config(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.this();
let boxed_tracker = this
.get(&mut cx, "_rust_ptr")?
.downcast_or_throw::<BoxedTracker, _>(&mut cx)?;
let ref mut config = boxed_tracker.borrow_mut().config;
let arg = cx.argument::<JsObject>(0)?;
let mode = arg
.get(&mut cx, "mode")?
.downcast_or_throw::<JsString, _>(&mut cx)?
.value(&mut cx);
config.mode = match mode.as_str() {
"auto" => TrackerMode::AUTO,
"manual" => TrackerMode::MANUAL,
s => {
return cx.throw_error(format!("Invalid config mode: '{}'", s.to_string()));
}
};
let ac_threshold = arg
.get(&mut cx, "acThreshold")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx);
config.ac_threshold = ac_threshold as f32;
let zero_crossing_beat_delay = arg
.get(&mut cx, "zeroCrossingBeatDelay")?
.downcast_or_throw::<JsNumber, _>(&mut cx)?
.value(&mut cx);
config.zero_crossing_beat_delay = zero_crossing_beat_delay as i64;
Ok(cx.undefined())
}
pub fn tap(mut cx: FunctionContext) -> JsResult<JsValue> { pub fn tap(mut cx: FunctionContext) -> JsResult<JsValue> {
let this = cx.this(); let this = cx.this();

View File

@ -6,14 +6,21 @@ use std::time::{Duration, Instant};
use super::audio::{AudioCaptureThread, MILLIS_PER_POINT}; use super::audio::{AudioCaptureThread, MILLIS_PER_POINT};
use super::metronome::Metronome; use super::metronome::Metronome;
pub enum TrackerMode {
AUTO,
MANUAL,
}
pub struct TrackerConfig { pub struct TrackerConfig {
ac_threshold: f32, pub mode: TrackerMode,
zero_crossing_beat_delay: i64,
pub ac_threshold: f32,
pub zero_crossing_beat_delay: i64,
} }
pub struct BeatTracker { pub struct BeatTracker {
metronome: Metronome, metronome: Metronome,
config: TrackerConfig, pub config: TrackerConfig,
audio_capture_thread: AudioCaptureThread, audio_capture_thread: AudioCaptureThread,
} }
@ -22,6 +29,7 @@ impl BeatTracker {
Self { Self {
metronome: Metronome::new(metronome_timeout), metronome: Metronome::new(metronome_timeout),
config: TrackerConfig { config: TrackerConfig {
mode: TrackerMode::AUTO,
ac_threshold: 1000.0, ac_threshold: 1000.0,
zero_crossing_beat_delay: 0, zero_crossing_beat_delay: 0,
}, },
@ -94,6 +102,8 @@ impl BeatTracker {
} }
pub fn current_beat_progress(&self) -> Option<f64> { pub fn current_beat_progress(&self) -> Option<f64> {
match self.config.mode {
TrackerMode::AUTO => {
let (points, autocorrelation) = self.get_graph_points(); let (points, autocorrelation) = self.get_graph_points();
if autocorrelation if autocorrelation
@ -161,6 +171,13 @@ impl BeatTracker {
Some(relative_time) Some(relative_time)
} }
TrackerMode::MANUAL => {
let now = Instant::now();
self.metronome.get_beat_progress(now)
}
}
}
pub fn stop(&mut self) -> Result<()> { pub fn stop(&mut self) -> Result<()> {
self.audio_capture_thread.stop() self.audio_capture_thread.stop()
} }