use super::{Note, Octave, Pitch, PitchClass};
use crate::types::interval::PerfectQuality::Augmented;
use crate::types::{Interval, PerfectQuality};
use std::fmt;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Tone {
octave: Octave,
note: Note,
}
impl Tone {
pub fn from_parts(octave: Octave, note: Note) -> Tone {
Tone { octave, note }
}
pub fn note(&self) -> Note {
self.note
}
pub fn pitch(&self) -> Pitch {
Pitch::from((12 * self.octave.to_index()) + self.note.pitch_class().to_index())
}
pub fn pitch_class(&self) -> PitchClass {
self.note.pitch_class()
}
pub fn octave(&self) -> Octave {
self.octave
}
}
impl fmt::Display for Tone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_args!("{}{}", self.note, self.octave).fmt(f)
}
}
impl std::ops::Add<Interval> for Tone {
type Output = Option<Self>;
fn add(self, interval: Interval) -> Self::Output {
if interval == Interval::First(PerfectQuality::TripleDiminished) {
self - Interval::First(PerfectQuality::TripleAugmented)
} else if interval == Interval::First(PerfectQuality::DoubleDiminished) {
self - Interval::First(PerfectQuality::DoubleAugmented)
} else if interval == Interval::First(PerfectQuality::Diminished) {
self - Interval::First(PerfectQuality::Augmented)
} else {
if let Some(pitch) = self.pitch() + interval {
Some(Tone {
note: (self.note + interval)?,
octave: pitch.octave(),
})
} else {
None
}
}
}
}
impl std::ops::Sub<Interval> for Tone {
type Output = Option<Self>;
fn sub(self, interval: Interval) -> Self::Output {
if interval == Interval::First(PerfectQuality::TripleDiminished) {
self + Interval::First(PerfectQuality::TripleAugmented)
} else if interval == Interval::First(PerfectQuality::DoubleDiminished) {
self + Interval::First(PerfectQuality::DoubleAugmented)
} else if interval == Interval::First(PerfectQuality::Diminished) {
self + Interval::First(PerfectQuality::Augmented)
} else {
if let Some(pitch) = self.pitch() - interval {
Some(Tone {
note: (self.note - interval)?,
octave: pitch.octave(),
})
} else {
None
}
}
}
}
#[cfg(test)]
mod tests {
use super::{Note, Octave, Tone};
use crate::types::{MajorQuality, PerfectQuality};
#[test]
fn test_creation() {
println!("Sharps");
for octave in Octave::all().iter() {
for note in Note::sharps().iter() {
let tone = Tone::from_parts(octave.clone(), note.clone());
print!("{} ", tone);
}
println!();
}
println!("Flats");
for octave in Octave::all().iter() {
for note in Note::flats().iter() {
let tone = Tone::from_parts(octave.clone(), note.clone());
print!("{} ", tone);
}
println!();
}
}
#[test]
fn test_addition() {
use crate::types::{Accidental, Interval, Note, Octave, Tone};
fn test(tone: Tone, interval: Interval, expect: Option<Tone>) {
let result = tone + interval;
assert_eq!(
result,
expect,
"{} {:?} + {} {:?} = {:?} Expected: {:?}",
tone,
tone.pitch(),
interval,
interval.steps(),
result,
expect
);
}
test(
Tone::from_parts(Octave::ThreeLine, Note::F(Accidental::Natural)),
Interval::Fourth(PerfectQuality::DoubleAugmented),
Some(Tone::from_parts(Octave::FourLine, Note::B(Accidental::Sharp))),
);
test(
Tone::from_parts(Octave::OneLine, Note::G(Accidental::Natural)),
Interval::Fourth(PerfectQuality::Perfect),
Some(Tone::from_parts(Octave::TwoLine, Note::C(Accidental::Natural))),
);
test(
Tone::from_parts(Octave::SevenLine, Note::B(Accidental::Natural)),
Interval::Third(MajorQuality::Major),
None,
);
test(
Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural)),
Interval::First(PerfectQuality::Perfect),
Some(Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural))),
);
test(
Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural)),
Interval::First(PerfectQuality::Diminished),
Some(Tone::from_parts(Octave::Small, Note::C(Accidental::Flat))),
);
}
#[test]
fn test_subtraction() {
use crate::types::{Accidental, Interval, Note, Octave, Tone};
fn test(tone: Tone, interval: Interval, expect: Option<Tone>) {
let result = tone - interval;
assert_eq!(
result,
expect,
"{} {:?} - {} {:?}= {:?} Expected: {:?}",
tone,
tone.pitch(),
interval,
interval.steps(),
result,
expect
);
}
test(
Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural)),
Interval::First(PerfectQuality::Perfect),
Some(Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural))),
);
test(
Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural)),
Interval::First(PerfectQuality::Diminished),
Some(Tone::from_parts(Octave::OneLine, Note::C(Accidental::Sharp))),
);
test(
Tone::from_parts(Octave::OneLine, Note::C(Accidental::Natural)),
Interval::First(PerfectQuality::Augmented),
Some(Tone::from_parts(Octave::Small, Note::C(Accidental::Flat))),
);
test(
Tone::from_parts(Octave::DoubleContra, Note::D(Accidental::Natural)),
Interval::Third(MajorQuality::Major),
None,
);
}
}