conformal_component/
events.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! Contains data structures representing _events_ sent to [`crate::synth::Synth`]s

#[cfg(test)]
mod tests;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum NoteIDInternals {
    NoteIDWithID(i32),
    NoteIDFromPitch(u8),
    NoteIDFromChannelID(i16),
}

/// Represents an identifier for a note
///
/// This is an opaque identifier that can be used to refer to a specific note
/// that is playing.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NoteID {
    internals: NoteIDInternals,
}

impl NoteID {
    /// Create a new `NoteID` from a numeric ID.
    ///
    /// Note that the `NoteID`s will be considered equal if they come from
    /// the same numeric ID, and different if they come from different numeric IDs.
    ///
    /// # Examples
    ///
    /// ```
    /// use conformal_component::events::NoteID;
    /// assert_eq!(NoteID::from_id(42), NoteID::from_id(42));
    /// assert_ne!(NoteID::from_id(42), NoteID::from_id(43));
    /// ```
    #[must_use]
    pub const fn from_id(id: i32) -> Self {
        Self {
            internals: NoteIDInternals::NoteIDWithID(id),
        }
    }

    #[doc(hidden)]
    #[must_use]
    pub const fn from_pitch(pitch: u8) -> Self {
        Self {
            internals: NoteIDInternals::NoteIDFromPitch(pitch),
        }
    }

    #[doc(hidden)]
    #[must_use]
    pub const fn from_channel_for_mpe_quirks(id: i16) -> Self {
        Self {
            internals: NoteIDInternals::NoteIDFromChannelID(id),
        }
    }
}

#[doc(hidden)]
#[must_use]
pub fn to_vst_note_id(note_id: NoteID) -> i32 {
    match note_id.internals {
        NoteIDInternals::NoteIDWithID(id) => id,
        NoteIDInternals::NoteIDFromPitch(_) | NoteIDInternals::NoteIDFromChannelID(_) => -1,
    }
}

#[doc(hidden)]
#[must_use]
pub fn to_vst_note_channel_for_mpe_quirks(note_id: NoteID) -> i16 {
    match note_id.internals {
        NoteIDInternals::NoteIDFromChannelID(id) => id,
        NoteIDInternals::NoteIDFromPitch(_) | NoteIDInternals::NoteIDWithID(_) => 0,
    }
}

/// Contains data common to both `NoteOn` and `NoteOff` events.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct NoteData {
    /// Opaque ID of the note.
    pub id: NoteID,

    /// Pitch of the note in terms of semitones higher than C-2
    pub pitch: u8,

    /// 0->1 velocity of the note on or off
    pub velocity: f32,

    /// Microtuning of the note in cents.
    pub tuning: f32,
}

/// A specific type of note expression.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum NoteExpression {
    /// Pitch bend note expression.
    ///
    /// This corresponds to the [`crate::synth::PITCH_BEND_PARAMETER`] controller and should
    /// change the tuning of the note.
    ///
    /// This is expressed in semitones away from the root note of the note (which may itself
    /// be affected by the global [`crate::synth::PITCH_BEND_PARAMETER`] controller).
    PitchBend(f32),

    /// Vertical movement note expression, meant to control some sort of timbre of the synth.
    ///
    /// This is called "slide" in some DAW UIs.
    ///
    /// This corresponds to the "timbre" controller ([`crate::synth::TIMBRE_PARAMETER`]), and
    /// its effects must be combined with the global controller.
    ///
    /// This value varies from 0->1, 0 being the bottommost position,
    /// and 1 being the topmost position.
    Timbre(f32),

    /// Depthwise note expression.
    ///
    /// This is called "Pressure" in some DAW UIs.
    ///
    /// This value varies from 0->1, 0 being neutral, and 1 being the maximum depth.
    ///
    /// This corresponds to the [`crate::synth::AFTERTOUCH_PARAMETER`] controller which
    /// affects all notes. The total effect must be a combination of this per-note note
    /// expression and the global controller.
    Aftertouch(f32),
}

/// Contains data about note expression.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct NoteExpressionData {
    /// Opaque ID of the note. This will always refer to a note that is
    /// currently "on".
    pub id: NoteID,

    /// The expression that is being sent.
    pub expression: NoteExpression,
}

/// The data associated with an event, independent of the time it occurred.
#[derive(Clone, Debug, PartialEq)]
pub enum Data {
    /// A note began.
    ///
    /// This will never be sent while a note with the same ID is still playing.
    NoteOn {
        /// Data associated with the note.
        data: NoteData,
    },

    /// A note ended.
    ///
    /// This will never be sent while a note with the same ID is not playing.
    NoteOff {
        /// Data associated with the note.
        data: NoteData,
    },

    /// A note expression was sent.
    ///
    /// This will never be sent while a note with the same ID is not playing.
    NoteExpression {
        /// Data associated with the note expression.
        data: NoteExpressionData,
    },
}

/// An event that occurred at a specific time within a buffer.
#[derive(Clone, Debug, PartialEq)]
pub struct Event {
    /// Number of sample frames after the beginning of the buffer that this event occurred
    pub sample_offset: usize,

    /// Data about the event!
    pub data: Data,
}

/// Contains an iterator that yields events in order of increasing sample offset.
///
/// Invariants:
///  - All events will have a sample offset in the range of the buffer
///  - Events are sorted by `sample_offset`
#[derive(Clone, Debug)]
pub struct Events<I> {
    events: I,
}

fn check_events_invariants<I: Iterator<Item = Event>>(iter: I, buffer_size: usize) -> bool {
    let mut last = None;
    for event in iter {
        if event.sample_offset >= buffer_size {
            return false;
        }
        if let Some(last) = last {
            if event.sample_offset < last {
                return false;
            }
        }
        last = Some(event.sample_offset);
    }
    true
}

impl<I: Iterator<Item = Event> + Clone> Events<I> {
    /// Create an `Events` object from the given iterator of events.
    ///
    /// Note that if any of the invariants are missed, this will return `None`.
    pub fn new(events: I, buffer_size: usize) -> Option<Self> {
        if check_events_invariants(events.clone(), buffer_size) {
            Some(Self { events })
        } else {
            None
        }
    }
}

impl<I: Iterator<Item = Event>> IntoIterator for Events<I> {
    type Item = Event;
    type IntoIter = I;

    fn into_iter(self) -> Self::IntoIter {
        self.events
    }
}