Skip to content

Commit

Permalink
Improve code coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
twitchax committed Jan 12, 2023
1 parent 831538b commit 772e8df
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 89 deletions.
4 changes: 2 additions & 2 deletions chord.pest
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ hat = { "^" }

bang = { "!" }

WHITESPACE = _{ " " | "(" | ")" }
WHITESPACE = _{ " " }

chord = {
SOI ~
note ~
(maj7_modifier | minor | augmented | diminished | half_diminished)? ~
(maj7_modifier | dominant_modifier)? ~
modifier* ~
("("* ~ modifier ~ ")"*)* ~
(slash ~ note)? ~
(at ~ digit)? ~
(hat ~ digit)? ~
Expand Down
27 changes: 27 additions & 0 deletions src/chord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,7 @@ impl Parsable for Chord {

#[cfg(feature = "audio")]
impl Playable for Chord {
#[no_coverage]
fn play(&self, delay: f32, length: f32, fade_in: f32) -> Res<PlaybackHandle> {
use rodio::{Sink, OutputStream, source::SineWave, Source};

Expand Down Expand Up @@ -1237,6 +1238,10 @@ mod tests {
#[test]
fn test_text() {
assert_eq!(Chord::new(C).flat9().sharp9().sharp11().add13().with_slash(E).name(), "C(♭9)(♯9)(♯11)(add13)/E");
assert_eq!(Chord::new(C).flat5().name(), "C(♭5)");
assert_eq!(Chord::new(C).minor().augmented().name(), "Cm(♯5)");
assert_eq!(Chord::new(C).with_octave(Octave::Six).precise_name(), "C@6");

assert_eq!(format!("{}", Chord::new(C).minor().seven().flat_five()), "Cm7(♭5)\n half diminished, locrian, minor seven flat five, seventh mode of major scale, major scale starting one half step up\n C, D, E♭, F, G♭, A♭, B♭\n C, E♭, G♭, B♭");
}

Expand Down Expand Up @@ -1395,11 +1400,33 @@ mod tests {
assert_eq!(Chord::parse("C7b9#11").unwrap().chord(), vec![C, E, G, BFlat, DFlatFive, FSharpFive]);
assert_eq!(Chord::parse("C(add6)").unwrap().chord(), vec![C, E, G, A]);
assert_eq!(Chord::parse("Em(#5)").unwrap().chord(), vec![E, G, BSharp]);
assert_eq!(Chord::parse("D+11").unwrap().chord(), vec![D, FSharp, ASharp, CFive, EFive, GFive]);
assert_eq!(Chord::parse("Dm13b5").unwrap().chord(), vec![D, F, AFlat, CFive, EFive, GFive, BFive]);
assert_eq!(Chord::parse("Dsus2").unwrap().chord(), vec![D, E, A]);
assert_eq!(Chord::parse("Dsus4").unwrap().chord(), vec![D, G, A]);
assert_eq!(Chord::parse("Dadd2").unwrap().chord(), vec![D, E, FSharp, A]);
assert_eq!(Chord::parse("Dadd4").unwrap().chord(), vec![D, FSharp, G, A]);
assert_eq!(Chord::parse("Dadd9").unwrap().chord(), vec![D, FSharp, A, EFive]);
assert_eq!(Chord::parse("Dadd11").unwrap().chord(), vec![D, FSharp, A, GFive]);
assert_eq!(Chord::parse("Dadd13").unwrap().chord(), vec![D, FSharp, A, BFive]);
assert_eq!(Chord::parse("Dm#9").unwrap().chord(), vec![D, F, A, ESharpFive]);
assert_eq!(Chord::parse("Dmb11").unwrap().chord(), vec![D, F, A, GFlatFive]);
assert_eq!(Chord::parse("D(b13)").unwrap().chord(), vec![D, FSharp, A, BFlatFive]);
assert_eq!(Chord::parse("D(#13)").unwrap().chord(), vec![D, FSharp, A, BSharpFive]);
}

#[test]
fn test_guess() {
assert_eq!(Chord::from_notes(&[EThree, C, EFlat, FSharp, ASharp, DFive]).unwrap().first().unwrap().chord(), Chord::parse("Cm9b5/E").unwrap().chord());
assert_eq!(Chord::from_notes(&[C, E, G]).unwrap().first().unwrap().chord(), Chord::parse("C").unwrap().chord());
assert_eq!(Chord::from_notes(&[C, E, G, BFlat, DFive, FFive]).unwrap().first().unwrap().chord(), Chord::parse("C11").unwrap().chord());
assert_eq!(Chord::from_notes(&[C, E, G, BFlat, DFive, FFive, AFive]).unwrap().first().unwrap().chord(), Chord::parse("C13").unwrap().chord());
assert_eq!(Chord::from_notes(&[C, EFlat, GFlat, A]).unwrap().first().unwrap().chord(), Chord::parse("Cdim").unwrap().chord());
}

#[test]
#[should_panic(expected = "Must have at least three notes to guess a chord.")]
fn test_chord_from_notes_failure() {
Chord::from_notes(&[C, E]).unwrap();
}
}
63 changes: 0 additions & 63 deletions src/interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,69 +215,6 @@ impl HasOctave for Interval {
}
}

impl CanReduceFrame for Interval {
fn reduce_frame(self) -> Self {
match self {
Interval::PerfectUnison => Interval::PerfectUnison,
Interval::DiminishedSecond => Interval::DiminishedSecond,

Interval::AugmentedUnison => Interval::AugmentedUnison,
Interval::MinorSecond => Interval::MinorSecond,

Interval::MajorSecond => Interval::MajorSecond,
Interval::DiminishedThird => Interval::DiminishedThird,

Interval::AugmentedSecond => Interval::AugmentedSecond,
Interval::MinorThird => Interval::MinorThird,

Interval::MajorThird => Interval::MajorThird,
Interval::DiminishedFourth => Interval::DiminishedFourth,

Interval::AugmentedThird => Interval::AugmentedThird,
Interval::PerfectFourth => Interval::PerfectFourth,

Interval::AugmentedFourth => Interval::AugmentedFourth,
Interval::DiminishedFifth => Interval::DiminishedFifth,

Interval::PerfectFifth => Interval::PerfectFifth,
Interval::DiminishedSixth => Interval::DiminishedSixth,

Interval::AugmentedFifth => Interval::AugmentedFifth,
Interval::MinorSixth => Interval::MinorSixth,

Interval::MajorSixth => Interval::MajorSixth,
Interval::DiminishedSeventh => Interval::DiminishedSeventh,

Interval::AugmentedSixth => Interval::AugmentedSixth,
Interval::MinorSeventh => Interval::MinorSeventh,

Interval::MajorSeventh => Interval::MajorSeventh,
Interval::DiminishedOctave => Interval::DiminishedOctave,

Interval::AugmentedSeventh => Interval::AugmentedSeventh,
Interval::PerfectOctave => Interval::PerfectOctave,

Interval::MinorNinth => Interval::MinorSecond,
Interval::MajorNinth => Interval::MajorSecond,
Interval::AugmentedNinth => Interval::AugmentedSecond,

Interval::DiminishedEleventh => Interval::DiminishedFourth,
Interval::PerfectEleventh => Interval::PerfectFourth,
Interval::AugmentedEleventh => Interval::AugmentedFourth,

Interval::MinorThirteenth => Interval::MinorSixth,
Interval::MajorThirteenth => Interval::MajorSixth,
Interval::AugmentedThirteenth => Interval::AugmentedSixth,

Interval::PerfectOctaveAndPerfectFifth => Interval::PerfectFifth,
Interval::TwoPerfectOctaves => Interval::PerfectOctave,
Interval::TwoPerfectOctavesAndMajorThird => Interval::MajorThird,
Interval::TwoPerfectOctavesAndPerfectFifth => Interval::PerfectFifth,
Interval::TwoPerfectOctavesAndMinorSeventh => Interval::MinorSeventh,
}
}
}

// Statics.

pub(crate) static PRIMARY_HARMONIC_SERIES: Lazy<[Interval; 6]> = Lazy::new(|| [
Expand Down
45 changes: 37 additions & 8 deletions src/listen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use std::{collections::HashMap, ops::Deref};

use rustfft::{FftPlanner, num_complex::{Complex, ComplexFloat}};

use crate::{note::{ALL_PITCH_NOTES_WITH_FREQUENCY}};
use crate::{note::{ALL_PITCH_NOTES_WITH_FREQUENCY, HasPrimaryHarmonicSeries}};

use crate::{base::Res, note::Note, interval::PRIMARY_HARMONIC_SERIES, pitch::HasFrequency};
use crate::{base::Res, note::Note, pitch::HasFrequency};

#[no_coverage]
pub async fn get_notes_from_microphone(length_in_seconds: u8) -> Res<Vec<Note>> {
if length_in_seconds < 1 {
return Err(anyhow::Error::msg("Listening length in seconds must be greater than 1."));
Expand Down Expand Up @@ -174,8 +175,8 @@ fn reduce_notes_by_harmonic_series(notes: &[(Note, f32)]) -> Vec<Note> {
while j < working_set.len() {
let other_note = working_set[j].0;

for interval in PRIMARY_HARMONIC_SERIES.iter() {
if (note + *interval).frequency() == other_note.frequency() {
for harmonic in note.primary_harmonic_series() {
if harmonic.frequency() == other_note.frequency() {
working_set[k].1 += working_set[j].1;
working_set.remove(j);
j -= 1;
Expand Down Expand Up @@ -225,10 +226,6 @@ fn translate_frequency_space_to_peak_space(frequency_space: &[(f32, f32)]) -> Ve
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or_default();

if max_in_window == 0.0 {
panic!("max_in_window is 0.0! This should never happen. k: {}, window_size: {}, frequency_space[k].0: {}, frequency_space[k].1: {}", k, window_size, frequency_space[k].0, frequency_space[k].1);
}

peak_space[k] = (peak_space[k].0, peak_space[k].1);

let mut next = 0;
Expand Down Expand Up @@ -339,4 +336,36 @@ fn plot_frequency_space(frequency_space: &[(f32, f32)], name: &'static str, x_mi
chart.configure_mesh().draw().unwrap();

chart.draw_series(LineSeries::new(normalized_frequency_space.iter().map(|(x, y)| (**x, *y)), RED)).unwrap();
}

// Tests.

#[cfg(test)]
mod tests {
use std::{fs::File, io::Read};

use crate::{chord::Chord, base::Parsable, note::Note};

#[test]
fn test_listen() {
let mut file = File::open("tests/vec.bin").unwrap();
let file_size = file.metadata().unwrap().len() as usize;
let float_size = std::mem::size_of::<f32>();
let element_count = file_size / float_size;
let mut buffer = vec![0u8; file_size];

// Read the contents of the file into the buffer
file.read_exact(&mut buffer).unwrap();

// Convert the buffer to a vector of f32
let data: Vec<f32> = unsafe {
std::slice::from_raw_parts(buffer.as_ptr() as *const f32, element_count).to_vec()
};

let notes = Note::from_audio(&data, 5).unwrap();

let chord = Chord::from_notes(&notes).unwrap();

assert_eq!(chord[0], Chord::parse("C7b9").unwrap());
}
}
43 changes: 29 additions & 14 deletions src/named_pitch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,20 +299,7 @@ impl Add<i8> for NamedPitch {

impl From<Pitch> for NamedPitch {
fn from(pitch: Pitch) -> Self {
match pitch {
Pitch::C => NamedPitch::C,
Pitch::CSharp => NamedPitch::CSharp,
Pitch::D => NamedPitch::D,
Pitch::DSharp => NamedPitch::DSharp,
Pitch::E => NamedPitch::E,
Pitch::F => NamedPitch::F,
Pitch::FSharp => NamedPitch::FSharp,
Pitch::G => NamedPitch::G,
Pitch::GSharp => NamedPitch::GSharp,
Pitch::A => NamedPitch::A,
Pitch::ASharp => NamedPitch::ASharp,
Pitch::B => NamedPitch::B,
}
NamedPitch::from(&pitch)
}
}

Expand Down Expand Up @@ -412,4 +399,32 @@ mod tests {
fn test_properties() {
assert_eq!(NamedPitch::A.named_pitch(), NamedPitch::A);
}

#[test]
fn test_pitch_conversion() {
assert_eq!(NamedPitch::from(Pitch::C), NamedPitch::C);
assert_eq!(NamedPitch::from(&Pitch::C), NamedPitch::C);
assert_eq!(NamedPitch::from(Pitch::CSharp), NamedPitch::CSharp);
assert_eq!(NamedPitch::from(&Pitch::CSharp), NamedPitch::CSharp);
assert_eq!(NamedPitch::from(Pitch::D), NamedPitch::D);
assert_eq!(NamedPitch::from(&Pitch::D), NamedPitch::D);
assert_eq!(NamedPitch::from(Pitch::DSharp), NamedPitch::DSharp);
assert_eq!(NamedPitch::from(&Pitch::DSharp), NamedPitch::DSharp);
assert_eq!(NamedPitch::from(Pitch::E), NamedPitch::E);
assert_eq!(NamedPitch::from(&Pitch::E), NamedPitch::E);
assert_eq!(NamedPitch::from(Pitch::F), NamedPitch::F);
assert_eq!(NamedPitch::from(&Pitch::F), NamedPitch::F);
assert_eq!(NamedPitch::from(Pitch::FSharp), NamedPitch::FSharp);
assert_eq!(NamedPitch::from(&Pitch::FSharp), NamedPitch::FSharp);
assert_eq!(NamedPitch::from(Pitch::G), NamedPitch::G);
assert_eq!(NamedPitch::from(&Pitch::G), NamedPitch::G);
assert_eq!(NamedPitch::from(Pitch::GSharp), NamedPitch::GSharp);
assert_eq!(NamedPitch::from(&Pitch::GSharp), NamedPitch::GSharp);
assert_eq!(NamedPitch::from(Pitch::A), NamedPitch::A);
assert_eq!(NamedPitch::from(&Pitch::A), NamedPitch::A);
assert_eq!(NamedPitch::from(Pitch::ASharp), NamedPitch::ASharp);
assert_eq!(NamedPitch::from(&Pitch::ASharp), NamedPitch::ASharp);
assert_eq!(NamedPitch::from(Pitch::B), NamedPitch::B);
assert_eq!(NamedPitch::from(&Pitch::B), NamedPitch::B);
}
}
24 changes: 22 additions & 2 deletions src/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{ops::{Add, AddAssign}, cmp::Ordering, fmt::{Display, Formatter, self}}
use once_cell::sync::Lazy;
use paste::paste;
use pest::Parser;
use crate::{named_pitch::{NamedPitch, HasNamedPitch}, interval::{Interval, HasEnharmonicDistance}, base::{HasStaticName, HasName, Parsable, Res}, chord::Chord, pitch::{HasFrequency, HasBaseFrequency, Pitch, HasPitch, ALL_PITCHES}, octave::{Octave, HasOctave, ALL_OCTAVES}, parser::{ChordParser, Rule, note_str_to_note, octave_str_to_octave}, listen::{get_notes_from_microphone, get_notes_from_audio_data}};
use crate::{named_pitch::{NamedPitch, HasNamedPitch}, interval::{Interval, HasEnharmonicDistance, PRIMARY_HARMONIC_SERIES}, base::{HasStaticName, HasName, Parsable, Res}, chord::Chord, pitch::{HasFrequency, HasBaseFrequency, Pitch, HasPitch, ALL_PITCHES}, octave::{Octave, HasOctave, ALL_OCTAVES}, parser::{ChordParser, Rule, note_str_to_note, octave_str_to_octave}, listen::{get_notes_from_microphone, get_notes_from_audio_data}};

// Macros.

Expand Down Expand Up @@ -99,6 +99,12 @@ pub trait NoteRecreator {
fn with_octave(self, octave: Octave) -> Self;
}

/// A trait which allows for obtaining the primary harmonic series of the note.
pub trait HasPrimaryHarmonicSeries {
/// Returns the primary harmonic series of the note.
fn primary_harmonic_series(self) -> Vec<Note>;
}

// Struct.

/// A note type.
Expand Down Expand Up @@ -130,6 +136,7 @@ impl Note {
/// to identify the notes in the recorded audio.
///
/// Currently, this does not work with WASM.
#[no_coverage]
pub async fn from_listen(length_in_seconds: u8) -> Res<Vec<Self>> {
get_notes_from_microphone(length_in_seconds).await
}
Expand Down Expand Up @@ -232,6 +239,12 @@ impl NoteRecreator for Note {
}
}

impl HasPrimaryHarmonicSeries for Note {
fn primary_harmonic_series(self) -> Vec<Self> {
PRIMARY_HARMONIC_SERIES.iter().map(|interval| self + *interval).collect()
}
}

impl Add<Interval> for Note {
type Output = Self;

Expand Down Expand Up @@ -389,8 +402,9 @@ mod tests {
use pretty_assertions::{assert_eq};

#[test]
fn test_name() {
fn test_text() {
assert_eq!(CFlat.static_name(), "C♭");
assert_eq!(C.to_string(), "C4");
}

#[test]
Expand Down Expand Up @@ -476,5 +490,11 @@ mod tests {
assert_eq!(CFlatFour.frequency(), BThree.frequency());
assert_eq!(BSharp.frequency(), CFive.frequency());
assert_eq!(DTripleFlatFive.frequency(), B.frequency());
assert_eq!(BDoubleSharpFive.with_named_pitch(NamedPitch::A).frequency(), AFive.frequency());
}

#[test]
fn test_harmonics() {
assert_eq!(C.primary_harmonic_series(), vec![CFive, GFive, CSix, ESix, GSix, BFlatSix]);
}
}
5 changes: 5 additions & 0 deletions src/octave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,9 @@ mod tests {
assert_eq!(Octave::Four.octave(), Octave::Four);
assert_eq!(Octave::default(), Octave::Four);
}

#[test]
fn test_names() {
assert_eq!(ALL_OCTAVES.map(|o| o.static_name()).join(" "), "0 1 2 3 4 5 6 7 8 9 10");
}
}
Binary file added tests/vec.bin
Binary file not shown.

0 comments on commit 772e8df

Please sign in to comment.