Skip to main content

music21/
audiosearch.rs

1//! Finding the notes in a recording: music21's `audioSearch`.
2//!
3//! A recording is cut into chunks of [`AUDIO_CHUNK_LENGTH`] frames; each
4//! chunk's frequency is found by autocorrelation
5//! ([`autocorrelation_frequency`]) and snapped to the nearest pitch of a
6//! scale ([`detect_pitch_frequencies`]). The frequencies are smoothed
7//! ([`smooth_frequencies`]), turned into pitches whose octaves are evened
8//! out ([`pitch_frequencies_to_objects`]), and runs of one pitch long
9//! enough to be a note become notes and rests
10//! ([`join_consecutive_identical_pitches`]), whose lengths are quantized
11//! against the most common length ([`notes_and_durations_to_stream`]).
12//! [`monophonic_stream_from_wav`] does all of it, as music21's
13//! `transcriber.monophonicStreamFromFile` does.
14//!
15//! As music21 does, every recording is taken to be sampled at
16//! [`RECORD_SAMPLE_RATE`], whatever its file says, and its samples are read
17//! as 16-bit numbers. music21's recording from a microphone and its score
18//! follower are not here.
19
20use 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
30/// How many frames of a recording each frequency is found from: music21's
31/// `audioChunkLength`.
32pub const AUDIO_CHUNK_LENGTH: usize = 1024;
33
34/// The sample rate every recording is taken to have: music21's
35/// `recordSampleRate`.
36pub const RECORD_SAMPLE_RATE: FloatType = 44100.0;
37
38/// The frequency standing for a rest.
39pub 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
52/// How many of `data` fall in each of `bins` equal bins from its least to
53/// its greatest, and the bins' edges: music21's `histogram`. A value on an
54/// edge counts in the bin below it.
55///
56/// ```
57/// use music21::audiosearch::histogram;
58///
59/// let (counts, edges) = histogram(&[1.0, 2.0, 2.0, 3.0, 5.0], 4)?;
60/// assert_eq!(counts, [3, 1, 0, 1]);
61/// assert_eq!(edges, [1.0, 2.0, 3.0, 4.0, 5.0]);
62/// # Ok::<(), music21::Error>(())
63/// ```
64///
65/// # Errors
66///
67/// No data, or no bins.
68pub 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
92/// Where between three points about a peak the top of the parabola
93/// through them lies: music21's `interpolation`. A peak at either end
94/// is taken as it is.
95pub 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
107/// The frequency a signal sounds, found by autocorrelation: music21's
108/// `autocorrelationFunction`. The signal's correlation with itself at
109/// each lag is taken; past the first lag where it starts to rise, its
110/// highest point, placed between samples by [`interpolation`], is the
111/// period. A signal whose correlation never rises is a rest, frequency
112/// ten.
113///
114/// music21 correlates with scipy's FFT convolution, which rounds; the
115/// crate sums the products exactly, so the two can differ in the last
116/// bits.
117///
118/// ```
119/// use music21::audiosearch::autocorrelation_frequency;
120///
121/// let rate = 44100.0;
122/// let signal: Vec<f64> = (0..1024)
123///     .map(|i| (2.0 * std::f64::consts::PI * 441.0 * i as f64 / rate).sin() * 10000.0)
124///     .collect();
125/// let frequency = autocorrelation_frequency(&signal, rate)?;
126/// // music21 finds 442.2033...: the interpolated peak sits a little early.
127/// assert!((frequency - 442.2033).abs() < 1e-3);
128/// # Ok::<(), music21::Error>(())
129/// ```
130///
131/// # Errors
132///
133/// An empty signal.
134pub 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    // numpy's argmax: the first of equal highs.
158    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
173/// The thresholds between a scale's pitches, as fractions of an octave
174/// above the octave's C, and the pitches: music21's `prepareThresholds`.
175/// The scale is the chromatic scale on C4 where none is given.
176///
177/// # Errors
178///
179/// A scale whose pitches cannot be worked out.
180pub 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
200/// The scale pitch a frequency falls nearest, put in the octave it sounds
201/// in, and the frequency of that pitch as the scale has it: music21's
202/// `normalizeInputFrequency`, with thresholds and pitches from
203/// [`prepare_thresholds`].
204///
205/// # Errors
206///
207/// A frequency that is not a finite number above nought, or no pitches.
208pub 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
235/// Each frequency's nearest pitch of `scale` (C major on C4 where none is
236/// given), and the frequency of that pitch in the octave it sounds:
237/// music21's `detectPitchFrequencies`.
238///
239/// # Errors
240///
241/// As [`normalize_input_frequency`].
242pub 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
259/// Each frequency averaged with its neighbours, `smooth_levels` at a time,
260/// and rounded to a whole number: music21's `smoothFrequencies`. As in
261/// music21, each average takes in those already made before it, and the
262/// first and last few frequencies are the truncated average of the first
263/// and last `smooth_levels`.
264///
265/// ```
266/// use music21::audiosearch::smooth_frequencies;
267///
268/// let smoothed = smooth_frequencies(&[100.0, 100.0, 400.0, 100.0, 100.0], 3)?;
269/// // Each average takes in the ones made before it.
270/// assert_eq!(smoothed, [200, 233, 244, 200, 200]);
271/// # Ok::<(), music21::Error>(())
272/// ```
273///
274/// # Errors
275///
276/// No levels, or more levels than frequencies.
277pub fn smooth_frequencies(frequencies: &[FloatType], smooth_levels: usize) -> Result<Vec<i64>> {
278    // music21 raises a `ValueError` for both of these rather than its own
279    // class: they are arguments that were never values, not music that
280    // cannot be read.
281    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
317/// The pitches of a list of frequencies, each the nearest pitch of `scale`
318/// (C major on C4 where none is given), with each run of one name put in
319/// the run's average octave, and the frequencies so placed: music21's
320/// `pitchFrequenciesToObjects`.
321///
322/// As in music21, a run's octave goes to the pitches from the one before
323/// it to the one before its last, the very first run's to the last pitch,
324/// and the last pitch is never counted in a run.
325///
326/// # Errors
327///
328/// As [`normalize_input_frequency`].
329pub 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            // Python's index one before, the last for the first.
353            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
361/// The pitch music21 makes by setting a pitch's frequency.
362fn 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/// One note or rest a run of pitches makes: the by-position twin of
368/// [`join_consecutive_identical_pitches`].
369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
370#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
371pub struct JoinedNote {
372    /// Which of the pitches it sounds, or none where it is a rest.
373    pub pitch: Option<usize>,
374    /// How many pitches it stands for.
375    pub length: usize,
376}
377
378/// What each run of one pitch makes, as the places of the pitches rather
379/// than as notes: the by-position twin of
380/// [`join_consecutive_identical_pitches`].
381///
382/// music21 hands the note it makes the very `Pitch` object of the run's
383/// last pitch, so a caller holding those objects wants to know which.
384///
385/// # Errors
386///
387/// No pitches.
388pub 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
428/// Notes and rests from runs of one pitch, and how many pitches each
429/// lasted: music21's `joinConsecutiveIdenticalPitches`. A run of six or
430/// more is a note of its last pitch; fifteen or more pitches in shorter
431/// runs before a note make a rest. The first pitch is taken as a rest's,
432/// and as in music21 the first pitch after each run is passed over.
433///
434/// # Errors
435///
436/// No pitches.
437pub 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
456/// A length, in quarters, snapped to a sixteenth, eighth, quarter, dotted
457/// quarter, half or whole: music21's `quantizeDuration`.
458pub 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
470/// How many pitches a quarter note lasts, from how many each note lasted:
471/// the middle of the last of the fullest bins of an eight-bin
472/// [`histogram`] of the lengths and nought, taken as
473/// `most_repeated_quarter_length` (a quarter where nought): music21's
474/// `quarterLengthEstimation`.
475///
476/// # Errors
477///
478/// As [`histogram`].
479pub 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/// What one note of a transcription is given: the by-position twin of
501/// [`notes_and_durations_to_stream`].
502#[derive(Clone, Copy, Debug, PartialEq)]
503#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
504pub struct TranscribedNote {
505    /// Which of the notes given it is.
506    pub note: usize,
507    /// How long it is to last, in quarters.
508    pub quarter_length: FloatType,
509    /// Whether it goes into the part: a rest before the first note is
510    /// left out where the caller asked for that.
511    pub kept: bool,
512}
513
514/// How long each note of a transcription is to last and which of them go
515/// into the part, and the quarter length estimated: the by-position twin
516/// of [`notes_and_durations_to_stream`].
517///
518/// music21 writes the length onto *every* note it is given, the rests it
519/// then leaves out included, which is why each note is answered for
520/// whether it is kept or not.
521///
522/// # Errors
523///
524/// Fewer notes than lengths, or no lengths.
525pub 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
558/// A score of one part holding `notes`, each as long as its length in
559/// [`quantize_duration`]d quarters of the estimated quarter length, and that
560/// estimate: music21's `notesAndDurationsToStream`. The estimate is
561/// [`quarter_length_estimation`] against the most common note length of
562/// `score_notes` where given, or against a quarter. With
563/// `remove_rests_at_beginning`, rests before the first note are left out.
564/// The score is titled `Automatic Music21 Transcription`.
565///
566/// # Errors
567///
568/// Fewer notes than lengths, or no lengths.
569pub 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
601/// Which of the parts a score follower is weighing to take as where the
602/// player is, and the count of doubtful guesses so far: music21's
603/// `decisionProcess`. Each part is its index into `beginning_data`, where
604/// it begins, and how well it matched; parts are in order of how well they
605/// matched. Of the parts matching within nine tenths of the best and
606/// beginning after `last_note_position`, the one beginning nearest
607/// `note_prediction` is taken; the countdown grows when the guess is
608/// doubtful and is otherwise reset.
609///
610/// # Errors
611///
612/// No parts, a part beginning where `beginning_data` has nothing, or every
613/// part beginning before the prediction with none taken instead.
614pub 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
667/// The 16-bit samples of a WAV file and how many frames it holds, read as
668/// Python's `wave` module reads an uncompressed one.
669fn 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
719/// The frequency of each whole chunk of [`AUDIO_CHUNK_LENGTH`] frames of a
720/// WAV file, found by [`autocorrelation_frequency`]: music21's
721/// `getFrequenciesFromAudioFile`. Each chunk's bytes are read as 16-bit
722/// samples, every channel's in turn, and taken to be sampled at
723/// [`RECORD_SAMPLE_RATE`]; frames after the last whole chunk are left out.
724///
725/// # Errors
726///
727/// A file that is not an uncompressed WAV file.
728pub 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
744/// The notes of a recording of one line, as a part: music21's
745/// `transcriber.monophonicStreamFromFile`, which runs
746/// [`frequencies_from_wav`], [`detect_pitch_frequencies`],
747/// [`smooth_frequencies`] by seven, [`pitch_frequencies_to_objects`],
748/// [`join_consecutive_identical_pitches`] and
749/// [`notes_and_durations_to_stream`], rests at the beginning left out.
750/// Pitches are taken from `scale`, C major on C4 where none is given.
751///
752/// # Errors
753///
754/// A file that is not an uncompressed WAV file, or one too short to
755/// smooth or to hold a note.
756pub 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(&notes, &lengths, None, true)?;
766    score
767        .parts()
768        .first()
769        .map(|part| (*part).clone())
770        .ok_or_else(|| audio_error("no part"))
771}