use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::dsl::note_to_hz;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum InstrumentError {
NotStreamable,
BadPatch(String),
}
impl fmt::Display for InstrumentError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InstrumentError::NotStreamable => {
write!(
f,
"instrument patch is not streamable (can't play in real time)"
)
}
InstrumentError::BadPatch(e) => write!(f, "instrument patch is invalid: {e}"),
}
}
}
impl std::error::Error for InstrumentError {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Note(pub u8);
impl Note {
pub const C4: Note = Note(60);
pub const A4: Note = Note(69);
pub fn freq(self) -> f32 {
440.0 * 2f32.powf((self.0 as f32 - 69.0) / 12.0)
}
pub fn midi(self) -> u8 {
self.0
}
pub fn parse(s: &str) -> Option<Note> {
let hz = note_to_hz(s)?;
let midi = crate::dsp::hz_to_midi(hz).round();
if (0.0..=127.0).contains(&midi) {
Some(Note(midi as u8))
} else {
None
}
}
pub fn transpose(self, semitones: i32) -> Note {
Note((self.0 as i32 + semitones).clamp(0, 127) as u8)
}
}
impl fmt::Display for Note {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const NAMES: [&str; 12] = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
write!(
f,
"{}{}",
NAMES[(self.0 % 12) as usize],
self.0 as i32 / 12 - 1
)
}
}
impl FromStr for Note {
type Err = ();
fn from_str(s: &str) -> Result<Note, ()> {
Note::parse(s).ok_or(())
}
}
impl From<u8> for Note {
fn from(midi: u8) -> Note {
Note(midi)
}
}