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;