1use crate::defaults::{FloatType, IntegerType};
21use crate::duration::Duration;
22use crate::error::{Error, Result};
23use crate::metadata::Metadata;
24use crate::note::Note;
25use crate::pitch::Pitch;
26use crate::rest::Rest;
27use crate::scale::{Scale, ScaleType};
28use crate::stream::{Stream, StreamElement, StreamKind};
29
30pub const AUDIO_CHUNK_LENGTH: usize = 1024;
33
34pub const RECORD_SAMPLE_RATE: FloatType = 44100.0;
37
38pub const REST_FREQUENCY: FloatType = 10.0;
40
41fn audio_error(message: impl Into<String>) -> Error {
42 Error::Analysis(message.into())
43}
44
45fn default_scale(scale: Option<&Scale>) -> Result<Scale> {
46 match scale {
47 Some(scale) => Ok(scale.clone()),
48 None => Ok(Scale::new(ScaleType::Major, Pitch::from_name("C4")?)),
49 }
50}
51
52pub fn histogram(data: &[FloatType], bins: usize) -> Result<(Vec<usize>, Vec<FloatType>)> {
69 let most = data
70 .iter()
71 .copied()
72 .reduce(FloatType::max)
73 .ok_or_else(|| audio_error("no values to count"))?;
74 let least = data.iter().copied().fold(most, FloatType::min);
75 let width = (most - least) / bins as FloatType;
76 let mut counts = vec![0; bins];
77 for value in data {
78 let mut count = 1;
79 while *value > least + count as FloatType * width {
80 count += 1;
81 }
82 *counts
83 .get_mut(count - 1)
84 .ok_or_else(|| audio_error(format!("{value} falls past the last bin")))? += 1;
85 }
86 let edges = (0..=bins)
87 .map(|index| least + index as FloatType * width)
88 .collect();
89 Ok((counts, edges))
90}
91
92pub fn interpolation(correlation: &[FloatType], peak: usize) -> FloatType {
96 if peak == 0 || peak + 1 == correlation.len() {
97 return peak as FloatType;
98 }
99 let (before, at, after) = (
100 correlation[peak - 1],
101 correlation[peak],
102 correlation[peak + 1],
103 );
104 (before - after) / (before - 2.0 * at + after) * 0.5 + peak as FloatType
105}
106
107pub fn autocorrelation_frequency(
135 signal: &[FloatType],
136 sample_rate: FloatType,
137) -> Result<FloatType> {
138 if signal.is_empty() {
139 return Err(audio_error("v cannot be empty"));
140 }
141 let length = signal.len();
142 let correlation: Vec<FloatType> = (0..length)
143 .map(|lag| {
144 signal[..length - lag]
145 .iter()
146 .zip(&signal[lag..])
147 .map(|(a, b)| a * b)
148 .sum()
149 })
150 .collect();
151 let Some(beginning) = correlation
152 .windows(2)
153 .position(|pair| pair[1] - pair[0] > 0.0)
154 else {
155 return Ok(REST_FREQUENCY);
156 };
157 let peak = correlation[beginning..]
159 .iter()
160 .enumerate()
161 .fold((0, FloatType::NEG_INFINITY), |best, (index, value)| {
162 if *value > best.1 {
163 (index, *value)
164 } else {
165 best
166 }
167 })
168 .0
169 + beginning;
170 Ok(sample_rate / interpolation(&correlation, peak))
171}
172
173pub fn prepare_thresholds(scale: Option<&Scale>) -> Result<(Vec<FloatType>, Vec<Pitch>)> {
181 let scale = match scale {
182 Some(scale) => scale.clone(),
183 None => Scale::new(ScaleType::Chromatic, Pitch::from_name("C4")?),
184 };
185 let pitches = scale.pitches()?;
186 let mut remainders: Vec<FloatType> = pitches
187 .iter()
188 .map(|pitch| pitch.frequency_hz().log2().fract())
189 .collect();
190 if let Some(last) = remainders.last_mut() {
191 *last += 1.0;
192 }
193 let thresholds = remainders
194 .windows(2)
195 .map(|pair| (pair[0] + pair[1]) / 2.0)
196 .collect();
197 Ok((thresholds, pitches))
198}
199
200pub fn normalize_input_frequency(
209 frequency: FloatType,
210 thresholds: &[FloatType],
211 pitches: &[Pitch],
212) -> Result<(FloatType, Pitch)> {
213 let log = frequency.log2();
214 if !log.is_finite() {
215 return Err(audio_error(format!(
216 "cannot place a frequency of {frequency}"
217 )));
218 }
219 let (remainder, octave) = (log.fract(), log.trunc() as IntegerType);
220 let (index, shift) = match thresholds
221 .iter()
222 .position(|threshold| remainder < *threshold)
223 {
224 Some(index) => (index, 4),
225 None => (pitches.len().wrapping_sub(1), 3),
226 };
227 let scale_pitch = pitches
228 .get(index)
229 .ok_or_else(|| audio_error("the scale has no pitches"))?;
230 let mut placed = scale_pitch.clone();
231 placed.set_octave(Some(octave - shift));
232 Ok((scale_pitch.frequency_hz(), placed))
233}
234
235pub fn detect_pitch_frequencies(
243 frequencies: &[FloatType],
244 scale: Option<&Scale>,
245) -> Result<Vec<FloatType>> {
246 let (thresholds, pitches) = prepare_thresholds(Some(&default_scale(scale)?))?;
247 frequencies
248 .iter()
249 .map(|frequency| {
250 Ok(
251 normalize_input_frequency(*frequency, &thresholds, &pitches)?
252 .1
253 .frequency_hz(),
254 )
255 })
256 .collect()
257}
258
259pub fn smooth_frequencies(frequencies: &[FloatType], smooth_levels: usize) -> Result<Vec<i64>> {
278 if smooth_levels < 1 {
282 return Err(Error::Value("smoothLevels must be >= 1".to_string()));
283 }
284 let count = frequencies.len();
285 if smooth_levels > count {
286 return Err(Error::Value(format!(
287 "There cannot be more smoothLevels ({smooth_levels}) than inputPitches ({count})"
288 )));
289 }
290 let mut smoothed = frequencies.to_vec();
291 let levels = smooth_levels as FloatType;
292 let beginning = smoothed[..smooth_levels].iter().sum::<FloatType>() / levels;
293 let ends = (0..smooth_levels)
294 .map(|i| smoothed[count - 1 - i])
295 .sum::<FloatType>()
296 / levels;
297 let half_down = smooth_levels / 2;
298 let half_up = smooth_levels.div_ceil(2);
299 for i in 0..count {
300 smoothed[i] = if i < half_down {
301 beginning.trunc()
302 } else if (i as i64) > count as i64 - half_up as i64 - 1 {
303 ends.trunc()
304 } else {
305 (0..smooth_levels)
306 .map(|j| smoothed[i + j - half_down])
307 .sum::<FloatType>()
308 / levels
309 };
310 }
311 Ok(smoothed
312 .into_iter()
313 .map(|value| value.round_ties_even() as i64)
314 .collect())
315}
316
317pub fn pitch_frequencies_to_objects(
330 frequencies: &[FloatType],
331 scale: Option<&Scale>,
332) -> Result<(Vec<Pitch>, Vec<FloatType>)> {
333 let (thresholds, pitches) = prepare_thresholds(Some(&default_scale(scale)?))?;
334 let mut placed: Vec<Pitch> = frequencies
335 .iter()
336 .map(|frequency| Ok(normalize_input_frequency(*frequency, &thresholds, &pitches)?.1))
337 .collect::<Result<_>>()?;
338 let count = placed.len();
339 let mut plotted = Vec::new();
340 let mut i = 0;
341 while i + 1 < count {
342 let name = placed[i].name();
343 let hold = i;
344 let mut total: IntegerType = 0;
345 while i + 1 < count && placed[i].name() == name {
346 total += placed[i].octave().unwrap_or(4);
347 i += 1;
348 }
349 let octave =
350 (total as FloatType / (i - hold) as FloatType).round_ties_even() as IntegerType;
351 for j in 0..i - hold {
352 let index = (hold + j + count - 1) % count;
354 placed[index].set_octave(Some(octave));
355 plotted.push(placed[index].frequency_hz());
356 }
357 }
358 Ok((placed, plotted))
359}
360
361fn pitch_at_frequency(hertz: FloatType) -> Result<Pitch> {
363 let space = 12.0 * ((hertz / 440.0).ln() / (2.0 as FloatType).ln()) + 69.0;
364 Pitch::from_pitch_space((space * 1e6).round_ties_even() / 1e6)
365}
366
367#[derive(Clone, Copy, Debug, PartialEq, Eq)]
370#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
371pub struct JoinedNote {
372 pub pitch: Option<usize>,
374 pub length: usize,
376}
377
378pub fn joined_pitches(pitches: &[Pitch]) -> Result<Vec<JoinedNote>> {
389 let mut read = pitches.to_vec();
390 let first = read
391 .first_mut()
392 .ok_or_else(|| audio_error("no pitches to join"))?;
393 *first = pitch_at_frequency(REST_FREQUENCY)?;
394 let mut joined = Vec::new();
395 let (mut good, mut bad) = (0, 0);
396 let mut j = 0;
397 while j < read.len() {
398 let frequency = read[j].frequency_hz();
399 let mut valid = false;
400 while j < read.len() && read[j].frequency_hz() == frequency {
401 good += 1;
402 if good >= 6 {
403 valid = true;
404 if bad >= 15 {
405 joined.push(JoinedNote {
406 pitch: None,
407 length: bad,
408 });
409 }
410 bad = 0;
411 }
412 j += 1;
413 }
414 if valid {
415 joined.push(JoinedNote {
416 pitch: Some(j - 1),
417 length: good,
418 });
419 } else {
420 bad += good;
421 }
422 good = 0;
423 j += 1;
424 }
425 Ok(joined)
426}
427
428pub fn join_consecutive_identical_pitches(
438 mut pitches: Vec<Pitch>,
439) -> Result<(Vec<StreamElement>, Vec<usize>)> {
440 let first = pitches
441 .first_mut()
442 .ok_or_else(|| audio_error("no pitches to join"))?;
443 *first = pitch_at_frequency(REST_FREQUENCY)?;
444 let mut notes: Vec<StreamElement> = Vec::new();
445 let mut lengths = Vec::new();
446 for one in joined_pitches(&pitches)? {
447 lengths.push(one.length);
448 notes.push(match one.pitch {
449 Some(place) => Note::from_pitch(pitches[place].clone()).into(),
450 None => Rest::new(Duration::quarter()).into(),
451 });
452 }
453 Ok((notes, lengths))
454}
455
456pub fn quantize_duration(length: FloatType) -> FloatType {
459 let length = length * 100.0;
460 let typical = [25.0, 50.0, 100.0, 150.0, 200.0, 400.0];
461 let mut chosen = typical[0];
462 for (index, pair) in typical.windows(2).enumerate() {
463 if length > (pair[0] + pair[1]) / 2.0 {
464 chosen = typical[index + 1];
465 }
466 }
467 chosen / 100.0
468}
469
470pub fn quarter_length_estimation(
480 lengths: &[usize],
481 most_repeated_quarter_length: FloatType,
482) -> Result<FloatType> {
483 let mut data: Vec<FloatType> = lengths.iter().map(|length| *length as FloatType).collect();
484 data.push(0.0);
485 let (counts, edges) = histogram(&data, 8)?;
486 let fullest = counts.iter().copied().max().unwrap_or(0);
487 let index = counts
488 .iter()
489 .rposition(|count| *count == fullest)
490 .unwrap_or(0);
491 let estimate = (edges[index] + edges[index + 1]) / 2.0;
492 let most_repeated = if most_repeated_quarter_length == 0.0 {
493 1.0
494 } else {
495 most_repeated_quarter_length
496 };
497 Ok(estimate * (2.0 as FloatType).powf(0.0 - most_repeated.log2()))
498}
499
500#[derive(Clone, Copy, Debug, PartialEq)]
503#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
504pub struct TranscribedNote {
505 pub note: usize,
507 pub quarter_length: FloatType,
509 pub kept: bool,
512}
513
514pub fn transcribed_notes(
526 notes: &[StreamElement],
527 lengths: &[usize],
528 score_notes: Option<&Stream>,
529 remove_rests_at_beginning: bool,
530) -> Result<(Vec<TranscribedNote>, FloatType)> {
531 let most_common = match score_notes {
532 Some(score) => crate::features::vector_by_id(score, "QL2")?
533 .and_then(|vector| vector.first().map(|value| value.as_float()))
534 .unwrap_or(1.0),
535 None => 1.0,
536 };
537 let estimate = quarter_length_estimation(lengths, most_common)?;
538 let mut out = Vec::with_capacity(lengths.len());
539 let mut skipping = remove_rests_at_beginning;
540 for (index, length) in lengths.iter().enumerate() {
541 let element = notes
542 .get(index)
543 .ok_or_else(|| audio_error("more lengths than notes"))?;
544 let quarter_length = quantize_duration(*length as FloatType / estimate);
545 let kept = !(skipping && matches!(element, StreamElement::Rest(_)));
546 if kept {
547 skipping = false;
548 }
549 out.push(TranscribedNote {
550 note: index,
551 quarter_length,
552 kept,
553 });
554 }
555 Ok((out, estimate))
556}
557
558pub fn notes_and_durations_to_stream(
570 notes: &[StreamElement],
571 lengths: &[usize],
572 score_notes: Option<&Stream>,
573 remove_rests_at_beginning: bool,
574) -> Result<(Stream, FloatType)> {
575 let (given, estimate) =
576 transcribed_notes(notes, lengths, score_notes, remove_rests_at_beginning)?;
577 let mut part = Stream::with_kind(StreamKind::Part);
578 for one in &given {
579 let mut element = notes
580 .get(one.note)
581 .cloned()
582 .ok_or_else(|| audio_error("more lengths than notes"))?;
583 let duration = Duration::new(one.quarter_length)?;
584 match &mut element {
585 StreamElement::Note(note) => note.set_duration(duration),
586 StreamElement::Rest(rest) => rest.set_duration(duration),
587 _ => {}
588 }
589 if one.kept {
590 part.push(element);
591 }
592 }
593 let mut score = Stream::with_kind(StreamKind::Score);
594 let mut metadata = Metadata::default();
595 metadata.add_text("title", "Automatic Music21 Transcription");
596 score.set_metadata(Some(metadata));
597 score.insert(0.0, part);
598 Ok((score, estimate))
599}
600
601pub fn decision_process(
615 parts: &[(usize, FloatType)],
616 note_prediction: FloatType,
617 beginning_data: &[FloatType],
618 last_note_position: FloatType,
619 countdown: usize,
620 page: Option<(FloatType, FloatType)>,
621) -> Result<(usize, usize)> {
622 let begins = |part: usize| -> Result<FloatType> {
623 let id = parts
624 .get(part)
625 .ok_or_else(|| audio_error(format!("no part {part}")))?
626 .0;
627 beginning_data
628 .get(id)
629 .copied()
630 .ok_or_else(|| audio_error(format!("no beginning recorded for part {part}")))
631 };
632 let mut position = 0;
633 let mut i = 0;
634 while i < parts.len() && begins(i)? < note_prediction {
635 i += 1;
636 position = i;
637 }
638 if parts.len() == 1 {
639 position = 0;
640 }
641 let first = beginning_data
642 .first()
643 .copied()
644 .ok_or_else(|| audio_error("no beginnings recorded"))?;
645 let mut distance = (first - note_prediction).abs();
646 let best = parts.first().ok_or_else(|| audio_error("no parts"))?.1;
647 for (index, (_, probability)) in parts.iter().enumerate() {
648 let start = begins(index)?;
649 if *probability >= 0.9 * best
650 && start > last_note_position
651 && (start - note_prediction).abs() < distance
652 {
653 distance = (start - note_prediction).abs();
654 position = index;
655 }
656 }
657 let start = begins(position)?;
658 let probability = parts[position].1;
659 let doubtful = probability < 0.6
660 || parts.len() == 1
661 || (distance > 20.0 && countdown == 0)
662 || (distance > 30.0 && countdown == 1)
663 || page.is_some_and(|(first, last)| (start < first || start > last) && countdown < 2);
664 Ok((position, if doubtful { countdown + 1 } else { 0 }))
665}
666
667fn wav_samples(bytes: &[u8]) -> Result<(Vec<u8>, usize, usize)> {
670 let word = |at: usize| -> Result<u32> {
671 bytes
672 .get(at..at + 4)
673 .map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
674 .ok_or_else(|| audio_error("file does not start with RIFF id"))
675 };
676 if bytes.get(..4) != Some(b"RIFF") || bytes.get(8..12) != Some(b"WAVE") {
677 return Err(audio_error("file does not start with RIFF id"));
678 }
679 let mut at = 12;
680 let mut frame_size = None;
681 while at + 8 <= bytes.len() {
682 let id = &bytes[at..at + 4];
683 let size = word(at + 4)? as usize;
684 let body = at + 8;
685 let end = (body + size).min(bytes.len());
686 match id {
687 b"fmt " => {
688 let field = |offset: usize| -> Result<u16> {
689 bytes
690 .get(body + offset..body + offset + 2)
691 .map(|b| u16::from_le_bytes([b[0], b[1]]))
692 .ok_or_else(|| audio_error("fmt chunk too short"))
693 };
694 let format = field(0)?;
695 if format != 1 && format != 0xFFFE {
696 return Err(audio_error(format!("unknown format: {format}")));
697 }
698 let channels = usize::from(field(2)?);
699 let width = usize::from(field(14)?).div_ceil(8);
700 if channels == 0 || width == 0 {
701 return Err(audio_error("bad # of channels or sample width"));
702 }
703 frame_size = Some(channels * width);
704 }
705 b"data" => {
706 let frame_size =
707 frame_size.ok_or_else(|| audio_error("data chunk before fmt chunk"))?;
708 let data = bytes[body..end].to_vec();
709 let frames = size / frame_size;
710 return Ok((data, frames, frame_size));
711 }
712 _ => {}
713 }
714 at = body + size + (size & 1);
715 }
716 Err(audio_error("fmt chunk and/or data chunk missing"))
717}
718
719pub fn frequencies_from_wav(bytes: &[u8]) -> Result<Vec<FloatType>> {
729 let (data, frames, frame_size) = wav_samples(bytes)?;
730 let chunk_bytes = AUDIO_CHUNK_LENGTH * frame_size;
731 (0..frames / AUDIO_CHUNK_LENGTH)
732 .map(|chunk| {
733 let samples: Vec<FloatType> = data[chunk * chunk_bytes..(chunk + 1) * chunk_bytes]
734 .as_chunks::<2>()
735 .0
736 .iter()
737 .map(|pair| FloatType::from(i16::from_le_bytes(*pair)))
738 .collect();
739 autocorrelation_frequency(&samples, RECORD_SAMPLE_RATE)
740 })
741 .collect()
742}
743
744pub fn monophonic_stream_from_wav(bytes: &[u8], scale: Option<&Scale>) -> Result<Stream> {
757 let frequencies = frequencies_from_wav(bytes)?;
758 let detected = detect_pitch_frequencies(&frequencies, scale)?;
759 let smoothed: Vec<FloatType> = smooth_frequencies(&detected, 7)?
760 .into_iter()
761 .map(|value| value as FloatType)
762 .collect();
763 let (pitches, _) = pitch_frequencies_to_objects(&smoothed, scale)?;
764 let (notes, lengths) = join_consecutive_identical_pitches(pitches)?;
765 let (score, _) = notes_and_durations_to_stream(¬es, &lengths, None, true)?;
766 score
767 .parts()
768 .first()
769 .map(|part| (*part).clone())
770 .ok_or_else(|| audio_error("no part"))
771}