conformal_component/events.rs
1//! Contains data structures representing _events_ sent to [`crate::synth::Synth`]s
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4enum NoteIDInternals {
5 NoteIDWithID(i32),
6 NoteIDFromPitch(u8),
7 NoteIDFromChannelID(i16),
8}
9
10/// Represents an identifier for a note
11///
12/// This is an opaque identifier that can be used to refer to a specific note
13/// that is playing.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15pub struct NoteID {
16 internals: NoteIDInternals,
17}
18
19impl NoteID {
20 /// Create a new `NoteID` from a numeric ID.
21 ///
22 /// Note that the `NoteID`s will be considered equal if they come from
23 /// the same numeric ID, and different if they come from different numeric IDs.
24 ///
25 /// # Examples
26 ///
27 /// ```
28 /// use conformal_component::events::NoteID;
29 /// assert_eq!(NoteID::from_id(42), NoteID::from_id(42));
30 /// assert_ne!(NoteID::from_id(42), NoteID::from_id(43));
31 /// ```
32 #[must_use]
33 pub const fn from_id(id: i32) -> Self {
34 Self {
35 internals: NoteIDInternals::NoteIDWithID(id),
36 }
37 }
38
39 #[doc(hidden)]
40 #[must_use]
41 pub const fn from_pitch(pitch: u8) -> Self {
42 Self {
43 internals: NoteIDInternals::NoteIDFromPitch(pitch),
44 }
45 }
46
47 #[doc(hidden)]
48 #[must_use]
49 pub const fn from_channel_for_mpe_quirks(id: i16) -> Self {
50 Self {
51 internals: NoteIDInternals::NoteIDFromChannelID(id),
52 }
53 }
54}
55
56#[doc(hidden)]
57#[must_use]
58pub fn to_vst_note_id(note_id: NoteID) -> i32 {
59 match note_id.internals {
60 NoteIDInternals::NoteIDWithID(id) => id,
61 NoteIDInternals::NoteIDFromPitch(_) | NoteIDInternals::NoteIDFromChannelID(_) => -1,
62 }
63}
64
65#[doc(hidden)]
66#[must_use]
67pub fn to_vst_note_channel_for_mpe_quirks(note_id: NoteID) -> i16 {
68 match note_id.internals {
69 NoteIDInternals::NoteIDFromChannelID(id) => id,
70 NoteIDInternals::NoteIDFromPitch(_) | NoteIDInternals::NoteIDWithID(_) => 0,
71 }
72}
73
74/// Contains data common to both `NoteOn` and `NoteOff` events.
75#[derive(Copy, Clone, Debug, PartialEq)]
76pub struct NoteData {
77 /// Opaque ID of the note.
78 pub id: NoteID,
79
80 /// Pitch of the note in terms of semitones higher than C-2
81 pub pitch: u8,
82
83 /// 0->1 velocity of the note on or off
84 pub velocity: f32,
85
86 /// Microtuning of the note in cents.
87 pub tuning: f32,
88}
89
90/// A specific type of note expression.
91#[derive(Copy, Clone, Debug, PartialEq)]
92pub enum NoteExpression {
93 /// Pitch bend note expression.
94 ///
95 /// This corresponds to the [`crate::synth::PITCH_BEND_PARAMETER`] controller and should
96 /// change the tuning of the note.
97 ///
98 /// This is expressed in semitones away from the root note of the note (which may itself
99 /// be affected by the global [`crate::synth::PITCH_BEND_PARAMETER`] controller).
100 PitchBend(f32),
101
102 /// Vertical movement note expression, meant to control some sort of timbre of the synth.
103 ///
104 /// This is called "slide" in some DAW UIs.
105 ///
106 /// This corresponds to the "timbre" controller ([`crate::synth::TIMBRE_PARAMETER`]), and
107 /// its effects must be combined with the global controller.
108 ///
109 /// This value varies from 0->1, 0 being the bottommost position,
110 /// and 1 being the topmost position.
111 Timbre(f32),
112
113 /// Depthwise note expression.
114 ///
115 /// This is called "Pressure" in some DAW UIs.
116 ///
117 /// This value varies from 0->1, 0 being neutral, and 1 being the maximum depth.
118 ///
119 /// This corresponds to the [`crate::synth::AFTERTOUCH_PARAMETER`] controller which
120 /// affects all notes. The total effect must be a combination of this per-note note
121 /// expression and the global controller.
122 Aftertouch(f32),
123}
124
125/// Contains data about note expression.
126#[derive(Copy, Clone, Debug, PartialEq)]
127pub struct NoteExpressionData {
128 /// Opaque ID of the note. This will always refer to a note that is
129 /// currently "on".
130 pub id: NoteID,
131
132 /// The expression that is being sent.
133 pub expression: NoteExpression,
134}
135
136/// The data associated with an event, independent of the time it occurred.
137#[derive(Clone, Debug, PartialEq)]
138pub enum Data {
139 /// A note began.
140 ///
141 /// This will never be sent while a note with the same ID is still playing.
142 NoteOn {
143 /// Data associated with the note.
144 data: NoteData,
145 },
146
147 /// A note ended.
148 ///
149 /// This will never be sent while a note with the same ID is not playing.
150 NoteOff {
151 /// Data associated with the note.
152 data: NoteData,
153 },
154
155 /// A note expression was sent.
156 ///
157 /// This will never be sent while a note with the same ID is not playing.
158 NoteExpression {
159 /// Data associated with the note expression.
160 data: NoteExpressionData,
161 },
162}
163
164/// An event that occurred at a specific time within a buffer.
165#[derive(Clone, Debug, PartialEq)]
166pub struct Event {
167 /// Number of sample frames after the beginning of the buffer that this event occurred
168 pub sample_offset: usize,
169
170 /// Data about the event!
171 pub data: Data,
172}
173
174/// Contains an iterator that yields events in order of increasing sample offset.
175///
176/// Invariants:
177/// - All events will have a sample offset in the range of the buffer
178/// - Events are sorted by `sample_offset`
179#[derive(Clone, Debug)]
180pub struct Events<I> {
181 events: I,
182}
183
184fn check_events_invariants<I: Iterator<Item = Event>>(iter: I, buffer_size: usize) -> bool {
185 let mut last = None;
186 for event in iter {
187 if event.sample_offset >= buffer_size {
188 return false;
189 }
190 if let Some(last) = last
191 && event.sample_offset < last
192 {
193 return false;
194 }
195 last = Some(event.sample_offset);
196 }
197 true
198}
199
200impl<I: Iterator<Item = Event> + Clone> Events<I> {
201 /// Create an `Events` object from the given iterator of events.
202 ///
203 /// Note that if any of the invariants are missed, this will return `None`.
204 pub fn new(events: I, buffer_size: usize) -> Option<Self> {
205 if check_events_invariants(events.clone(), buffer_size) {
206 Some(Self { events })
207 } else {
208 None
209 }
210 }
211}
212
213impl<I: Iterator<Item = Event>> IntoIterator for Events<I> {
214 type Item = Event;
215 type IntoIter = I;
216
217 fn into_iter(self) -> Self::IntoIter {
218 self.events
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::{Data, Event, Events, NoteData, NoteID};
225
226 static EXAMPLE_NOTE: NoteData = NoteData {
227 id: NoteID::from_pitch(60),
228 pitch: 60,
229 velocity: 1.0,
230 tuning: 0.0,
231 };
232
233 #[test]
234 fn out_of_order_events_rejected() {
235 assert!(
236 Events::new(
237 (&[
238 Event {
239 sample_offset: 5,
240 data: Data::NoteOn {
241 data: EXAMPLE_NOTE.clone()
242 }
243 },
244 Event {
245 sample_offset: 4,
246 data: Data::NoteOff {
247 data: EXAMPLE_NOTE.clone()
248 }
249 }
250 ])
251 .iter()
252 .cloned(),
253 10
254 )
255 .is_none()
256 )
257 }
258
259 #[test]
260 fn out_of_bounds_events_rejected() {
261 assert!(
262 Events::new(
263 (&[Event {
264 sample_offset: 50,
265 data: Data::NoteOn {
266 data: EXAMPLE_NOTE.clone()
267 }
268 },])
269 .iter()
270 .cloned(),
271 10
272 )
273 .is_none()
274 )
275 }
276
277 #[test]
278 fn empty_events_accepted() {
279 assert!(Events::new((&[]).iter().cloned(), 10).is_some())
280 }
281}