#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Note {
pub(crate) pitch: u8,
pub(crate) velocity: u8,
pub(crate) state: NoteState,
}
impl Note {
pub fn as_midi_message(&self, channel: Channel) -> [u8; 3] {
[
self.state.as_midi_message(channel),
self.pitch,
self.velocity,
]
}
pub fn pitch(&self) -> Pitch {
Pitch(self.pitch)
}
pub fn velocity(&self) -> u8 {
self.velocity
}
pub fn state(&self) -> NoteState {
self.state
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteState {
On,
Off,
}
impl NoteState {
pub fn as_midi_message(&self, channel: Channel) -> u8 {
channel.0
| match self {
Self::On => 0x90,
Self::Off => 0x80,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Channel(u8);
impl Channel {
pub fn new(channel: u8) -> Result<Self, InvalidMidiChannel> {
if channel > 15 {
return Err(InvalidMidiChannel::new(channel));
}
Ok(Self(channel))
}
pub fn number(&self) -> u8 {
self.0
}
pub fn all_sound_off(&self) -> [u8; 3] {
[0xB0 | self.0, 120, 0]
}
}
pub type InvalidMidiChannel = crate::util::OutOfBounds<15>;
pub type InvalidMidiNote = crate::util::OutOfBounds<127>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Pitch(u8);
impl Pitch {
pub const fn new(note_number: u8) -> Result<Self, InvalidMidiNote> {
if note_number > 127 {
return Err(InvalidMidiNote::new(note_number));
}
Ok(Self(note_number))
}
pub fn note_number(&self) -> u8 {
self.0
}
}
impl core::fmt::Display for Pitch {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
const NAMES: [&str; 12] = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
let note = self.0 % 12;
let octave = (self.0 / 12) as i8 - 1;
write!(f, "{}{octave}", NAMES[note as usize])
}
}
impl core::str::FromStr for Pitch {
type Err = ParsePitchError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(if let Ok(note_number) = s.parse() {
note_number
} else {
let mut octave_start = 1;
let mut chars = s.chars();
let note_name = chars.next().ok_or(Self::Err::Empty)?;
let (mut note, can_sharpen) = match note_name.to_ascii_uppercase() {
'C' => (0, true),
'D' => (2, true),
'E' => (4, false),
'F' => (5, true),
'G' => (7, true),
'A' => (9, true),
'B' => (11, false),
_ => return Err(Self::Err::InvalidNoteName(note_name)),
};
if let Some('#') = chars.next() {
if !can_sharpen {
return Err(Self::Err::InvalidSharp(note_name.to_ascii_uppercase()));
}
note += 1;
octave_start += 1;
}
let octave: i8 = s[octave_start..].parse().map_err(Self::Err::OctaveText)?;
let octave: u8 = (octave + 1).try_into().map_err(Self::Err::OctaveNumber)?;
octave * 12 + note
})
.map_err(Self::Err::OutOfRange)
}
}
#[derive(Debug)]
pub enum ParsePitchError {
Empty,
InvalidNoteName(char),
InvalidSharp(char),
OctaveText(core::num::ParseIntError),
OctaveNumber(core::num::TryFromIntError),
OutOfRange(InvalidMidiNote),
}
impl core::fmt::Display for ParsePitchError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ParsePitchError::Empty => write!(f, "Provided string was empty"),
ParsePitchError::InvalidNoteName(c) => write!(f, "{c} is not a valid note name"),
ParsePitchError::InvalidSharp(c) => {
write!(f, "Note {c} cannot have a sharp attached to it")
}
ParsePitchError::OctaveText(e) => write!(f, "Failed to parse octave number: {e}"),
ParsePitchError::OctaveNumber(e) => {
write!(
f,
"Could not convert octave number into unsigned integer: {e}"
)
}
ParsePitchError::OutOfRange(e) => {
write!(f, "{e}")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParsePitchError {}