Skip to main content

truce_rack_core/
events.rs

1//! Sample-accurate event list shared between MIDI / parameter
2//! automation / transport flags.
3//!
4//! An [`EventList`] arrives sorted by `sample_offset` and gets
5//! consumed by the plugin during one block of `process`. The
6//! output `EventList` on [`crate::ProcessContext`] is the
7//! plugin's path back to the host for outbound MIDI and
8//! parameter touches.
9
10use smallvec::SmallVec;
11
12/// One event with sample-accurate timing.
13#[derive(Debug, Clone, Copy)]
14pub struct Event {
15    /// Sample offset within the current `process` block.
16    pub sample_offset: u32,
17    /// Event payload.
18    pub body: EventBody,
19}
20
21/// What this event carries.
22#[derive(Debug, Clone, Copy)]
23pub enum EventBody {
24    /// MIDI 1.0 / 2.0 message.
25    Midi(MidiData),
26    /// Host-driven parameter automation point.
27    ParamValue {
28        /// Parameter id from [`crate::ParameterInfo::id`].
29        param_id: u32,
30        /// New value in the parameter's native range.
31        value: f64,
32    },
33    /// Plugin-emitted "user touched this parameter" notification.
34    /// Hosts use the touch / release pair to delimit a gesture
35    /// for undo grouping and automation.
36    ParamGesture {
37        /// Parameter id.
38        param_id: u32,
39        /// `true` = begin gesture, `false` = end.
40        active: bool,
41    },
42    /// Host transport state changed mid-block (e.g. user hit
43    /// play between samples 256 and 257). Plugins that care
44    /// about exact transport flip points read these out of the
45    /// input event list.
46    TransportFlag(TransportFlag),
47}
48
49/// Sub-flags describing transport state transitions.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TransportFlag {
52    /// Playback started.
53    PlayStart,
54    /// Playback stopped.
55    PlayStop,
56    /// Recording armed → engaged.
57    RecordStart,
58    /// Recording stopped.
59    RecordStop,
60    /// Loop boundary crossed (host jumped from end to start).
61    Looped,
62}
63
64/// MIDI message body.
65///
66/// MIDI 1.0 channel-voice messages are first-class; system
67/// real-time and `SysEx` ride in [`MidiData::Raw`] as raw bytes
68/// for the rare hosts that care.
69#[derive(Debug, Clone, Copy)]
70pub enum MidiData {
71    /// Note On — velocity 0 is treated as Note Off per the
72    /// MIDI 1.0 spec, but we represent that explicitly via
73    /// [`MidiData::NoteOff`] when known.
74    NoteOn {
75        /// MIDI channel, 0-15.
76        channel: u8,
77        /// Note number, 0-127.
78        note: u8,
79        /// Velocity, 0-127.
80        velocity: u8,
81    },
82    /// Note Off.
83    NoteOff {
84        /// MIDI channel, 0-15.
85        channel: u8,
86        /// Note number, 0-127.
87        note: u8,
88        /// Release velocity, 0-127.
89        velocity: u8,
90    },
91    /// Polyphonic key pressure.
92    PolyAftertouch {
93        /// MIDI channel, 0-15.
94        channel: u8,
95        /// Note number, 0-127.
96        note: u8,
97        /// Pressure, 0-127.
98        pressure: u8,
99    },
100    /// Control change.
101    ControlChange {
102        /// MIDI channel, 0-15.
103        channel: u8,
104        /// Controller number, 0-127.
105        controller: u8,
106        /// Value, 0-127.
107        value: u8,
108    },
109    /// Program change.
110    ProgramChange {
111        /// MIDI channel, 0-15.
112        channel: u8,
113        /// Program number, 0-127.
114        program: u8,
115    },
116    /// Channel pressure.
117    ChannelAftertouch {
118        /// MIDI channel, 0-15.
119        channel: u8,
120        /// Pressure, 0-127.
121        pressure: u8,
122    },
123    /// Pitch bend, 14-bit (0-16383, 8192 = center).
124    PitchBend {
125        /// MIDI channel, 0-15.
126        channel: u8,
127        /// Bend value, 0-16383.
128        value: u16,
129    },
130    /// Raw MIDI bytes — system real-time, `SysEx` fragments,
131    /// anything the channel-voice variants don't cover.
132    /// `len` bytes of `data` are meaningful; trailing bytes
133    /// are undefined. Cap of 8 covers MIDI 2.0 UMP 64-bit and
134    /// most system messages without spilling to the heap.
135    Raw {
136        /// Number of meaningful bytes in `data`.
137        len: u8,
138        /// Message bytes, big-endian.
139        data: [u8; 8],
140    },
141}
142
143/// Reasonable inline capacity for the per-block event list.
144/// Few hosts produce more than ~16 events per audio block; sizing
145/// the inline buffer this way keeps the audio thread out of the
146/// allocator for the vast majority of blocks.
147const EVENT_LIST_INLINE: usize = 32;
148
149/// Sample-ordered event buffer used for one `process` block.
150///
151/// Backed by `SmallVec<[Event; 32]>`: 32 inline entries cover
152/// almost every block without heap allocation; bursts spill to
153/// the heap rather than getting dropped. Cleared between blocks
154/// by [`EventList::clear`] (keeps the heap allocation when one
155/// was forced).
156#[derive(Debug, Default, Clone)]
157pub struct EventList {
158    events: SmallVec<[Event; EVENT_LIST_INLINE]>,
159}
160
161impl EventList {
162    /// An empty list with inline capacity for 32 events before
163    /// spilling to the heap.
164    #[must_use]
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    /// Build from an existing slice.
170    #[must_use]
171    pub fn from_slice(events: &[Event]) -> Self {
172        Self {
173            events: SmallVec::from_slice(events),
174        }
175    }
176
177    /// Append an event. Caller is responsible for keeping the
178    /// list sample-offset-sorted.
179    pub fn push(&mut self, event: Event) {
180        self.events.push(event);
181    }
182
183    /// Reset to empty without dropping any heap allocation.
184    pub fn clear(&mut self) {
185        self.events.clear();
186    }
187
188    /// Number of events.
189    #[must_use]
190    pub fn len(&self) -> usize {
191        self.events.len()
192    }
193
194    /// `true` when the list contains no events.
195    #[must_use]
196    pub fn is_empty(&self) -> bool {
197        self.events.is_empty()
198    }
199
200    /// Borrow the events as a slice.
201    #[must_use]
202    pub fn as_slice(&self) -> &[Event] {
203        &self.events
204    }
205
206    /// Iterate over the events.
207    pub fn iter(&self) -> std::slice::Iter<'_, Event> {
208        self.events.iter()
209    }
210}
211
212impl<'a> IntoIterator for &'a EventList {
213    type Item = &'a Event;
214    type IntoIter = std::slice::Iter<'a, Event>;
215    fn into_iter(self) -> Self::IntoIter {
216        self.events.iter()
217    }
218}