use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use crate::key::PianoKey;
use crate::math;
use crate::pitch::Pitch;
use crate::pitch::Pitched;
use crate::pitch::Ratio;
use crate::tuning::ConcertPitch;
#[derive(Copy, Clone, Debug, Ord, Eq, Hash, PartialEq, PartialOrd)]
pub struct Note {
midi_number: i32,
}
impl Note {
pub fn from_midi_number(midi_number: impl Into<i32>) -> Self {
Self {
midi_number: midi_number.into(),
}
}
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)
}
pub fn from_piano_key(piano_key: PianoKey) -> Self {
Self::from_midi_number(piano_key.midi_number())
}
pub fn midi_number(self) -> i32 {
self.midi_number
}
pub fn checked_midi_number(self) -> Option<u8> {
u8::try_from(self.midi_number)
.ok()
.filter(|midi_number| (0..128).contains(midi_number))
}
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,
},
)
}
pub fn as_piano_key(self) -> PianoKey {
PianoKey::from_midi_number(self.midi_number())
}
pub fn at_pitch(self, pitched: impl Pitched) -> NoteAtConcertPitch {
(self, ConcertPitch::from_note_and_pitch(self, pitched))
}
pub fn at_concert_pitch(self, concert_pitch: ConcertPitch) -> NoteAtConcertPitch {
(self, concert_pitch)
}
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)
}
pub fn num_semitones_before(self, other: Note) -> i32 {
other.midi_number - self.midi_number
}
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()
}
}
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))
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum NoteLetter {
C,
Csh,
D,
Dsh,
E,
F,
Fsh,
G,
Gsh,
A,
Ash,
B,
}
impl NoteLetter {
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)
}
}
#[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)
}
}
#[derive(Copy, Clone, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)]
pub enum HelmholtzOctave {
SubContra,
Contra,
Great,
Small,
OneLined,
TwoLined,
ThreeLined,
FourLined,
FiveLined,
SixLined,
}
pub trait PitchedNote: Pitched {
fn note(&self) -> Note;
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
}
}
pub type NoteAtConcertPitch = (Note, ConcertPitch);
impl PitchedNote for NoteAtConcertPitch {
fn note(&self) -> Note {
self.0
}
}