diff --git a/src/bin.rs b/src/bin.rs index 42f79bb..2c93f6c 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -33,6 +33,10 @@ enum Command { #[arg(short, long, default_value_t = 4i8)] octave: i8, + /// Sets the inversion of the chord + #[arg(short, long, default_value_t = 0u8)] + inversion: u8, + /// Sets the delay between notes (in seconds) #[arg(short, long, default_value_t = 0.2f32)] delay: f32, @@ -40,6 +44,10 @@ enum Command { /// Sets the duration of play (in seconds) #[arg(short, long, default_value_t = 3.0f32)] length: f32, + + /// Fade in duration (in seconds) + #[arg(short, long, default_value_t = 0.1f32)] + fade_in: f32, }, } @@ -58,10 +66,10 @@ fn start(args: Args) -> Void { describe(&chord); } - Some(Command::Play { symbol, octave, delay, length }) => { - let chord = Chord::parse(&symbol)?.with_octave(Octave::Zero + octave); + Some(Command::Play { symbol, octave, inversion, delay, length, fade_in }) => { + let chord = Chord::parse(&symbol)?.with_octave(Octave::Zero + octave).with_inversion(inversion); - play(&chord, delay, length); + play(&chord, delay, length, fade_in); } None => { println!("No command given."); @@ -74,7 +82,7 @@ fn describe(chord: &Chord) { println!("{}", chord); } -fn play(chord: &Chord, delay: f32, length: f32) { +fn play(chord: &Chord, delay: f32, length: f32, fade_in: f32) { describe(chord); let (_stream, stream_handle) = OutputStream::try_default().unwrap(); @@ -87,6 +95,7 @@ fn play(chord: &Chord, delay: f32, length: f32) { let source = SineWave::new(n.frequency()) .take_duration(Duration::from_secs_f32(length - k as f32 * delay)) .delay(Duration::from_secs_f32(k as f32 * delay)) + .fade_in(Duration::from_secs_f32(fade_in)) .amplify(0.20); sink.append(source); diff --git a/src/chord.rs b/src/chord.rs index 29140d2..0e47a49 100644 --- a/src/chord.rs +++ b/src/chord.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, fmt::Display}; use pest::Parser; -use crate::{note::{Note, CZero}, modifier::{Modifier, Extension, Degree, HasIsDominant}, known_chord::{KnownChord, HasRelativeChord, HasRelativeScale}, interval::Interval, base::{HasDescription, HasName, HasStaticName, Res}, parser::{ChordParser, Rule, note_str_to_note}, octave::{Octave}, named_pitch::HasNamedPitch}; +use crate::{note::{Note, CZero, NoteRecreator}, modifier::{Modifier, Extension, Degree, HasIsDominant}, known_chord::{KnownChord, HasRelativeChord, HasRelativeScale}, interval::Interval, base::{HasDescription, HasName, HasStaticName, Res}, parser::{ChordParser, Rule, note_str_to_note}, octave::{Octave, HasOctave}, named_pitch::HasNamedPitch}; // Traits. @@ -767,7 +767,16 @@ impl HasChord for Chord { } // Add slash note. - if let Some(slash) = self.slash { + if let Some(mut slash) = self.slash { + // Fix slash note (it should be less than, or equal to, one octave away from the bottom tone). + let bottom = *result.first().unwrap_or(&CZero); + let floor = Note::new(bottom.named_pitch(), bottom.octave() - 1); + + slash = slash.with_octave(Octave::Zero); + while slash < floor { + slash += Interval::PerfectOctave; + } + result.insert(0, slash); } @@ -1050,12 +1059,14 @@ mod tests { // Slashes. - assert_eq!(Chord::new(C).with_slash(D).chord(), vec![D, C, E, G]); + assert_eq!(Chord::new(C).with_slash(D).chord(), vec![DThree, C, E, G]); + assert_eq!(Chord::new(CFive).with_slash(D).chord(), vec![DFour, CFive, EFive, GFive]); // Inversions. assert_eq!(C.into_chord().with_inversion(1).chord(), vec![E, G, CFive]); assert_eq!(C.into_chord().with_inversion(2).chord(), vec![G, CFive, EFive]); + assert_eq!(C.into_chord().maj7().with_inversion(3).chord(), vec![B, CFive, EFive, GFive]); } diff --git a/src/note.rs b/src/note.rs index 32ab6a9..1bc9166 100644 --- a/src/note.rs +++ b/src/note.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] #![allow(non_upper_case_globals)] -use std::ops::{Add, AddAssign}; +use std::{ops::{Add, AddAssign}, cmp::Ordering}; use paste::paste; use crate::{named_pitch::{NamedPitch, HasNamedPitch}, interval::{Interval, HasEnharmonicDistance}, base::HasStaticName, chord::Chord, pitch::{HasFrequency, HasBaseFrequency, Pitch, HasPitch}, octave::{Octave, HasOctave}}; @@ -89,13 +89,21 @@ pub trait IntoChord { fn into_chord(self) -> Chord; } +/// A trait which allows for a [`Note`] to be recreated with different properties. +pub trait NoteRecreator { + /// Recreates this [`Note`] with the given [`NamedPitch`]. + fn with_named_pitch(self, named_pitch: NamedPitch) -> Self; + /// Recreates this [`Note`] with the given [`Octave`]. + fn with_octave(self, octave: Octave) -> Self; +} + // Struct. /// A note type. /// /// This is a named pitch with an octave. This type allows for correctly attributing octave changes /// across an interval from one [`Note`] to another. -#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)] pub struct Note { /// The octave of the note. octave: Octave, @@ -161,6 +169,16 @@ impl IntoChord for Note { } } +impl NoteRecreator for Note { + fn with_named_pitch(self, named_pitch: NamedPitch) -> Self { + Self::new(named_pitch, self.octave) + } + + fn with_octave(self, octave: Octave) -> Self { + Self::new(self.named_pitch, octave) + } +} + impl Add for Note { type Output = Self; @@ -199,6 +217,12 @@ impl AddAssign for Note { } } +impl PartialOrd for Note { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.octave.cmp(&other.octave).then(self.pitch().cmp(&other.pitch()))) + } +} + // Define octaves. define_octave!(Zero, Octave::Zero);