sound_to_patch/notes/
transcribe.rs1use std::error::Error;
9use std::fmt;
10use std::num::NonZeroUsize;
11
12use basic_pitch::notes::{NoteEvent, frame_to_time, model_output_to_notes};
13use basic_pitch::{BasicPitch, Posteriorgrams};
14
15pub use basic_pitch::model::{Backend, MODEL_SHA256};
16
17use super::{DecodeSettings, LOWEST_MODEL_PITCH, Note, NoteList, PitchTrack, TrackError, Velocity};
18use crate::audio::{ResampleError, SampleRate, resample};
19use crate::cast;
20use crate::units::{MidiPitch, Seconds};
21
22const MODEL_HOP: u16 = 256;
24
25pub const MODEL_RATE: SampleRate = SampleRate::from_const(22_050);
27
28pub type Tracker = fn(&[f64], SampleRate) -> Result<PitchTrack, TrackError>;
31
32#[derive(Debug)]
35pub struct Transcriber {
36 model: BasicPitch,
37 tracker: Tracker,
38}
39
40impl Transcriber {
41 pub fn new() -> Result<Self, TranscribeError> {
48 let threads = std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN);
49 Ok(Self {
50 model: BasicPitch::new()
51 .map_err(TranscribeError::Model)?
52 .with_threads(threads),
53 tracker: PitchTrack::of,
54 })
55 }
56
57 #[must_use]
60 pub fn with_threads(self, threads: NonZeroUsize) -> Self {
61 Self {
62 model: self.model.with_threads(threads),
63 ..self
64 }
65 }
66
67 #[must_use]
70 pub fn with_backend(backend: impl Backend + 'static) -> Self {
71 Self {
72 model: BasicPitch::with_backend(backend),
73 tracker: PitchTrack::of,
74 }
75 }
76
77 #[must_use]
80 pub fn with_tracker(self, tracker: Tracker) -> Self {
81 Self { tracker, ..self }
82 }
83
84 pub(crate) fn track(&self, mono: &[f64], rate: SampleRate) -> Result<PitchTrack, TrackError> {
86 (self.tracker)(mono, rate)
87 }
88
89 pub fn run(&self, mono: &[f64], rate: SampleRate) -> Result<ModelRun, TranscribeError> {
98 let samples = resample(mono, rate, MODEL_RATE).map_err(TranscribeError::Resample)?;
99 let input: Vec<f32> = samples.into_iter().map(cast::f64_to_f32).collect();
100 let posteriorgrams = self.model.predict(&input).map_err(TranscribeError::Model)?;
101 Ok(ModelRun { posteriorgrams })
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct ModelRun {
108 posteriorgrams: Posteriorgrams,
109}
110
111impl ModelRun {
112 #[cfg(test)]
114 pub(crate) fn from_posteriorgrams(posteriorgrams: Posteriorgrams) -> Self {
115 Self { posteriorgrams }
116 }
117
118 #[must_use]
120 pub fn posteriorgrams(&self) -> &Posteriorgrams {
121 &self.posteriorgrams
122 }
123
124 #[must_use]
129 pub fn onset_strength(&self, time: Seconds, pitch: MidiPitch) -> Option<f64> {
130 let onsets = &self.posteriorgrams.onset;
131 let bin = cast::round_to_usize(pitch.get() - f64::from(LOWEST_MODEL_PITCH))
132 .filter(|&bin| bin < onsets.n_bins())?;
133 let hop = f64::from(MODEL_HOP) / f64::from(MODEL_RATE.get());
134 (0..onsets.n_frames())
135 .filter(|&frame| (frame_to_time(frame) - time.get()).abs() <= hop)
136 .filter_map(|frame| onsets.get(frame, bin))
137 .map(f64::from)
138 .reduce(f64::max)
139 }
140
141 pub fn decode(&self, settings: &DecodeSettings) -> Result<NoteList, TranscribeError> {
153 let events = model_output_to_notes(&self.posteriorgrams, &settings.note_params())
154 .map_err(TranscribeError::Model)?;
155 Ok(NoteList::new(events.iter().filter_map(note_of).collect()))
156 }
157}
158
159fn note_of(event: &NoteEvent) -> Option<Note> {
160 let note = Note::new(
161 Seconds::clamped(event.start_s),
162 Seconds::clamped(event.end_s),
163 MidiPitch::clamped(f64::from(event.pitch)),
164 )
165 .ok()?;
166 Some(match Velocity::new(event.velocity().max(1)) {
169 Ok(velocity) => note.with_velocity(velocity),
170 Err(_) => note,
171 })
172}
173
174#[derive(Debug)]
176pub enum TranscribeError {
177 Resample(ResampleError),
179 Model(basic_pitch::Error),
181}
182
183impl fmt::Display for TranscribeError {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 match self {
186 Self::Resample(source) => {
187 write!(
188 f,
189 "cannot prepare the stem for the transcription model: {source}"
190 )
191 }
192 Self::Model(source) => write!(f, "the transcription model failed: {source}"),
193 }
194 }
195}
196
197impl Error for TranscribeError {
198 fn source(&self) -> Option<&(dyn Error + 'static)> {
199 match self {
200 Self::Resample(source) => Some(source),
201 Self::Model(source) => Some(source),
202 }
203 }
204}