Skip to main content

clack_common/events/
pckn.rs

1#![allow(clippy::cast_possible_wrap)]
2
3use Match::*;
4
5/// A Port, Channel, Key, NoteId (PCKN) tuple.
6///
7/// CLAP addresses notes and voices use this 4-value tuple: `port`, `channel`, `key` and `note_id`.
8/// Each of the components in this PCKN can either be a specific value, or a wildcard that matches
9/// any value in that part of the tuple. This is representing using the [`Match`] enum.
10///
11/// For instance, a [`Pckn`] of `(0, 3, All, All)` will match all voices
12/// on channel 3 of port 0. And a [`Pckn`] of `(All, 0, 60, All)` will match
13/// all channel 0 key 60 voices, independent of port or note id.
14///
15/// See the [`matches`](Pckn::matches) for an implementation of the PCKN matching logic that you
16/// can use to match incoming events against active voices.
17#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
18pub struct Pckn {
19    /// The Note Port the plugin received this event on. See the Note Ports extension.
20    pub port_index: Match<u16>,
21    /// The Channel the note is on, akin to MIDI1 channels. This is usually in the `0..=15` range.
22    pub channel: Match<u16>,
23    /// The note's Key. This is the same representation as MIDI1 Key numbers,
24    /// `60` being a Middle C, and is in the `0..=127` range.
25    pub key: Match<u16>,
26    /// The unique ID of this note. This is used to distinguish between multiple overlapping
27    /// notes that play the same key. This is in the `0..i32::MAX` range.
28    pub note_id: Match<u32>,
29}
30
31impl Pckn {
32    /// Constructs a new PCKN tuple from each of its components.
33    pub fn new(
34        port: impl Into<Match<u16>>,
35        channel: impl Into<Match<u16>>,
36        key: impl Into<Match<u16>>,
37        note_id: impl Into<Match<u32>>,
38    ) -> Self {
39        Self {
40            port_index: port.into(),
41            channel: channel.into(),
42            key: key.into(),
43            note_id: note_id.into(),
44        }
45    }
46
47    /// Returns a [`Pckn`] tuple that matches *all* events, i.e. all of its components are set to
48    /// [`Match::All`].
49    #[inline]
50    pub const fn match_all() -> Self {
51        Self {
52            port_index: All,
53            channel: All,
54            key: All,
55            note_id: All,
56        }
57    }
58
59    /// Returns whether this [`Pckn`] tuple matches all possible notes.
60    ///
61    /// This is true if all four matchers are set to [`Match::All`].
62    pub const fn matches_all(&self) -> bool {
63        self.port_index.is_all()
64            && self.channel.is_all()
65            && self.key.is_all()
66            && self.note_id.is_all()
67    }
68
69    /// Returns `true` if this PCKN tuple matches the given one, considering both specific values
70    /// and wildcard [`Match::All`] values.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use clack_common::events::{Match, Pckn};
76    ///
77    /// assert!(Pckn::new(0u16, 0u16, 60u16, 42u32).matches(&Pckn::new(0u16, 0u16, 60u16, Match::All)));
78    /// ```
79    pub fn matches(&self, other: &Pckn) -> bool {
80        self.port_index.matches(other.port_index)
81            && self.channel.matches(other.channel)
82            && self.key.matches(other.key)
83            && self.note_id.matches(other.note_id)
84    }
85
86    // Raw accessors
87
88    /// Constructs a new PCKN tuple from its raw, C-FFI compatible components.
89    ///
90    /// Components set to any negative value (usually `-1`) are interpreted as [`Match::All`], while
91    /// any other value is interpreted as [`Match::Specific`].
92    #[inline]
93    pub const fn from_raw(port: i16, channel: i16, key: i16, note_id: i32) -> Self {
94        Self {
95            port_index: Match::<u16>::from_raw(port),
96            channel: Match::<u16>::from_raw(channel),
97            key: Match::<u16>::from_raw(key),
98            note_id: Match::<u32>::from_raw(note_id),
99        }
100    }
101
102    /// Returns the raw, C-FFI compatible Port component of this PCKN.
103    ///
104    /// This returns `-1` if the port is set to [`Match::All`], otherwise the specific value is
105    /// returned.
106    #[inline]
107    pub const fn raw_port_index(&self) -> i16 {
108        match self.port_index {
109            Specific(p) => p as i16,
110            All => -1,
111        }
112    }
113
114    /// Returns the raw, C-FFI compatible Channel component of this PCKN.
115    ///
116    /// This returns `-1` if the Channel is set to [`Match::All`], otherwise the specific value is
117    /// returned.
118    #[inline]
119    pub const fn raw_channel(&self) -> i16 {
120        match self.channel {
121            Specific(p) => p as i16,
122            All => -1,
123        }
124    }
125
126    /// Returns the raw, C-FFI compatible Key component of this PCKN.
127    ///
128    /// This returns `-1` if the Key is set to [`Match::All`], otherwise the specific value is
129    /// returned.
130    #[inline]
131    pub const fn raw_key(&self) -> i16 {
132        match self.key {
133            Specific(p) => p as i16,
134            All => -1,
135        }
136    }
137
138    /// Returns the raw, C-FFI compatible Note ID component of this PCKN.
139    ///
140    /// This returns `-1` if the Note ID is set to [`Match::All`], otherwise the specific value is
141    /// returned.
142    #[inline]
143    pub const fn raw_note_id(&self) -> i32 {
144        match self.note_id {
145            Specific(p) => p as i32,
146            All => -1,
147        }
148    }
149}
150
151/// Represents matching either a specific value or all values of a given type.
152///
153/// This is used in the [`Pckn`] type to support matching multiple kinds of notes at once.
154#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
155pub enum Match<T> {
156    /// Matches a specific value.
157    Specific(T),
158    /// Matches all values.
159    All,
160}
161
162impl<T> Match<T> {
163    /// Returns a reference to the specific value of this matcher, or [`None`] if this is [`Match::All`].
164    ///
165    /// # Example
166    ///
167    /// ```
168    /// use clack_common::events::Match;
169    ///
170    /// assert_eq!(Match::Specific(42).as_specific(), Some(&42));
171    /// assert_eq!(Match::<u16>::All.as_specific(), None);
172    /// ```
173    pub const fn as_specific(&self) -> Option<&T> {
174        match self {
175            Specific(v) => Some(v),
176            All => None,
177        }
178    }
179
180    /// Returns a reference to the specific value of this matcher, or [`None`] if this is [`Match::All`].
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use clack_common::events::Match;
186    ///
187    /// assert_eq!(Match::Specific(42).into_specific(), Some(42));
188    /// assert_eq!(Match::<u16>::All.into_specific(), None);
189    /// ```
190    pub fn into_specific(self) -> Option<T> {
191        match self {
192            Specific(v) => Some(v),
193            All => None,
194        }
195    }
196
197    /// Returns whether this matcher is [`Match::All`].
198    /// # Example
199    ///
200    /// ```
201    /// use clack_common::events::Match;
202    ///
203    /// assert!(Match::<u16>::All.is_all());
204    /// assert!(!Match::Specific(42).is_all());
205    /// ```
206    #[inline]
207    pub const fn is_all(&self) -> bool {
208        matches!(self, All)
209    }
210
211    /// Returns whether this matcher is [`Match::Specific`].
212    /// # Example
213    ///
214    /// ```
215    /// use clack_common::events::Match;
216    ///
217    /// assert!(Match::Specific(42).is_specific());
218    /// assert!(!Match::<u16>::All.is_specific());
219    /// ```
220    #[inline]
221    pub const fn is_specific(&self) -> bool {
222        matches!(self, Specific(_))
223    }
224}
225
226impl<T> From<T> for Match<T> {
227    #[inline]
228    fn from(value: T) -> Self {
229        Specific(value)
230    }
231}
232
233impl From<u8> for Match<u16> {
234    #[inline]
235    fn from(value: u8) -> Self {
236        Specific(value.into())
237    }
238}
239
240impl From<u8> for Match<u32> {
241    #[inline]
242    fn from(value: u8) -> Self {
243        Specific(value.into())
244    }
245}
246
247impl From<u16> for Match<u32> {
248    #[inline]
249    fn from(value: u16) -> Self {
250        Specific(value.into())
251    }
252}
253
254impl From<ClapId> for Match<u32> {
255    #[inline]
256    fn from(value: ClapId) -> Self {
257        Specific(value.get())
258    }
259}
260
261impl<T: PartialEq> Match<T> {
262    /// Returns `true` if the given [`Match`] matches this one, `false` otherwise.
263    ///
264    /// This will always return true if any of the two is [`Match::All`]. Otherwise, if both values
265    /// are specific, they are compared directly (using their [`PartialEq`] implementation).
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// use clack_common::events::Match;
271    ///
272    /// assert!(Match::Specific(42).matches(42));
273    /// assert!(!Match::Specific(42).matches(21));
274    ///
275    /// assert!(Match::Specific(42).matches(Match::All));
276    /// assert!(Match::All.matches(42));
277    /// assert!(Match::<u16>::All.matches(Match::All));
278    /// ```
279    #[inline]
280    pub fn matches(&self, other: impl Into<Match<T>>) -> bool {
281        match (self, other.into()) {
282            (Specific(x), Specific(y)) => *x == y,
283            _ => true,
284        }
285    }
286}
287
288impl Match<u16> {
289    /// Creates the [`Match`] that corresponds to the given raw C-FFI compatible `i16` type.
290    #[inline]
291    pub const fn from_raw(raw: i16) -> Self {
292        if raw < 0 {
293            All
294        } else {
295            Specific(raw.unsigned_abs())
296        }
297    }
298
299    /// Returns the raw C-FFI compatible `i16` type that corresponds to this [`Match`].
300    ///
301    /// If this matches a specific value, it is returned. Otherwise, if this matches all values, it
302    /// returns `-1`.
303    #[inline]
304    pub const fn to_raw(&self) -> i16 {
305        match self {
306            Specific(raw) => *raw as i16,
307            All => -1,
308        }
309    }
310}
311
312impl Match<u32> {
313    /// Creates the [`Match`] that corresponds to the given raw C-FFI compatible `i32` type.
314    #[inline]
315    pub const fn from_raw(raw: i32) -> Self {
316        if raw < 0 {
317            All
318        } else {
319            Specific(raw.unsigned_abs())
320        }
321    }
322
323    /// Returns the raw C-FFI compatible `i32` type that corresponds to this [`Match`].
324    ///
325    /// If this matches a specific value, it is returned. Otherwise, if this matches all values, it
326    /// returns `-1`.
327    #[inline]
328    pub const fn to_raw(&self) -> i32 {
329        match self {
330            Specific(raw) => *raw as i32,
331            All => -1,
332        }
333    }
334}
335
336macro_rules! impl_event_pckn {
337    (self.$($raw_event:ident).*) => {
338        /// The [`Pckn`](crate::events::Pckn) tuple indicating which note(s) this note event targets.
339        #[inline]
340        pub const fn pckn(&self) -> crate::events::Pckn {
341            Pckn::from_raw(
342                self.$($raw_event).*.port_index,
343                self.$($raw_event).*.channel,
344                self.$($raw_event).*.key,
345                self.$($raw_event).*.note_id,
346            )
347        }
348
349        /// Sets the [`Pckn`](crate::events::Pckn) tuple for this event.
350        #[inline]
351        pub const fn set_pckn(&mut self, pckn: crate::events::Pckn) {
352            self.$($raw_event).*.port_index = pckn.raw_port_index();
353            self.$($raw_event).*.channel = pckn.raw_channel();
354            self.$($raw_event).*.key = pckn.raw_key();
355            self.$($raw_event).*.note_id = pckn.raw_note_id();
356        }
357
358        /// Sets the [`Pckn`](crate::events::Pckn) tuple for this event.
359        ///
360        /// This method takes and returns ownership of the event, allowing it to be used in a
361        /// builder-style pattern.
362        #[inline]
363        pub const fn with_pckn(mut self, pckn: crate::events::Pckn) -> Self {
364            self.set_pckn(pckn);
365            self
366        }
367
368        /// The index of the note port this event targets.
369        ///
370        /// This returns [`Match::All`] if this event targets all possible note ports.
371        #[inline]
372        pub const fn port_index(&self) -> Match<u16> {
373            Match::<u16>::from_raw(self.$($raw_event).*.port_index)
374        }
375
376        /// Sets the index of the note port this event targets.
377        ///
378        /// Use [`Match::All`] to target all possible note ports.
379        #[inline]
380        pub const fn set_port_index(&mut self, port_index: Match<u16>) {
381            self.$($raw_event).*.port_index = port_index.to_raw()
382        }
383
384        /// Sets the index of the note port this event targets.
385        ///
386        /// Use [`Match::All`] to target all possible note ports.
387        ///
388        /// This method takes and returns ownership of the event, allowing it to be used in a
389        /// builder-style pattern.
390        #[inline]
391        pub const fn with_port_index(mut self, port_index: Match<u16>) -> Self {
392            self.$($raw_event).*.port_index = port_index.to_raw();
393            self
394        }
395
396        /// The note channel this event targets (0-15).
397        ///
398        /// This returns [`Match::All`] if this event targets all possible note channels.
399        #[inline]
400        pub const fn channel(&self) -> Match<u16> {
401            Match::<u16>::from_raw(self.$($raw_event).*.channel)
402        }
403
404        /// Sets the note channel this event targets (0-15).
405        ///
406        /// Use [`Match::All`] to target all possible channels.
407        #[inline]
408        pub const fn set_channel(&mut self, channel: Match<u16>) {
409            self.$($raw_event).*.channel = channel.to_raw();
410        }
411
412        /// Sets the note channel this event targets (0-15).
413        ///
414        /// Use [`Match::All`] to target all possible channels.
415        ///
416        /// This method takes and returns ownership of the event, allowing it to be used in a
417        /// builder-style pattern.
418        #[inline]
419        pub const fn with_channel(mut self, channel: Match<u16>) -> Self {
420            self.$($raw_event).*.channel = channel.to_raw();
421            self
422        }
423
424        /// The key of the note(s) this event targets (0-127).
425        ///
426        /// This returns [`Match::All`] if this event targets all possible note keys.
427        #[inline]
428        pub const fn key(&self) -> Match<u16> {
429            Match::<u16>::from_raw(self.$($raw_event).*.key)
430        }
431
432        /// Sets the key of the note(s) this event targets (0-127).
433        ///
434        /// Use [`Match::All`] to target all possible note keys.
435        #[inline]
436        pub const fn set_key(&mut self, key: Match<u16>) {
437            self.$($raw_event).*.key = key.to_raw();
438        }
439
440        /// Sets the key of the note(s) this event targets (0-127).
441        ///
442        /// Use [`Match::All`] to target all possible note keys.
443        ///
444        /// This method takes and returns ownership of the event, allowing it to be used in a
445        /// builder-style pattern.
446        #[inline]
447        pub const fn with_key(mut self, key: Match<u16>) -> Self {
448            self.$($raw_event).*.key = key.to_raw();
449            self
450        }
451
452        /// The specific ID of the Note this event targets.
453        ///
454        /// This returns [`Match::All`] if this event doesn't target a specific note, or doesn't
455        /// provide a Note ID.
456        #[inline]
457        pub const fn note_id(&self) -> Match<u32> {
458            Match::<u32>::from_raw(self.$($raw_event).*.note_id)
459        }
460
461        /// Sets the specific ID of the Note this event targets.
462        ///
463        /// Use [`Match::All`] to not target a single specific note in particular.
464        #[inline]
465        pub const fn set_note_id(&mut self, note_id: Match<u32>) {
466            self.$($raw_event).*.note_id = note_id.to_raw();
467        }
468
469        /// Sets the specific ID of the Note this event targets.
470        ///
471        /// Use [`Match::All`] to not target a single specific note in particular.
472        ///
473        /// This method takes and returns ownership of the event, allowing it to be used in a
474        /// builder-style pattern.
475        #[inline]
476        pub const fn with_note_id(mut self, note_id: Match<u32>) -> Self {
477            self.$($raw_event).*.note_id = note_id.to_raw();
478            self
479        }
480    };
481}
482
483use crate::utils::ClapId;
484pub(crate) use impl_event_pckn;