use crate::Error;
use core::convert::TryFrom;
use core::fmt;
#[allow(clippy::excessive_precision)]
const FREQ_F64: [f64; 128] = [
8.175798915643707e+00, 8.661957218027251e+00, 9.177023997418985e+00, 9.722718241315027e+00, 1.030086115352718e+01, 1.091338223228137e+01, 1.156232570973857e+01, 1.224985737442966e+01, 1.297827179937328e+01, 1.375000000000000e+01, 1.456761754744030e+01, 1.543385316425388e+01, 1.635159783128741e+01, 1.732391443605450e+01, 1.835404799483797e+01, 1.944543648263005e+01, 2.060172230705437e+01, 2.182676446456274e+01, 2.312465141947714e+01, 2.449971474885933e+01, 2.595654359874657e+01, 2.749999999999999e+01, 2.913523509488062e+01, 3.086770632850775e+01, 3.270319566257481e+01, 3.464782887210901e+01, 3.670809598967594e+01, 3.889087296526010e+01, 4.120344461410874e+01, 4.365352892912548e+01, 4.624930283895428e+01, 4.899942949771867e+01, 5.191308719749313e+01, 5.499999999999998e+01, 5.827047018976124e+01, 6.173541265701550e+01, 6.540639132514963e+01, 6.929565774421802e+01, 7.341619197935188e+01, 7.778174593052020e+01, 8.240688922821748e+01, 8.730705785825096e+01, 9.249860567790856e+01, 9.799885899543733e+01, 1.038261743949863e+02, 1.100000000000000e+02, 1.165409403795225e+02, 1.234708253140310e+02, 1.308127826502993e+02, 1.385913154884360e+02, 1.468323839587038e+02, 1.555634918610404e+02, 1.648137784564350e+02, 1.746141157165019e+02, 1.849972113558171e+02, 1.959977179908747e+02, 2.076523487899725e+02, 2.199999999999999e+02, 2.330818807590450e+02, 2.469416506280620e+02, 2.616255653005987e+02, 2.771826309768719e+02, 2.936647679174075e+02, 3.111269837220810e+02, 3.296275569128697e+02, 3.492282314330038e+02, 3.699944227116345e+02, 3.919954359817490e+02, 4.153046975799451e+02, 4.400000000000001e+02, 4.661637615180896e+02, 4.938833012561240e+02, 5.232511306011974e+02, 5.543652619537438e+02, 5.873295358348150e+02, 6.222539674441620e+02, 6.592551138257395e+02, 6.984564628660077e+02, 7.399888454232689e+02, 7.839908719634981e+02, 8.306093951598901e+02, 8.800000000000002e+02, 9.323275230361793e+02, 9.877666025122480e+02, 1.046502261202395e+03, 1.108730523907488e+03, 1.174659071669630e+03, 1.244507934888324e+03, 1.318510227651479e+03, 1.396912925732015e+03, 1.479977690846538e+03, 1.567981743926996e+03, 1.661218790319782e+03, 1.760000000000000e+03, 1.864655046072361e+03, 1.975533205024499e+03, 2.093004522404789e+03, 2.217461047814978e+03, 2.349318143339263e+03, 2.489015869776648e+03, 2.637020455302961e+03, 2.793825851464034e+03, 2.959955381693076e+03, 3.135963487853996e+03, 3.322437580639565e+03, 3.520000000000001e+03, 3.729310092144722e+03, 3.951066410048997e+03, 4.186009044809579e+03, 4.434922095629956e+03, 4.698636286678526e+03, 4.978031739553296e+03, 5.274040910605922e+03, 5.587651702928068e+03, 5.919910763386151e+03, 6.271926975707993e+03, 6.644875161279129e+03, 7.040000000000002e+03, 7.458620184289443e+03, 7.902132820097994e+03, 8.372018089619158e+03, 8.869844191259912e+03, 9.397272573357051e+03, 9.956063479106591e+03, 1.054808182121184e+04, 1.117530340585614e+04, 1.183982152677230e+04, 1.254385395141599e+04, ];
#[allow(clippy::excessive_precision)]
const FREQ_F32: [f32; 128] = [
8.1757987e+00_f32, 8.6619569e+00_f32, 9.1770237e+00_f32, 9.7227179e+00_f32, 1.0300861e+01_f32, 1.0913382e+01_f32, 1.1562325e+01_f32, 1.2249857e+01_f32, 1.2978271e+01_f32, 1.3750000e+01_f32, 1.4567617e+01_f32, 1.5433853e+01_f32, 1.6351597e+01_f32, 1.7323914e+01_f32, 1.8354047e+01_f32, 1.9445436e+01_f32, 2.0601722e+01_f32, 2.1826764e+01_f32, 2.3124651e+01_f32, 2.4499714e+01_f32, 2.5956543e+01_f32, 2.7499999e+01_f32, 2.9135234e+01_f32, 3.0867705e+01_f32, 3.2703195e+01_f32, 3.4647828e+01_f32, 3.6708095e+01_f32, 3.8890872e+01_f32, 4.1203443e+01_f32, 4.3653528e+01_f32, 4.6249301e+01_f32, 4.8999428e+01_f32, 5.1913086e+01_f32, 5.4999998e+01_f32, 5.8270468e+01_f32, 6.1735411e+01_f32, 6.5406389e+01_f32, 6.9295655e+01_f32, 7.3416190e+01_f32, 7.7781743e+01_f32, 8.2406887e+01_f32, 8.7307055e+01_f32, 9.2498603e+01_f32, 9.7998856e+01_f32, 1.0382617e+02_f32, 1.1000000e+02_f32, 1.1654094e+02_f32, 1.2347082e+02_f32, 1.3081278e+02_f32, 1.3859131e+02_f32, 1.4683238e+02_f32, 1.5556349e+02_f32, 1.6481377e+02_f32, 1.7461411e+02_f32, 1.8499721e+02_f32, 1.9599771e+02_f32, 2.0765234e+02_f32, 2.1999999e+02_f32, 2.3308187e+02_f32, 2.4694164e+02_f32, 2.6162556e+02_f32, 2.7718262e+02_f32, 2.9366476e+02_f32, 3.1112697e+02_f32, 3.2962755e+02_f32, 3.4922822e+02_f32, 3.6999441e+02_f32, 3.9199542e+02_f32, 4.1530468e+02_f32, 4.3999999e+02_f32, 4.6616375e+02_f32, 4.9388329e+02_f32, 5.2325111e+02_f32, 5.5436524e+02_f32, 5.8732952e+02_f32, 6.2225395e+02_f32, 6.5925509e+02_f32, 6.9845644e+02_f32, 7.3998882e+02_f32, 7.8399085e+02_f32, 8.3060937e+02_f32, 8.7999997e+02_f32, 9.3232749e+02_f32, 9.8776657e+02_f32, 1.0465022e+03_f32, 1.1087305e+03_f32, 1.1746590e+03_f32, 1.2445079e+03_f32, 1.3185102e+03_f32, 1.3969129e+03_f32, 1.4799776e+03_f32, 1.5679817e+03_f32, 1.6612187e+03_f32, 1.7599999e+03_f32, 1.8646550e+03_f32, 1.9755331e+03_f32, 2.0930045e+03_f32, 2.2174610e+03_f32, 2.3493181e+03_f32, 2.4890158e+03_f32, 2.6370204e+03_f32, 2.7938258e+03_f32, 2.9599553e+03_f32, 3.1359634e+03_f32, 3.3224375e+03_f32, 3.5199999e+03_f32, 3.7293100e+03_f32, 3.9510663e+03_f32, 4.1860089e+03_f32, 4.4349220e+03_f32, 4.6986361e+03_f32, 4.9780316e+03_f32, 5.2740407e+03_f32, 5.5876515e+03_f32, 5.9199106e+03_f32, 6.2719268e+03_f32, 6.6448749e+03_f32, 7.0399998e+03_f32, 7.4586199e+03_f32, 7.9021326e+03_f32, 8.3720178e+03_f32, 8.8698439e+03_f32, 9.3972723e+03_f32, 9.9560632e+03_f32, 1.0548081e+04_f32, 1.1175303e+04_f32, 1.1839821e+04_f32, 1.2543854e+04_f32, ];
#[repr(u8)]
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Note {
CMinus1 = 0,
DbMinus1 = 1,
DMinus1 = 2,
EbMinus1 = 3,
EMinus1 = 4,
FMinus1 = 5,
GbMinus1 = 6,
GMinus1 = 7,
AbMinus1 = 8,
AMinus1 = 9,
BbMinus1 = 10,
BMinus1 = 11,
C0 = 12,
Db0 = 13,
D0 = 14,
Eb0 = 15,
E0 = 16,
F0 = 17,
Gb0 = 18,
G0 = 19,
Ab0 = 20,
A0 = 21,
Bb0 = 22,
B0 = 23,
C1 = 24,
Db1 = 25,
D1 = 26,
Eb1 = 27,
E1 = 28,
F1 = 29,
Gb1 = 30,
G1 = 31,
Ab1 = 32,
A1 = 33,
Bb1 = 34,
B1 = 35,
C2 = 36,
Db2 = 37,
D2 = 38,
Eb2 = 39,
E2 = 40,
F2 = 41,
Gb2 = 42,
G2 = 43,
Ab2 = 44,
A2 = 45,
Bb2 = 46,
B2 = 47,
C3 = 48,
Db3 = 49,
D3 = 50,
Eb3 = 51,
E3 = 52,
F3 = 53,
Gb3 = 54,
G3 = 55,
Ab3 = 56,
A3 = 57,
Bb3 = 58,
B3 = 59,
C4 = 60,
Db4 = 61,
D4 = 62,
Eb4 = 63,
E4 = 64,
F4 = 65,
Gb4 = 66,
G4 = 67,
Ab4 = 68,
A4 = 69,
Bb4 = 70,
B4 = 71,
C5 = 72,
Db5 = 73,
D5 = 74,
Eb5 = 75,
E5 = 76,
F5 = 77,
Gb5 = 78,
G5 = 79,
Ab5 = 80,
A5 = 81,
Bb5 = 82,
B5 = 83,
C6 = 84,
Db6 = 85,
D6 = 86,
Eb6 = 87,
E6 = 88,
F6 = 89,
Gb6 = 90,
G6 = 91,
Ab6 = 92,
A6 = 93,
Bb6 = 94,
B6 = 95,
C7 = 96,
Db7 = 97,
D7 = 98,
Eb7 = 99,
E7 = 100,
F7 = 101,
Gb7 = 102,
G7 = 103,
Ab7 = 104,
A7 = 105,
Bb7 = 106,
B7 = 107,
C8 = 108,
Db8 = 109,
D8 = 110,
Eb8 = 111,
E8 = 112,
F8 = 113,
Gb8 = 114,
G8 = 115,
Ab8 = 116,
A8 = 117,
Bb8 = 118,
B8 = 119,
C9 = 120,
Db9 = 121,
D9 = 122,
Eb9 = 123,
E9 = 124,
F9 = 125,
Gb9 = 126,
G9 = 127,
}
#[allow(non_upper_case_globals)]
impl Note {
pub const CSharpMinus1: Note = Note::DbMinus1;
pub const DSharpMinus1: Note = Note::EbMinus1;
pub const FSharpMinus1: Note = Note::GbMinus1;
pub const GSharpMinus1: Note = Note::AbMinus1;
pub const ASharpMinus1: Note = Note::BbMinus1;
pub const CSharp0: Note = Note::Db0;
pub const DSharp0: Note = Note::Eb0;
pub const FSharp0: Note = Note::Gb0;
pub const GSharp0: Note = Note::Ab0;
pub const ASharp0: Note = Note::Bb0;
pub const CSharp1: Note = Note::Db1;
pub const DSharp1: Note = Note::Eb1;
pub const FSharp1: Note = Note::Gb1;
pub const GSharp1: Note = Note::Ab1;
pub const ASharp1: Note = Note::Bb1;
pub const CSharp2: Note = Note::Db2;
pub const DSharp2: Note = Note::Eb2;
pub const FSharp2: Note = Note::Gb2;
pub const GSharp2: Note = Note::Ab2;
pub const ASharp2: Note = Note::Bb2;
pub const CSharp3: Note = Note::Db3;
pub const DSharp3: Note = Note::Eb3;
pub const FSharp3: Note = Note::Gb3;
pub const GSharp3: Note = Note::Ab3;
pub const ASharp3: Note = Note::Bb3;
pub const CSharp4: Note = Note::Db4;
pub const DSharp4: Note = Note::Eb4;
pub const FSharp4: Note = Note::Gb4;
pub const GSharp4: Note = Note::Ab4;
pub const ASharp4: Note = Note::Bb4;
pub const CSharp5: Note = Note::Db5;
pub const DSharp5: Note = Note::Eb5;
pub const FSharp5: Note = Note::Gb5;
pub const GSharp5: Note = Note::Ab5;
pub const ASharp5: Note = Note::Bb5;
pub const CSharp6: Note = Note::Db6;
pub const DSharp6: Note = Note::Eb6;
pub const FSharp6: Note = Note::Gb6;
pub const GSharp6: Note = Note::Ab6;
pub const ASharp6: Note = Note::Bb6;
pub const CSharp7: Note = Note::Db7;
pub const DSharp7: Note = Note::Eb7;
pub const FSharp7: Note = Note::Gb7;
pub const GSharp7: Note = Note::Ab7;
pub const ASharp7: Note = Note::Bb7;
pub const CSharp8: Note = Note::Db8;
pub const DSharp8: Note = Note::Eb8;
pub const FSharp8: Note = Note::Gb8;
pub const GSharp8: Note = Note::Ab8;
pub const ASharp8: Note = Note::Bb8;
pub const CSharp9: Note = Note::Db9;
pub const DSharp9: Note = Note::Eb9;
pub const FSharp9: Note = Note::Gb9;
pub const LOWEST_NOTE: Note = Note::CMinus1;
pub const HIGHEST_NOTE: Note = Note::G9;
#[inline(always)]
pub unsafe fn from_u8_unchecked(note: u8) -> Note {
core::mem::transmute(note)
}
#[inline(always)]
pub fn from_u8_lossy(note: u8) -> Note {
Note::from(crate::U7::from_u8_lossy(note))
}
#[inline(always)]
pub fn to_freq_f32(self) -> f32 {
FREQ_F32[self as usize]
}
#[inline(always)]
pub fn to_freq_f64(self) -> f64 {
FREQ_F64[self as usize]
}
pub fn step(self, half_steps: i8) -> Result<Note, Error> {
let half_steps: i16 = half_steps.into();
let raw_note = self as i16 + half_steps;
if Note::LOWEST_NOTE as i16 <= raw_note && raw_note <= Note::HIGHEST_NOTE as i16 {
Ok(unsafe { Note::from_u8_unchecked(raw_note as u8) })
} else {
Err(Error::NoteOutOfRange)
}
}
pub fn to_str(self) -> &'static str {
match self {
Note::CMinus1 => "C-1",
Note::DbMinus1 => "C#/Db-1",
Note::DMinus1 => "D-1",
Note::EbMinus1 => "D#/Eb-1",
Note::EMinus1 => "E-1",
Note::FMinus1 => "F-1",
Note::GbMinus1 => "F#/Gb-1",
Note::GMinus1 => "G-1",
Note::AbMinus1 => "G#/Ab-1",
Note::AMinus1 => "A-1",
Note::BbMinus1 => "A#/Bb-1",
Note::BMinus1 => "B-1",
Note::C0 => "C0",
Note::Db0 => "C#/Db0",
Note::D0 => "D0",
Note::Eb0 => "D#/Eb0",
Note::E0 => "E0",
Note::F0 => "F0",
Note::Gb0 => "F#/Gb0",
Note::G0 => "G0",
Note::Ab0 => "G#/Ab0",
Note::A0 => "A0",
Note::Bb0 => "A#/Bb0",
Note::B0 => "B0",
Note::C1 => "C1",
Note::Db1 => "C#/Db1",
Note::D1 => "D1",
Note::Eb1 => "D#/Eb1",
Note::E1 => "E1",
Note::F1 => "F1",
Note::Gb1 => "F#/Gb1",
Note::G1 => "G1",
Note::Ab1 => "G#/Ab1",
Note::A1 => "A1",
Note::Bb1 => "A#/Bb1",
Note::B1 => "B1",
Note::C2 => "C2",
Note::Db2 => "C#/Db2",
Note::D2 => "D2",
Note::Eb2 => "D#/Eb2",
Note::E2 => "E2",
Note::F2 => "F2",
Note::Gb2 => "F#/Gb2",
Note::G2 => "G2",
Note::Ab2 => "G#/Ab2",
Note::A2 => "A2",
Note::Bb2 => "A#/Bb2",
Note::B2 => "B2",
Note::C3 => "C3",
Note::Db3 => "C#/Db3",
Note::D3 => "D3",
Note::Eb3 => "D#/Eb3",
Note::E3 => "E3",
Note::F3 => "F3",
Note::Gb3 => "F#/Gb3",
Note::G3 => "G3",
Note::Ab3 => "G#/Ab3",
Note::A3 => "A3",
Note::Bb3 => "A#/Bb3",
Note::B3 => "B3",
Note::C4 => "C4",
Note::Db4 => "C#/Db4",
Note::D4 => "D4",
Note::Eb4 => "D#/Eb4",
Note::E4 => "E4",
Note::F4 => "F4",
Note::Gb4 => "F#/Gb4",
Note::G4 => "G4",
Note::Ab4 => "G#/Ab4",
Note::A4 => "A4",
Note::Bb4 => "A#/Bb4",
Note::B4 => "B4",
Note::C5 => "C5",
Note::Db5 => "C#/Db5",
Note::D5 => "D5",
Note::Eb5 => "D#/Eb5",
Note::E5 => "E5",
Note::F5 => "F5",
Note::Gb5 => "F#/Gb5",
Note::G5 => "G5",
Note::Ab5 => "G#/Ab5",
Note::A5 => "A5",
Note::Bb5 => "A#/Bb5",
Note::B5 => "B5",
Note::C6 => "C6",
Note::Db6 => "C#/Db6",
Note::D6 => "D6",
Note::Eb6 => "D#/Eb6",
Note::E6 => "E6",
Note::F6 => "F6",
Note::Gb6 => "F#/Gb6",
Note::G6 => "G6",
Note::Ab6 => "G#/Ab6",
Note::A6 => "A6",
Note::Bb6 => "A#/Bb6",
Note::B6 => "B6",
Note::C7 => "C7",
Note::Db7 => "C#/Db7",
Note::D7 => "D7",
Note::Eb7 => "D#/Eb7",
Note::E7 => "E7",
Note::F7 => "F7",
Note::Gb7 => "F#/Gb7",
Note::G7 => "G7",
Note::Ab7 => "G#/Ab7",
Note::A7 => "A7",
Note::Bb7 => "A#/Bb7",
Note::B7 => "B7",
Note::C8 => "C8",
Note::Db8 => "C#/Db8",
Note::D8 => "D8",
Note::Eb8 => "D#/Eb8",
Note::E8 => "E8",
Note::F8 => "F8",
Note::Gb8 => "F#/Gb8",
Note::G8 => "G8",
Note::Ab8 => "G#/Ab8",
Note::A8 => "A8",
Note::Bb8 => "A#/Bb8",
Note::B8 => "B8",
Note::C9 => "C9",
Note::Db9 => "C#/Db9",
Note::D9 => "D9",
Note::Eb9 => "D#/Eb9",
Note::E9 => "E9",
Note::F9 => "F9",
Note::Gb9 => "F#/Gb9",
Note::G9 => "G9",
}
}
}
impl TryFrom<u8> for Note {
type Error = Error;
#[inline(always)]
fn try_from(note: u8) -> Result<Note, Error> {
if note > 127 {
Err(Error::NoteOutOfRange)
} else {
Ok(unsafe { Note::from_u8_unchecked(note) })
}
}
}
impl From<crate::U7> for Note {
#[inline(always)]
fn from(note: crate::U7) -> Note {
unsafe { Note::from_u8_unchecked(u8::from(note)) }
}
}
impl From<Note> for u8 {
#[inline(always)]
fn from(note: Note) -> u8 {
note as u8
}
}
impl fmt::Debug for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", self.to_str(), *self as u8)
}
}
impl fmt::Display for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
#[cfg(test)]
mod test {
use super::*;
#[cfg(feature = "std")]
#[test]
fn note_to_frequency_a440() {
let a440_f64 = Note::A4.to_freq_f64();
assert!((a440_f64 - 440.0).abs() < 1E-10, "{} != 440", a440_f64);
let a440_f32 = Note::A4.to_freq_f32();
assert!((a440_f32 - 440.0).abs() < 1E-3, "{} != 440", a440_f32);
}
#[cfg(feature = "std")]
#[test]
fn note_to_frequency_f64() {
for midi in 0u8..=127 {
let note = Note::from_u8_lossy(midi);
let expected = {
let exp = (f64::from(midi) + 36.376_316_562_295_91) / 12.0;
2.0_f64.powf(exp)
};
let got = note.to_freq_f64();
let rel_err = (got - expected).abs() / expected;
assert!(
rel_err < 1e-4,
"note={}: got {} expected {} rel_err={}",
midi,
got,
expected,
rel_err
);
}
}
#[cfg(feature = "std")]
#[test]
fn note_to_frequency_f32() {
for midi in 0u8..=127 {
let note = Note::from_u8_lossy(midi);
let expected = {
let exp = (f32::from(midi) + 36.376_316_f32) / 12.0;
2.0_f32.powf(exp)
};
let got = note.to_freq_f32();
let rel_err = (got - expected).abs() / expected;
assert!(
rel_err < 1e-4,
"note={}: got {} expected {} rel_err={}",
midi,
got,
expected,
rel_err
);
}
}
#[test]
fn step() {
assert_eq!(Note::CMinus1.step(12), Ok(Note::C0));
assert_eq!(Note::C0.step(-12), Ok(Note::CMinus1));
assert_eq!(Note::B3.step(1), Ok(Note::C4));
assert_eq!(Note::B3.step(100), Err(Error::NoteOutOfRange));
assert_eq!(Note::B3.step(-100), Err(Error::NoteOutOfRange));
}
#[cfg(feature = "std")]
#[test]
fn test_debug() {
let debug_str = format!("{:?}", Note::Bb3);
assert!(debug_str.contains("Bb"), "{}", debug_str);
assert!(debug_str.contains('3'), "{}", debug_str);
assert!(debug_str.contains("A#"), "{}", debug_str);
}
}