use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MidiChannel {
Ch1,
Ch2,
Ch3,
Ch4,
Ch5,
Ch6,
Ch7,
Ch8,
Ch9,
Ch10,
Ch11,
Ch12,
Ch13,
Ch14,
Ch15,
Ch16,
}
impl MidiChannel {
pub fn as_index(&self) -> u8 {
match self {
MidiChannel::Ch1 => 0,
MidiChannel::Ch2 => 1,
MidiChannel::Ch3 => 2,
MidiChannel::Ch4 => 3,
MidiChannel::Ch5 => 4,
MidiChannel::Ch6 => 5,
MidiChannel::Ch7 => 6,
MidiChannel::Ch8 => 7,
MidiChannel::Ch9 => 8,
MidiChannel::Ch10 => 9,
MidiChannel::Ch11 => 10,
MidiChannel::Ch12 => 11,
MidiChannel::Ch13 => 12,
MidiChannel::Ch14 => 13,
MidiChannel::Ch15 => 14,
MidiChannel::Ch16 => 15,
}
}
pub fn from_index(index: u8) -> Option<Self> {
match index {
0 => Some(MidiChannel::Ch1),
1 => Some(MidiChannel::Ch2),
2 => Some(MidiChannel::Ch3),
3 => Some(MidiChannel::Ch4),
4 => Some(MidiChannel::Ch5),
5 => Some(MidiChannel::Ch6),
6 => Some(MidiChannel::Ch7),
7 => Some(MidiChannel::Ch8),
8 => Some(MidiChannel::Ch9),
9 => Some(MidiChannel::Ch10),
10 => Some(MidiChannel::Ch11),
11 => Some(MidiChannel::Ch12),
12 => Some(MidiChannel::Ch13),
13 => Some(MidiChannel::Ch14),
14 => Some(MidiChannel::Ch15),
15 => Some(MidiChannel::Ch16),
_ => None,
}
}
}
impl fmt::Display for MidiChannel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Ch{}", self.as_index() + 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum MidiEvent {
NoteOn {
channel: MidiChannel,
note: u8,
velocity: u8,
},
NoteOff {
channel: MidiChannel,
note: u8,
velocity: u8,
},
ControlChange {
channel: MidiChannel,
controller: u8,
value: u8,
},
ProgramChange {
channel: MidiChannel,
program: u8,
},
PitchBend {
channel: MidiChannel,
value: u16,
},
ChannelAftertouch {
channel: MidiChannel,
pressure: u8,
},
PolyAftertouch {
channel: MidiChannel,
note: u8,
pressure: u8,
},
}
pub mod cc {
pub const BANK_SELECT_MSB: u8 = 0;
pub const MODULATION: u8 = 1;
pub const BREATH: u8 = 2;
pub const FOOT: u8 = 4;
pub const PORTAMENTO_TIME: u8 = 5;
pub const DATA_ENTRY_MSB: u8 = 6;
pub const VOLUME: u8 = 7;
pub const BALANCE: u8 = 8;
pub const PAN: u8 = 10;
pub const EXPRESSION: u8 = 11;
pub const SUSTAIN: u8 = 64;
pub const PORTAMENTO: u8 = 65;
pub const SOSTENUTO: u8 = 66;
pub const SOFT_PEDAL: u8 = 67;
pub const LEGATO: u8 = 68;
pub const HOLD_2: u8 = 69;
pub const SOUND_CONTROLLER_1: u8 = 70;
pub const SOUND_CONTROLLER_2: u8 = 71;
pub const SOUND_CONTROLLER_3: u8 = 72;
pub const SOUND_CONTROLLER_4: u8 = 73;
pub const SOUND_CONTROLLER_5: u8 = 74;
pub const SOUND_CONTROLLER_6: u8 = 75;
pub const SOUND_CONTROLLER_7: u8 = 76;
pub const SOUND_CONTROLLER_8: u8 = 77;
pub const SOUND_CONTROLLER_9: u8 = 78;
pub const SOUND_CONTROLLER_10: u8 = 79;
pub const GENERAL_PURPOSE_1: u8 = 80;
pub const GENERAL_PURPOSE_2: u8 = 81;
pub const GENERAL_PURPOSE_3: u8 = 82;
pub const GENERAL_PURPOSE_4: u8 = 83;
pub const PORTAMENTO_CONTROL: u8 = 84;
pub const REVERB_DEPTH: u8 = 91;
pub const TREMOLO_DEPTH: u8 = 92;
pub const CHORUS_DEPTH: u8 = 93;
pub const CELESTE_DEPTH: u8 = 94;
pub const PHASER_DEPTH: u8 = 95;
pub const DATA_INCREMENT: u8 = 96;
pub const DATA_DECREMENT: u8 = 97;
pub const NRPN_LSB: u8 = 98;
pub const NRPN_MSB: u8 = 99;
pub const RPN_LSB: u8 = 100;
pub const RPN_MSB: u8 = 101;
pub const ALL_SOUNDS_OFF: u8 = 120;
pub const RESET_ALL_CONTROLLERS: u8 = 121;
pub const LOCAL_CONTROL: u8 = 122;
pub const ALL_NOTES_OFF: u8 = 123;
pub const OMNI_MODE_OFF: u8 = 124;
pub const OMNI_MODE_ON: u8 = 125;
pub const MONO_MODE_ON: u8 = 126;
pub const POLY_MODE_ON: u8 = 127;
}
pub fn note_to_name(note: u8) -> String {
let note_names = [
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
];
let octave = (note as i32 / 12) - 2;
let note_in_octave = note % 12;
format!("{}{}", note_names[note_in_octave as usize], octave)
}
pub fn name_to_note(name: &str) -> Option<u8> {
let name = name.trim().to_uppercase();
let (note_part, octave_str) = if name.contains('#') {
let parts: Vec<&str> = name.split('#').collect();
if parts.len() != 2 {
return None;
}
(format!("{}#", parts[0]), parts[1])
} else if name.contains('B') && name.len() > 2 && &name[1..2] == "B" {
(format!("{}B", &name[0..1]), &name[2..])
} else {
let mut chars = name.chars();
let note = chars.next()?.to_string();
let octave = chars.as_str();
(note, octave)
};
let octave: i32 = octave_str.parse().ok()?;
let semitone = match note_part.as_str() {
"C" => 0,
"C#" | "DB" => 1,
"D" => 2,
"D#" | "EB" => 3,
"E" => 4,
"F" => 5,
"F#" | "GB" => 6,
"G" => 7,
"G#" | "AB" => 8,
"A" => 9,
"A#" | "BB" => 10,
"B" => 11,
_ => return None,
};
let midi_note = (octave + 2) * 12 + semitone;
if (0..=127).contains(&midi_note) {
Some(midi_note as u8)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_midi_conversions() {
assert_eq!(name_to_note("C3"), Some(60));
assert_eq!(name_to_note("C2"), Some(48));
assert_eq!(name_to_note("A3"), Some(69)); assert_eq!(name_to_note("C-2"), Some(0));
assert_eq!(name_to_note("G8"), Some(127));
assert_eq!(note_to_name(60), "C3");
assert_eq!(note_to_name(48), "C2");
assert_eq!(note_to_name(69), "A3");
assert_eq!(note_to_name(0), "C-2");
assert_eq!(note_to_name(127), "G8");
assert_eq!(name_to_note("C#3"), Some(61));
assert_eq!(name_to_note("Db3"), Some(61));
assert_eq!(name_to_note("F#3"), Some(66));
}
#[test]
fn test_midi_channel() {
assert_eq!(MidiChannel::Ch1.as_index(), 0);
assert_eq!(MidiChannel::Ch16.as_index(), 15);
assert_eq!(MidiChannel::from_index(0), Some(MidiChannel::Ch1));
assert_eq!(MidiChannel::from_index(15), Some(MidiChannel::Ch16));
assert_eq!(MidiChannel::from_index(16), None);
}
}