//! Abstractions for working with notes, letters and octaves.
use std::fmt::{self, Display, Formatter};
use crate::{
key::PianoKey,
math,
pitch::{Pitch, Pitched, Ratio},
tuning::ConcertPitch,
};
/// A musical note encapsulating a clearly defined pitch.
///
/// The pitch can be derived using the [`Pitched`] impl on the [`Note`] type itself, assuming
/// standard 440 Hz tuning, or on [`NoteAtConcertPitch`], given a specific concert pitch.
#[derive(Copy, Clone, Debug, Ord, Eq, Hash, PartialEq, PartialOrd)]
pub struct Note {
midi_number: i32,
}
impl Note {
/// Creates a [`Note`] instance from the given MIDI number.
pub fn from_midi_number(midi_number: impl Into<i32>) -> Self {
Self {
midi_number: midi_number.into(),
}
}
/// Creates a [`Note`] instance given a [`NoteLetter`] and an octave.
///
/// # Examples
///
/// ```
/// # use tune::note::HelmholtzOctave;
/// # use tune::note::Note;
/// # use tune::note::NoteLetter;
/// # use tune::note::Octave;
/// let a4 = Note::from_midi_number(69);
/// assert_eq!(Note::from_letter_and_octave(NoteLetter::A, 4), a4);
/// assert_eq!(Note::from_letter_and_octave(NoteLetter::A, Octave::from_octave_number(4)), a4);
/// assert_eq!(Note::from_letter_and_octave(NoteLetter::A, HelmholtzOctave::OneLined), a4);
/// ```
pub fn from_letter_and_octave(note_letter: NoteLetter, octave: impl Into<Octave>) -> Self {
let semitone = match note_letter {
NoteLetter::C => 0,
NoteLetter::Csh => 1,
NoteLetter::D => 2,
NoteLetter::Dsh => 3,
NoteLetter::E => 4,
NoteLetter::F => 5,
NoteLetter::Fsh => 6,
NoteLetter::G => 7,
NoteLetter::Gsh => 8,
NoteLetter::A => 9,
NoteLetter::Ash => 10,
NoteLetter::B => 11,
};
Self::from_midi_number((octave.into().octave_number + 1) * 12 + semitone)
}
/// Creates a [`Note`] instance from a [`PianoKey`] assuming standard 12-EDO tuning.
pub fn from_piano_key(piano_key: PianoKey) -> Self {
Self::from_midi_number(piano_key.midi_number())
}
/// Returns the MIDI number of this [`Note`].
pub fn midi_number(self) -> i32 {
self.midi_number
}
/// Returns the MIDI number of this [`Note`] if it is in the valid MIDI range [0..128).
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// assert_eq!(Note::from_midi_number(-1).checked_midi_number(), None);
/// assert_eq!(Note::from_midi_number(0).checked_midi_number(), Some(0));
/// assert_eq!(Note::from_midi_number(64).checked_midi_number(), Some(64));
/// assert_eq!(Note::from_midi_number(127).checked_midi_number(), Some(127));
/// assert_eq!(Note::from_midi_number(128).checked_midi_number(), None);
/// ```
pub fn checked_midi_number(self) -> Option<u8> {
u8::try_from(self.midi_number)
.ok()
.filter(|midi_number| (0..128).contains(midi_number))
}
/// Splits the current note into a [`NoteLetter`] and an [`Octave`] part.
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// # use tune::note::NoteLetter;
/// # use tune::note::Octave;
/// let a4 = Note::from_midi_number(69);
/// assert_eq!(a4.letter_and_octave(), (NoteLetter::A, Octave::from_octave_number(4)));
///
/// let midi_root = Note::from_midi_number(0);
/// assert_eq!(midi_root.letter_and_octave(), (NoteLetter::C, Octave::from_octave_number(-1)));
/// ```
pub fn letter_and_octave(self) -> (NoteLetter, Octave) {
let (midi_octave, semitone) = math::i32_dr_u(self.midi_number, 12u32);
let note_letter = match semitone {
0 => NoteLetter::C,
1 => NoteLetter::Csh,
2 => NoteLetter::D,
3 => NoteLetter::Dsh,
4 => NoteLetter::E,
5 => NoteLetter::F,
6 => NoteLetter::Fsh,
7 => NoteLetter::G,
8 => NoteLetter::Gsh,
9 => NoteLetter::A,
10 => NoteLetter::Ash,
11 => NoteLetter::B,
other => unreachable!("value was {}", other),
};
(
note_letter,
Octave {
octave_number: midi_octave - 1,
},
)
}
/// Retrieves the associated [`PianoKey`] assuming standard 12-EDO tuning.
pub fn as_piano_key(self) -> PianoKey {
PianoKey::from_midi_number(self.midi_number())
}
/// Creates a [`NoteAtConcertPitch`] instance with `self` sounding at a different pitch.
///
/// # Examples
///
/// ```
/// # use assert_approx_eq::assert_approx_eq;
/// # use tune::note::NoteLetter;
/// # use tune::pitch::Pitch;
/// # use tune::tuning::ConcertPitch;
/// use tune::pitch::Pitched;
///
/// let c4_at_260_hz = NoteLetter::C.in_octave(4).at_pitch(Pitch::from_hz(260.0));
/// assert_approx_eq!(c4_at_260_hz.pitch().as_hz(), 260.0);
///
/// let (_note, concert_pitch) = c4_at_260_hz;
/// assert_approx_eq!(concert_pitch.a4_pitch().as_hz(), 437.266136);
/// ```
pub fn at_pitch(self, pitched: impl Pitched) -> NoteAtConcertPitch {
(self, ConcertPitch::from_note_and_pitch(self, pitched))
}
/// Convenience function creating a [`NoteAtConcertPitch`] instance.
///
/// # Examples
///
/// ```
/// # use tune::note::NoteLetter;
/// # use tune::pitch::Pitch;
/// # use tune::tuning::ConcertPitch;
/// let a4 = NoteLetter::A.in_octave(4);
/// let concert_pitch = ConcertPitch::from_a4_pitch(Pitch::from_hz(435.0));
/// assert_eq!(a4.at_concert_pitch(concert_pitch), (a4, concert_pitch));
/// ```
pub fn at_concert_pitch(self, concert_pitch: ConcertPitch) -> NoteAtConcertPitch {
(self, concert_pitch)
}
/// Iterates over all [`Note`]s in the range [`self`..`upper_bound`).
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// let midi_note_62 = Note::from_midi_number(62);
/// let midi_note_67 = Note::from_midi_number(67);
///
/// assert_eq!(
/// midi_note_62.notes_before(midi_note_67).collect::<Vec<_>>(),
/// (62..67).map(Note::from_midi_number).collect::<Vec<_>>()
/// );
/// assert!(midi_note_67.notes_before(midi_note_62).collect::<Vec<_>>().is_empty());
/// ```
pub fn notes_before(
self,
upper_bound: Note,
) -> impl DoubleEndedIterator<Item = Note> + ExactSizeIterator<Item = Note> + 'static {
(self.midi_number..upper_bound.midi_number).map(Self::from_midi_number)
}
/// Counts the number of semitones [left inclusive, right exclusive) between `self` and `other`.
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// let midi_note_62 = Note::from_midi_number(62);
/// let midi_note_67 = Note::from_midi_number(67);
///
/// assert_eq!(midi_note_62.num_semitones_before(midi_note_67), 5);
/// assert_eq!(midi_note_67.num_semitones_before(midi_note_62), -5);
/// ```
pub fn num_semitones_before(self, other: Note) -> i32 {
other.midi_number - self.midi_number
}
/// Returns the note `num_semitones` semitones above `self`.
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// let midi_note_62 = Note::from_midi_number(62);
/// let midi_note_67 = Note::from_midi_number(67);
///
/// assert_eq!(midi_note_62.plus_semitones(5), midi_note_67);
/// assert_eq!(midi_note_67.plus_semitones(-5), midi_note_62);
/// ```
pub fn plus_semitones(self, num_semitones: i32) -> Note {
Note::from_midi_number(self.midi_number() + num_semitones)
}
}
impl Pitched for Note {
fn pitch(&self) -> Pitch {
(*self, ConcertPitch::default()).pitch()
}
}
/// [`Note`]s are rendered in scientific pitch notation.
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// assert_eq!(Note::from_midi_number(0).to_string(), "C -1");
/// assert_eq!(Note::from_midi_number(69).to_string(), "A 4");
/// assert_eq!(Note::from_midi_number(70).to_string(), "A#/Bb 4");
/// assert_eq!(Note::from_midi_number(71).to_string(), "B 4");
/// assert_eq!(Note::from_midi_number(72).to_string(), "C 5");
/// assert_eq!(Note::from_midi_number(127).to_string(), "G 9");
///
/// // Format flags
/// assert_eq!(format!("{:+}", Note::from_midi_number(70)), "A# 4");
/// assert_eq!(format!("{:-}", Note::from_midi_number(70)), "Bb 4");
/// assert_eq!(format!("{:10}", Note::from_midi_number(70)), "A#/Bb 4 ");
/// assert_eq!(format!("{:<10}", Note::from_midi_number(70)), "A#/Bb 4 ");
/// assert_eq!(format!("{:>10}", Note::from_midi_number(70)), " A#/Bb 4");
/// ```
impl Display for Note {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let (letter, octave) = self.letter_and_octave();
let formatted_note = match (f.sign_plus(), f.sign_minus()) {
(false, false) => format!("{}", letter),
(true, false) => format!("{:+}", letter),
(false, true) => format!("{:-}", letter),
(true, true) => unreachable!("Impossible format string"),
};
f.pad(&format!("{} {}", formatted_note, octave.octave_number))
}
}
/// The speaking name of a note within its octave.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum NoteLetter {
C,
Csh,
D,
Dsh,
E,
F,
Fsh,
G,
Gsh,
A,
Ash,
B,
}
impl NoteLetter {
/// Shortcut for [`Note::from_letter_and_octave`].
///
/// # Examples
///
/// ```
/// # use tune::note::Note;
/// # use tune::note::NoteLetter;
/// assert_eq!(NoteLetter::C.in_octave(4), Note::from_letter_and_octave(NoteLetter::C, 4));
/// ```
pub fn in_octave(self, octave: impl Into<Octave>) -> Note {
Note::from_letter_and_octave(self, octave)
}
}
impl Display for NoteLetter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
enum Sign {
Sharp,
Flat,
Both,
}
let sign = match (f.sign_plus(), f.sign_minus()) {
(false, false) => Sign::Both,
(true, false) => Sign::Sharp,
(false, true) => Sign::Flat,
(true, true) => unreachable!("Impossible format string"),
};
let note_name = match (self, sign) {
(NoteLetter::C, _) => "C",
(NoteLetter::Csh, Sign::Both) => "C#/Db",
(NoteLetter::Csh, Sign::Sharp) => "C#",
(NoteLetter::Csh, Sign::Flat) => "Db",
(NoteLetter::D, _) => "D",
(NoteLetter::Dsh, Sign::Both) => "D#/Eb",
(NoteLetter::Dsh, Sign::Sharp) => "D#",
(NoteLetter::Dsh, Sign::Flat) => "Eb",
(NoteLetter::E, _) => "E",
(NoteLetter::F, _) => "F",
(NoteLetter::Fsh, Sign::Both) => "F#/Gb",
(NoteLetter::Fsh, Sign::Sharp) => "F#",
(NoteLetter::Fsh, Sign::Flat) => "Gb",
(NoteLetter::G, _) => "G",
(NoteLetter::Gsh, Sign::Both) => "G#/Ab",
(NoteLetter::Gsh, Sign::Sharp) => "G#",
(NoteLetter::Gsh, Sign::Flat) => "Ab",
(NoteLetter::A, _) => "A",
(NoteLetter::Ash, Sign::Both) => "A#/Bb",
(NoteLetter::Ash, Sign::Sharp) => "A#",
(NoteLetter::Ash, Sign::Flat) => "Bb",
(NoteLetter::B, _) => "B",
};
f.pad(note_name)
}
}
/// Typed representation of the octave of a note.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Octave {
octave_number: i32,
}
impl Octave {
pub fn from_octave_number(octave_number: i16) -> Self {
Self {
octave_number: i32::from(octave_number),
}
}
pub fn octave_number(self) -> i32 {
self.octave_number
}
}
impl From<i16> for Octave {
fn from(octave_number: i16) -> Self {
Octave::from_octave_number(octave_number)
}
}
impl From<HelmholtzOctave> for Octave {
fn from(helmholtz_octave: HelmholtzOctave) -> Self {
let octave_number = match helmholtz_octave {
HelmholtzOctave::SubContra => 0,
HelmholtzOctave::Contra => 1,
HelmholtzOctave::Great => 2,
HelmholtzOctave::Small => 3,
HelmholtzOctave::OneLined => 4,
HelmholtzOctave::TwoLined => 5,
HelmholtzOctave::ThreeLined => 6,
HelmholtzOctave::FourLined => 7,
HelmholtzOctave::FiveLined => 8,
HelmholtzOctave::SixLined => 9,
};
Self::from_octave_number(octave_number)
}
}
/// The speaking name of the octave of a note.
#[derive(Copy, Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
pub enum HelmholtzOctave {
SubContra,
Contra,
Great,
Small,
OneLined,
TwoLined,
ThreeLined,
FourLined,
FiveLined,
SixLined,
}
/// Trait for objects that provide [`Pitch`] and [`Note`] information.
///
/// A [`Note`] has a unique pitch defined by the 440 Hz standard tuning.
/// For a note to sound at a different [`Pitch`] the type alias [`NoteAtConcertPitch`] is used.
pub trait PitchedNote: Pitched {
/// Retrieves the [`Note`] part of `self`.
///
/// ```
/// # use tune::note::NoteLetter;
/// # use tune::pitch::Pitch;
/// use tune::note::PitchedNote;
///
/// let c4 = NoteLetter::C.in_octave(4);
/// assert_eq!(c4.note(), c4);
///
/// let c4_altered = c4.at_pitch(Pitch::from_hz(256.0));
/// assert_eq!(c4_altered.note(), c4);
/// ```
fn note(&self) -> Note;
/// Returns a new `PitchedNote` with the same [`Note`] part but a [`Pitch`] altered by `delta`.
///
/// # Examples
///
/// ```
/// # use assert_approx_eq::assert_approx_eq;
/// # use tune::note::NoteLetter;
/// # use tune::pitch::Ratio;
/// use tune::note::PitchedNote;
/// use tune::pitch::Pitched;
///
/// let a4 = NoteLetter::A.in_octave(4);
/// let a4_altered = a4.alter_pitch_by(Ratio::from_float(1.01));
/// assert_eq!(a4_altered.note(), a4);
/// assert_approx_eq!(a4_altered.pitch().as_hz(), 444.4);
/// ```
fn alter_pitch_by(&self, delta: Ratio) -> NoteAtConcertPitch {
let new_concert_pitch =
ConcertPitch::from_note_and_pitch(self.note(), self.pitch() * delta);
(self.note(), new_concert_pitch)
}
}
impl PitchedNote for Note {
fn note(&self) -> Note {
*self
}
}
/// Type alias for [`Note`]s that should sound at a [`Pitch`] different from standard 440 Hz tuning.
pub type NoteAtConcertPitch = (Note, ConcertPitch);
impl PitchedNote for NoteAtConcertPitch {
fn note(&self) -> Note {
self.0
}
}