use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MidiEvent {
pub timestamp: u64,
pub channel: u8,
pub kind: MidiEventKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MidiEventKind {
NoteOn { note: u8, velocity: u8 },
NoteOff { note: u8, velocity: u8 },
PitchBend { value: i16 },
ChannelPressure { pressure: u8 },
PolyPressure { note: u8, pressure: u8 },
ControlChange { cc: u8, value: u8 },
ProgramChange { program: u8 },
AllNotesOff,
AllSoundOff,
}
impl MidiEvent {
pub fn from_bytes(bytes: &[u8], timestamp: u64) -> Option<Self> {
if bytes.is_empty() { return None; }
let status = bytes[0];
let channel = status & 0x0F;
let kind_byte = status >> 4;
let kind = match kind_byte {
0x8 => {
if bytes.len() < 3 { return None; }
MidiEventKind::NoteOff { note: bytes[1] & 0x7F, velocity: bytes[2] & 0x7F }
}
0x9 => {
if bytes.len() < 3 { return None; }
let vel = bytes[2] & 0x7F;
if vel == 0 {
MidiEventKind::NoteOff { note: bytes[1] & 0x7F, velocity: 0 }
} else {
MidiEventKind::NoteOn { note: bytes[1] & 0x7F, velocity: vel }
}
}
0xA => {
if bytes.len() < 3 { return None; }
MidiEventKind::PolyPressure { note: bytes[1] & 0x7F, pressure: bytes[2] & 0x7F }
}
0xB => {
if bytes.len() < 3 { return None; }
let cc = bytes[1] & 0x7F;
let val = bytes[2] & 0x7F;
match cc {
120 => MidiEventKind::AllSoundOff,
123 => MidiEventKind::AllNotesOff,
_ => MidiEventKind::ControlChange { cc, value: val },
}
}
0xC => {
if bytes.len() < 2 { return None; }
MidiEventKind::ProgramChange { program: bytes[1] & 0x7F }
}
0xD => {
if bytes.len() < 2 { return None; }
MidiEventKind::ChannelPressure { pressure: bytes[1] & 0x7F }
}
0xE => {
if bytes.len() < 3 { return None; }
let lsb = bytes[1] as i16;
let msb = bytes[2] as i16;
let value = ((msb << 7) | lsb) - 8192;
MidiEventKind::PitchBend { value }
}
_ => return None,
};
Some(MidiEvent { timestamp, channel, kind })
}
pub fn is_note_on(&self) -> bool {
matches!(self.kind, MidiEventKind::NoteOn { .. })
}
pub fn is_note_off(&self) -> bool {
matches!(self.kind, MidiEventKind::NoteOff { .. })
}
pub fn note(&self) -> Option<u8> {
match self.kind {
MidiEventKind::NoteOn { note, .. } | MidiEventKind::NoteOff { note, .. } => Some(note),
_ => None,
}
}
pub fn velocity(&self) -> Option<u8> {
match self.kind {
MidiEventKind::NoteOn { velocity, .. } | MidiEventKind::NoteOff { velocity, .. } => Some(velocity),
_ => None,
}
}
pub fn velocity_linear(&self) -> f32 {
self.velocity().map(|v| v as f32 / 127.0).unwrap_or(0.0)
}
}
pub mod cc {
pub const MOD_WHEEL: u8 = 1;
pub const BREATH: u8 = 2;
pub const FOOT_PEDAL: u8 = 4;
pub const PORTAMENTO_TIME: u8 = 5;
pub const VOLUME: u8 = 7;
pub const BALANCE: u8 = 8;
pub const PAN: u8 = 10;
pub const EXPRESSION: u8 = 11;
pub const SUSTAIN_PEDAL: 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 REVERB_SEND: u8 = 91;
pub const CHORUS_SEND: u8 = 93;
}