mod audio;
mod inference;
mod mel;
mod output;
mod postprocessing;
mod runtime;
use std::path::Path;
use std::time::Duration;
use anyhow::Result;
pub use audio::{load_audio, AudioData};
pub use output::{beat_counts, calculate_bpm};
#[cfg(feature = "ort")]
pub use runtime::ort::OrtRuntime;
pub use runtime::{rten::RtenRuntime, Model, Runtime, Tensor};
use inference::BeatPredictor;
use mel::MelExtractor;
use postprocessing::PeakPicker;
const TARGET_SAMPLE_RATE: u32 = 22050;
#[derive(Debug, Clone)]
pub struct BeatAnalysis {
pub beats: Vec<f32>,
pub downbeats: Vec<f32>,
pub mel: Tensor,
pub beat_logits: Vec<f32>,
pub downbeat_logits: Vec<f32>,
}
pub struct BeatThis<M: Model> {
mel: MelExtractor<M>,
predictor: BeatPredictor<M>,
peak_picker: PeakPicker,
}
#[derive(Debug, Clone)]
pub struct AnalysisTiming {
pub mel: Duration,
pub predict: Duration,
pub decode: Duration,
}
#[derive(Debug, Clone)]
pub struct TimedAnalysis {
pub analysis: BeatAnalysis,
pub timing: AnalysisTiming,
}
impl<M: Model> BeatThis<M> {
pub fn new<R: Runtime<Model = M>>(
runtime: &R,
mel_model_path: &Path,
beat_model_path: &Path,
) -> Result<Self> {
let mel_model = runtime.load_model(mel_model_path)?;
let beat_model = runtime.load_model(beat_model_path)?;
Ok(Self {
mel: MelExtractor::new(mel_model),
predictor: BeatPredictor::new(beat_model),
peak_picker: PeakPicker::default(),
})
}
pub fn from_models(mel_model: M, beat_model: M) -> Self {
Self {
mel: MelExtractor::new(mel_model),
predictor: BeatPredictor::new(beat_model),
peak_picker: PeakPicker::default(),
}
}
pub fn beat_model_mut(&mut self) -> &mut M {
self.predictor.model_mut()
}
pub fn analyze_audio(&mut self, samples: &[f32], sample_rate: u32) -> Result<BeatAnalysis> {
Ok(self.analyze_audio_timed(samples, sample_rate)?.analysis)
}
pub fn analyze_audio_timed(
&mut self,
samples: &[f32],
sample_rate: u32,
) -> Result<TimedAnalysis> {
let samples = if sample_rate != TARGET_SAMPLE_RATE {
audio::resample(samples.to_vec(), sample_rate, TARGET_SAMPLE_RATE)?
} else {
samples.to_vec()
};
let t = std::time::Instant::now();
let mel = self.mel.extract(&samples)?;
let mel_time = t.elapsed();
let t = std::time::Instant::now();
let (beat_logits, downbeat_logits) = self.predictor.predict(&mel)?;
let predict_time = t.elapsed();
let t = std::time::Instant::now();
let (beats, downbeats) = self.peak_picker.decode(&beat_logits, &downbeat_logits)?;
let decode_time = t.elapsed();
Ok(TimedAnalysis {
analysis: BeatAnalysis {
beats,
downbeats,
mel,
beat_logits,
downbeat_logits,
},
timing: AnalysisTiming {
mel: mel_time,
predict: predict_time,
decode: decode_time,
},
})
}
pub fn analyze_file(&mut self, path: &Path) -> Result<BeatAnalysis> {
let audio = load_audio(path, TARGET_SAMPLE_RATE)?;
self.analyze_audio(&audio.samples, audio.sample_rate)
}
}