tono_core/instrument/
note.rs1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::dsl::note_to_hz;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum InstrumentError {
14 NotStreamable,
17 BadPatch(String),
19}
20
21impl fmt::Display for InstrumentError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 InstrumentError::NotStreamable => {
25 write!(
26 f,
27 "instrument patch is not streamable (can't play in real time)"
28 )
29 }
30 InstrumentError::BadPatch(e) => write!(f, "instrument patch is invalid: {e}"),
31 }
32 }
33}
34
35impl std::error::Error for InstrumentError {}
36
37#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, Serialize, Deserialize)]
39pub struct Note(pub u8);
40
41impl Note {
42 pub const C4: Note = Note(60);
44 pub const A4: Note = Note(69);
46
47 pub fn freq(self) -> f32 {
49 440.0 * 2f32.powf((self.0 as f32 - 69.0) / 12.0)
50 }
51
52 pub fn midi(self) -> u8 {
54 self.0
55 }
56
57 pub fn parse(s: &str) -> Option<Note> {
60 let hz = note_to_hz(s)?;
61 let midi = crate::dsp::hz_to_midi(hz).round();
62 if (0.0..=127.0).contains(&midi) {
63 Some(Note(midi as u8))
64 } else {
65 None
66 }
67 }
68
69 pub fn transpose(self, semitones: i32) -> Note {
71 Note((self.0 as i32 + semitones).clamp(0, 127) as u8)
72 }
73}
74
75impl fmt::Display for Note {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 const NAMES: [&str; 12] = [
78 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
79 ];
80 write!(
81 f,
82 "{}{}",
83 NAMES[(self.0 % 12) as usize],
84 self.0 as i32 / 12 - 1
85 )
86 }
87}
88
89impl FromStr for Note {
90 type Err = ();
91 fn from_str(s: &str) -> Result<Note, ()> {
92 Note::parse(s).ok_or(())
93 }
94}
95
96impl From<u8> for Note {
97 fn from(midi: u8) -> Note {
98 Note(midi)
99 }
100}