Skip to main content

aether_midi/
event.rs

1//! MIDI event types.
2
3use serde::{Deserialize, Serialize};
4
5/// A single MIDI event with timestamp.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct MidiEvent {
8    /// Sample-accurate timestamp (samples since engine start).
9    pub timestamp: u64,
10    /// MIDI channel (0–15).
11    pub channel: u8,
12    /// The event kind.
13    pub kind: MidiEventKind,
14}
15
16/// All MIDI event types Aether handles.
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub enum MidiEventKind {
19    /// Key pressed. note=0–127, velocity=0–127.
20    NoteOn { note: u8, velocity: u8 },
21    /// Key released. note=0–127, velocity=0–127 (release velocity).
22    NoteOff { note: u8, velocity: u8 },
23    /// Pitch wheel. value=-8192 to +8191 (0 = center).
24    PitchBend { value: i16 },
25    /// Channel aftertouch (pressure on any held key).
26    ChannelPressure { pressure: u8 },
27    /// Polyphonic aftertouch (pressure on a specific key).
28    PolyPressure { note: u8, pressure: u8 },
29    /// Control Change. cc=controller number, value=0–127.
30    ControlChange { cc: u8, value: u8 },
31    /// Program Change. program=0–127.
32    ProgramChange { program: u8 },
33    /// All notes off (panic).
34    AllNotesOff,
35    /// All sound off (immediate silence).
36    AllSoundOff,
37}
38
39impl MidiEvent {
40    /// Parse raw MIDI bytes into a MidiEvent.
41    /// Returns None for unsupported or malformed messages.
42    pub fn from_bytes(bytes: &[u8], timestamp: u64) -> Option<Self> {
43        if bytes.is_empty() { return None; }
44        let status = bytes[0];
45        let channel = status & 0x0F;
46        let kind_byte = status >> 4;
47
48        let kind = match kind_byte {
49            0x8 => {
50                // Note Off
51                if bytes.len() < 3 { return None; }
52                MidiEventKind::NoteOff { note: bytes[1] & 0x7F, velocity: bytes[2] & 0x7F }
53            }
54            0x9 => {
55                if bytes.len() < 3 { return None; }
56                let vel = bytes[2] & 0x7F;
57                if vel == 0 {
58                    // Note On with velocity 0 = Note Off
59                    MidiEventKind::NoteOff { note: bytes[1] & 0x7F, velocity: 0 }
60                } else {
61                    MidiEventKind::NoteOn { note: bytes[1] & 0x7F, velocity: vel }
62                }
63            }
64            0xA => {
65                if bytes.len() < 3 { return None; }
66                MidiEventKind::PolyPressure { note: bytes[1] & 0x7F, pressure: bytes[2] & 0x7F }
67            }
68            0xB => {
69                if bytes.len() < 3 { return None; }
70                let cc = bytes[1] & 0x7F;
71                let val = bytes[2] & 0x7F;
72                match cc {
73                    120 => MidiEventKind::AllSoundOff,
74                    123 => MidiEventKind::AllNotesOff,
75                    _ => MidiEventKind::ControlChange { cc, value: val },
76                }
77            }
78            0xC => {
79                if bytes.len() < 2 { return None; }
80                MidiEventKind::ProgramChange { program: bytes[1] & 0x7F }
81            }
82            0xD => {
83                if bytes.len() < 2 { return None; }
84                MidiEventKind::ChannelPressure { pressure: bytes[1] & 0x7F }
85            }
86            0xE => {
87                if bytes.len() < 3 { return None; }
88                let lsb = bytes[1] as i16;
89                let msb = bytes[2] as i16;
90                let value = ((msb << 7) | lsb) - 8192;
91                MidiEventKind::PitchBend { value }
92            }
93            _ => return None,
94        };
95
96        Some(MidiEvent { timestamp, channel, kind })
97    }
98
99    /// Is this a note-on event?
100    pub fn is_note_on(&self) -> bool {
101        matches!(self.kind, MidiEventKind::NoteOn { .. })
102    }
103
104    /// Is this a note-off event?
105    pub fn is_note_off(&self) -> bool {
106        matches!(self.kind, MidiEventKind::NoteOff { .. })
107    }
108
109    /// Get the MIDI note number if this is a note event.
110    pub fn note(&self) -> Option<u8> {
111        match self.kind {
112            MidiEventKind::NoteOn { note, .. } | MidiEventKind::NoteOff { note, .. } => Some(note),
113            _ => None,
114        }
115    }
116
117    /// Get velocity (0–127) if this is a note event.
118    pub fn velocity(&self) -> Option<u8> {
119        match self.kind {
120            MidiEventKind::NoteOn { velocity, .. } | MidiEventKind::NoteOff { velocity, .. } => Some(velocity),
121            _ => None,
122        }
123    }
124
125    /// Convert velocity to linear amplitude (0.0–1.0).
126    pub fn velocity_linear(&self) -> f32 {
127        self.velocity().map(|v| v as f32 / 127.0).unwrap_or(0.0)
128    }
129}
130
131/// Standard MIDI CC numbers.
132pub mod cc {
133    pub const MOD_WHEEL: u8 = 1;
134    pub const BREATH: u8 = 2;
135    pub const FOOT_PEDAL: u8 = 4;
136    pub const PORTAMENTO_TIME: u8 = 5;
137    pub const VOLUME: u8 = 7;
138    pub const BALANCE: u8 = 8;
139    pub const PAN: u8 = 10;
140    pub const EXPRESSION: u8 = 11;
141    pub const SUSTAIN_PEDAL: u8 = 64;
142    pub const PORTAMENTO: u8 = 65;
143    pub const SOSTENUTO: u8 = 66;
144    pub const SOFT_PEDAL: u8 = 67;
145    pub const LEGATO: u8 = 68;
146    pub const REVERB_SEND: u8 = 91;
147    pub const CHORUS_SEND: u8 = 93;
148}