Skip to main content

clack_common/events/spaces/
core.rs

1use crate::events::event_types::*;
2use crate::events::{Event, EventSpace, UnknownEvent};
3use std::ffi::CStr;
4use std::fmt::{Debug, Formatter};
5
6/// Core CLAP event set.
7///
8/// This enum represents the standard events defined in the CLAP specification
9/// (e.g. [`NoteOnEvent`], [`ParamValueEvent`]).
10///
11/// It can be constructed from an [`UnknownEvent`] when the event is one of the core types.
12///
13/// # Example
14/// Converting from [`UnknownEvent`] to a [`CoreEventSpace`]:
15/// ```no_run
16/// use clack_common::events::UnknownEvent;
17/// use clack_common::events::spaces::CoreEventSpace;
18///
19/// fn handle_event(event: &UnknownEvent) {
20///     if let Some(ev) = event.as_core_event() {
21///         match ev {
22///             CoreEventSpace::NoteOn(note) => { /* handle note‑on */ }
23///             CoreEventSpace::ParamValue(param) => { /* handle param change */ }
24///             _ => {}
25///         }
26///     }
27/// }
28/// ```
29#[derive(Copy, Clone, PartialEq)]
30pub enum CoreEventSpace<'a> {
31    /// A note-on event (key press / attack)
32    NoteOn(&'a NoteOnEvent),
33    /// A note-off event (key release)
34    NoteOff(&'a NoteOffEvent),
35    /// A note choke (forcibly ends a note)
36    NoteChoke(&'a NoteChokeEvent),
37    /// A note end (natural end of note)
38    NoteEnd(&'a NoteEndEvent),
39    /// A note expression change (e.g. per-note pitch)
40    NoteExpression(&'a NoteExpressionEvent),
41    /// A parameter value change
42    ParamValue(&'a ParamValueEvent),
43    /// A parameter modulation change
44    ParamMod(&'a ParamModEvent),
45    /// Begin of a gesture (automation touch)
46    ParamGestureBegin(&'a ParamGestureBeginEvent),
47    /// End of a gesture (automation release)
48    ParamGestureEnd(&'a ParamGestureEndEvent),
49    /// Transport info (playhead, tempo, etc.)
50    Transport(&'a TransportEvent),
51    /// MIDI 1.0 event
52    Midi(&'a MidiEvent),
53    /// MIDI 2.0 event
54    Midi2(&'a Midi2Event),
55    /// MIDI SysEx event
56    MidiSysEx(&'a MidiSysExEvent),
57}
58
59// SAFETY: The core event space has the empty C string for a name.
60unsafe impl<'a> EventSpace<'a> for CoreEventSpace<'a> {
61    const NAME: &'static CStr = c"";
62
63    /// Attempts to reinterpret a raw [`UnknownEvent`] as one of the core event types.
64    ///
65    /// Returns `None` if the event does not belong to this event space.
66    unsafe fn from_unknown(event: &'a UnknownEvent) -> Option<Self> {
67        use CoreEventSpace::*;
68
69        match event.header().type_id() {
70            NoteOnEvent::TYPE_ID => Some(NoteOn(event.as_event_unchecked())),
71            NoteOffEvent::TYPE_ID => Some(NoteOff(event.as_event_unchecked())),
72            NoteChokeEvent::TYPE_ID => Some(NoteChoke(event.as_event_unchecked())),
73            NoteEndEvent::TYPE_ID => Some(NoteEnd(event.as_event_unchecked())),
74            NoteExpressionEvent::TYPE_ID => Some(NoteExpression(event.as_event_unchecked())),
75            ParamValueEvent::TYPE_ID => Some(ParamValue(event.as_event_unchecked())),
76            ParamModEvent::TYPE_ID => Some(ParamMod(event.as_event_unchecked())),
77            ParamGestureBeginEvent::TYPE_ID => Some(ParamGestureBegin(event.as_event_unchecked())),
78            ParamGestureEndEvent::TYPE_ID => Some(ParamGestureEnd(event.as_event_unchecked())),
79            TransportEvent::TYPE_ID => Some(Transport(event.as_event_unchecked())),
80            MidiEvent::TYPE_ID => Some(Midi(event.as_event_unchecked())),
81            Midi2Event::TYPE_ID => Some(Midi2(event.as_event_unchecked())),
82            MidiSysExEvent::TYPE_ID => Some(MidiSysEx(event.as_event_unchecked())),
83            _ => None,
84        }
85    }
86
87    #[inline]
88    fn as_unknown(&self) -> &'a UnknownEvent {
89        match self {
90            CoreEventSpace::NoteOn(e) => e.as_unknown(),
91            CoreEventSpace::NoteOff(e) => e.as_unknown(),
92            CoreEventSpace::NoteChoke(e) => e.as_unknown(),
93            CoreEventSpace::NoteEnd(e) => e.as_unknown(),
94            CoreEventSpace::NoteExpression(e) => e.as_unknown(),
95            CoreEventSpace::ParamValue(e) => e.as_unknown(),
96            CoreEventSpace::ParamMod(e) => e.as_unknown(),
97            CoreEventSpace::Transport(e) => e.as_unknown(),
98            CoreEventSpace::Midi(e) => e.as_unknown(),
99            CoreEventSpace::Midi2(e) => e.as_unknown(),
100            CoreEventSpace::MidiSysEx(e) => e.as_unknown(),
101            CoreEventSpace::ParamGestureBegin(e) => e.as_unknown(),
102            CoreEventSpace::ParamGestureEnd(e) => e.as_unknown(),
103        }
104    }
105}
106
107impl Debug for CoreEventSpace<'_> {
108    #[inline]
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        match self {
111            CoreEventSpace::NoteOn(e) => Debug::fmt(e, f),
112            CoreEventSpace::NoteOff(e) => Debug::fmt(e, f),
113            CoreEventSpace::NoteChoke(e) => Debug::fmt(e, f),
114            CoreEventSpace::NoteEnd(e) => Debug::fmt(e, f),
115            CoreEventSpace::NoteExpression(e) => Debug::fmt(e, f),
116            CoreEventSpace::ParamValue(e) => Debug::fmt(e, f),
117            CoreEventSpace::ParamMod(e) => Debug::fmt(e, f),
118            CoreEventSpace::ParamGestureBegin(e) => Debug::fmt(e, f),
119            CoreEventSpace::ParamGestureEnd(e) => Debug::fmt(e, f),
120            CoreEventSpace::Transport(e) => Debug::fmt(e, f),
121            CoreEventSpace::Midi(e) => Debug::fmt(e, f),
122            CoreEventSpace::Midi2(e) => Debug::fmt(e, f),
123            CoreEventSpace::MidiSysEx(e) => Debug::fmt(e, f),
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::events::io::{EventBuffer, OutputEvents};
132    use crate::utils::ClapId;
133
134    const PARAM: ClapId = ClapId::new(0);
135
136    /// Regression test for <https://github.com/prokopyl/clack/issues/97>.
137    ///
138    /// A parameter gesture could be written through clack, but never read back through it:
139    /// [`CoreEventSpace::from_unknown`] had no match arm for either gesture type, so
140    /// `as_core_event()` returned [`None`] for both.
141    #[test]
142    pub fn decodes_param_gestures_from_core_event_space() {
143        let mut buffer = EventBuffer::new();
144        let mut out = OutputEvents::from_buffer(&mut buffer);
145
146        out.try_push(ParamGestureBeginEvent::new(0, PARAM)).unwrap();
147        out.try_push(ParamGestureEndEvent::new(1, PARAM)).unwrap();
148
149        let mut events = buffer.iter();
150
151        let begin = events.next().unwrap();
152        // This always worked: it checks the type ID directly.
153        assert!(begin.as_event::<ParamGestureBeginEvent>().is_some());
154        // This goes through CoreEventSpace::from_unknown, and used to return None.
155        assert!(matches!(
156            begin.as_core_event(),
157            Some(CoreEventSpace::ParamGestureBegin(_))
158        ));
159
160        let end = events.next().unwrap();
161        assert!(end.as_event::<ParamGestureEndEvent>().is_some());
162        assert!(matches!(
163            end.as_core_event(),
164            Some(CoreEventSpace::ParamGestureEnd(_))
165        ));
166
167        assert!(events.next().is_none());
168    }
169}