clap_clap/ext/
audio_ports.rs

1use std::fmt::{Display, Formatter};
2
3use crate::{
4    ffi::{
5        CLAP_AUDIO_PORTS_RESCAN_CHANNEL_COUNT, CLAP_AUDIO_PORTS_RESCAN_FLAGS,
6        CLAP_AUDIO_PORTS_RESCAN_IN_PLACE_PAIR, CLAP_AUDIO_PORTS_RESCAN_LIST,
7        CLAP_AUDIO_PORTS_RESCAN_NAMES, CLAP_AUDIO_PORTS_RESCAN_PORT_TYPE, clap_host_audio_ports,
8    },
9    impl_flags_u32,
10    plugin::Plugin,
11    prelude::Host,
12};
13
14pub trait AudioPorts<P>
15where
16    P: Plugin,
17{
18    fn count(plugin: &P, is_input: bool) -> u32;
19    fn get(plugin: &P, index: u32, is_input: bool) -> Option<AudioPortInfo>;
20}
21
22/// Port rescan flags.
23///
24/// # Example
25///
26/// ```rust
27/// # use clap_clap::ext::audio_ports::RescanFlags;
28/// assert_eq!(RescanFlags::Names as u32, 0b1);
29/// assert!(RescanFlags::Names.is_set(0b101));
30/// assert_eq!(RescanFlags::Names.set(0b100), 0b101);
31/// assert_eq!(RescanFlags::Names.clear(0b101), 0b100);
32/// ```
33#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34#[repr(u32)]
35pub enum RescanFlags {
36    /// The ports name did change, the host can scan them right away.
37    Names = CLAP_AUDIO_PORTS_RESCAN_NAMES,
38    ///  The flags did change
39    Flags = CLAP_AUDIO_PORTS_RESCAN_FLAGS,
40    ///  The channel_count did change
41    ChannelCount = CLAP_AUDIO_PORTS_RESCAN_CHANNEL_COUNT,
42    ///  The port type did change
43    PortType = CLAP_AUDIO_PORTS_RESCAN_PORT_TYPE,
44    ///  The in-place pair did change
45    InPlacePair = CLAP_AUDIO_PORTS_RESCAN_IN_PLACE_PAIR,
46    ///  The list of ports have changed: entries have been removed/added.
47    List = CLAP_AUDIO_PORTS_RESCAN_LIST,
48}
49
50impl_flags_u32!(RescanFlags);
51
52#[derive(Debug)]
53pub struct HostAudioPorts<'a> {
54    host: &'a Host,
55    clap_host_audio_ports: &'a clap_host_audio_ports,
56}
57
58impl<'a> HostAudioPorts<'a> {
59    /// # Safety
60    ///
61    /// All extension interface function pointers must be non-null (Some), and
62    /// the functions must be thread-safe.
63    pub(crate) const unsafe fn new_unchecked(
64        host: &'a Host,
65        clap_host_audio_ports: &'a clap_host_audio_ports,
66    ) -> Self {
67        Self {
68            host,
69            clap_host_audio_ports,
70        }
71    }
72
73    pub fn is_rescan_flag_supported(&self, flag: RescanFlags) -> bool {
74        // SAFETY: By construction, the callback must be a valid function pointer,
75        // and the call is thread-safe.
76        let callback = self.clap_host_audio_ports.is_rescan_flag_supported.unwrap();
77        unsafe { callback(self.host.clap_host(), flag as u32) }
78    }
79
80    pub fn rescan(&self, flags: u32) {
81        // SAFETY: By construction, the callback must be a valid function pointer,
82        // and the call is thread-safe.
83        let callback = self.clap_host_audio_ports.rescan.unwrap();
84        unsafe { callback(self.host.clap_host(), flags) };
85    }
86}
87
88pub(crate) use ffi::PluginAudioPorts;
89mod ffi {
90    use std::marker::PhantomData;
91
92    use crate::{
93        ext::audio_ports::AudioPorts,
94        ffi::{clap_audio_port_info, clap_plugin, clap_plugin_audio_ports},
95        plugin::{ClapPlugin, Plugin},
96    };
97
98    extern "C-unwind" fn count<A, P>(plugin: *const clap_plugin, is_input: bool) -> u32
99    where
100        P: Plugin,
101        A: AudioPorts<P>,
102    {
103        if plugin.is_null() {
104            return 0;
105        }
106        // SAFETY: We just checked that the pointer is non-null and the plugin
107        // has been obtained from host and is tied to type P.
108        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
109
110        // SAFETY: This function is called on the main thread.
111        // It is guaranteed that we are the only function accessing the plugin now.
112        // So the mutable reference to plugin for the duration of this call is
113        // safe.
114        let plugin = unsafe { clap_plugin.plugin() };
115
116        A::count(plugin, is_input)
117    }
118
119    extern "C-unwind" fn get<A, P>(
120        plugin: *const clap_plugin,
121        index: u32,
122        is_input: bool,
123        info: *mut clap_audio_port_info,
124    ) -> bool
125    where
126        P: Plugin,
127        A: AudioPorts<P>,
128    {
129        if plugin.is_null() {
130            return false;
131        }
132        // SAFETY: We just checked that the pointer is non-null and the plugin
133        // has been obtained from host and is tied to type P.
134        let mut clap_plugin = unsafe { ClapPlugin::<P>::new_unchecked(plugin) };
135
136        // SAFETY: This function is called on the main thread.
137        // It is guaranteed that we are the only function accessing the plugin now.
138        // So the mutable reference to plugin for the duration of this call is
139        // safe.
140        let plugin = unsafe { clap_plugin.plugin() };
141
142        // SAFETY: The host guarantees we are the only function that can access info
143        // for the duration of the function call.  So obtaining a mutable reference
144        // is safe.
145        let info = unsafe { &mut *info };
146
147        A::get(plugin, index, is_input)
148            .map(|x| x.fill_clap_audio_port_info(info))
149            .is_some()
150    }
151
152    pub struct PluginAudioPorts<P> {
153        #[allow(unused)]
154        clap_plugin_audio_ports: clap_plugin_audio_ports,
155        _marker: PhantomData<P>,
156    }
157
158    impl<P: Plugin> PluginAudioPorts<P> {
159        pub fn new<A: AudioPorts<P>>(_: A) -> Self {
160            Self {
161                clap_plugin_audio_ports: clap_plugin_audio_ports {
162                    count: Some(count::<A, P>),
163                    get: Some(get::<A, P>),
164                },
165                _marker: PhantomData,
166            }
167        }
168    }
169}
170
171pub use port_info::{AudioPortFlags, AudioPortInfo, AudioPortType};
172mod port_info {
173    use std::ptr::null;
174
175    use crate::{
176        ffi::{
177            CLAP_AUDIO_PORT_IS_MAIN, CLAP_AUDIO_PORT_PREFERS_64BITS,
178            CLAP_AUDIO_PORT_REQUIRES_COMMON_SAMPLE_SIZE, CLAP_AUDIO_PORT_SUPPORTS_64BITS,
179            CLAP_INVALID_ID, CLAP_PORT_AMBISONIC, CLAP_PORT_MONO, CLAP_PORT_STEREO,
180            CLAP_PORT_SURROUND, clap_audio_port_info,
181        },
182        id::ClapId,
183        impl_flags_u32,
184        plugin::Plugin,
185        prelude::AudioPorts,
186    };
187
188    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
189    #[repr(u32)]
190    pub enum AudioPortFlags {
191        /// This port is the main audio input or output. There can be only one
192        /// main input and main output. Main port must be at index 0.
193        IsMain = CLAP_AUDIO_PORT_IS_MAIN,
194        /// This port can be used with 64 bits audio
195        Supports64bits = CLAP_AUDIO_PORT_SUPPORTS_64BITS,
196        /// 64 bits audio is preferred with this port
197        Prefers64bits = CLAP_AUDIO_PORT_PREFERS_64BITS,
198        /// This port must be used with the same sample size as all the other
199        /// ports which have this flag. In other words if all ports have
200        /// this flag then the plugin may either be used entirely with
201        /// 64 bits audio or 32 bits audio, but it can't be mixed.
202        RequiresCommonSampleSize = CLAP_AUDIO_PORT_REQUIRES_COMMON_SAMPLE_SIZE,
203    }
204
205    impl_flags_u32!(AudioPortFlags);
206
207    #[derive(Debug, Default, Clone, PartialEq)]
208    pub struct AudioPortInfo {
209        pub id: ClapId,
210        pub name: String,
211        pub flags: u32,
212        pub channel_count: u32,
213        pub port_type: Option<AudioPortType>,
214        pub in_place_pair: Option<ClapId>,
215    }
216
217    impl AudioPortInfo {
218        pub(super) fn fill_clap_audio_port_info(&self, info: &mut clap_audio_port_info) {
219            info.id = self.id.into();
220
221            // info.name.len > 1 so no underflow
222            let n = self.name.len().min(info.name.len() - 1);
223            unsafe {
224                std::ptr::copy_nonoverlapping(
225                    self.name.as_ptr(),
226                    info.name.as_mut_ptr() as *mut _,
227                    n,
228                )
229            }
230            // n is within bounds
231            info.name[n] = b'\0' as _;
232
233            info.flags = self.flags;
234
235            info.channel_count = self.channel_count;
236
237            info.port_type = match self.port_type {
238                Some(AudioPortType::Mono) => CLAP_PORT_MONO.as_ptr(),
239                Some(AudioPortType::Stereo) => CLAP_PORT_STEREO.as_ptr(),
240                Some(AudioPortType::Surround) => CLAP_PORT_SURROUND.as_ptr(),
241                Some(AudioPortType::Ambisonic) => CLAP_PORT_AMBISONIC.as_ptr(),
242                None => null(),
243            };
244
245            info.in_place_pair = self
246                .in_place_pair
247                .map(Into::into)
248                .unwrap_or(CLAP_INVALID_ID);
249        }
250    }
251
252    #[derive(Debug, Copy, Clone, PartialEq)]
253    pub enum AudioPortType {
254        Mono,
255        Stereo,
256        Surround,
257        Ambisonic,
258    }
259
260    impl TryFrom<&str> for AudioPortType {
261        type Error = crate::ext::audio_ports::Error;
262
263        fn try_from(value: &str) -> Result<Self, Self::Error> {
264            if value == "mono" {
265                Ok(AudioPortType::Mono)
266            } else if value == "stereo" {
267                Ok(AudioPortType::Stereo)
268            } else if value == "ambisonic" {
269                Ok(AudioPortType::Ambisonic)
270            } else if value == "surround" {
271                Ok(AudioPortType::Surround)
272            } else {
273                Err(Self::Error::PortType)
274            }
275        }
276    }
277
278    impl<P: Plugin> AudioPorts<P> for () {
279        fn count(_: &P, _: bool) -> u32 {
280            0
281        }
282
283        fn get(_: &P, _: u32, _: bool) -> Option<AudioPortInfo> {
284            None
285        }
286    }
287}
288
289pub use static_ports::{MonoPorts, StereoPorts};
290mod static_ports {
291    use crate::{
292        ext::audio_ports::{AudioPortFlags, AudioPortInfo, AudioPortType, AudioPorts},
293        plugin::Plugin,
294    };
295
296    /// Static mono ports, in and out.
297    #[derive(Default, Debug, Copy, Clone)]
298    pub struct MonoPorts<const IN: u32, const OUT: u32>;
299
300    impl<const IN: u32, const OUT: u32> MonoPorts<IN, OUT> {
301        pub const fn new() -> Self {
302            Self {}
303        }
304    }
305
306    impl<P, const IN: u32, const OUT: u32> AudioPorts<P> for MonoPorts<IN, OUT>
307    where
308        P: Plugin,
309    {
310        fn count(_: &P, is_input: bool) -> u32 {
311            if is_input { IN } else { OUT }
312        }
313
314        fn get(_: &P, index: u32, is_input: bool) -> Option<AudioPortInfo> {
315            if is_input {
316                (index < IN).then_some(if index == 0 {
317                    AudioPortInfo {
318                        id: index.try_into().unwrap(),
319                        name: "Main In".to_owned(),
320                        flags: AudioPortFlags::IsMain as u32,
321                        channel_count: 1,
322                        port_type: Some(AudioPortType::Mono),
323                        in_place_pair: None,
324                    }
325                } else {
326                    AudioPortInfo {
327                        id: index.try_into().unwrap(),
328                        name: format!("In {index}"),
329                        flags: 0,
330                        channel_count: 1,
331                        port_type: Some(AudioPortType::Mono),
332                        in_place_pair: None,
333                    }
334                })
335            } else {
336                (index < OUT).then_some(if index == 0 {
337                    AudioPortInfo {
338                        id: (IN + index).try_into().unwrap(),
339                        name: "Main Out".to_owned(),
340                        flags: AudioPortFlags::IsMain as u32,
341                        channel_count: 1,
342                        port_type: Some(AudioPortType::Mono),
343                        in_place_pair: None,
344                    }
345                } else {
346                    AudioPortInfo {
347                        id: (IN + index).try_into().unwrap(),
348                        name: format!("Out {index}"),
349                        flags: 0,
350                        channel_count: 1,
351                        port_type: Some(AudioPortType::Mono),
352                        in_place_pair: None,
353                    }
354                })
355            }
356        }
357    }
358
359    /// Single static stereo port, in and out.
360    #[derive(Debug, Copy, Clone)]
361    pub struct StereoPorts<const IN: u32, const OUT: u32>;
362
363    impl<P, const IN: u32, const OUT: u32> AudioPorts<P> for StereoPorts<IN, OUT>
364    where
365        P: Plugin,
366    {
367        fn count(_: &P, is_input: bool) -> u32 {
368            if is_input { IN } else { OUT }
369        }
370
371        fn get(_: &P, index: u32, is_input: bool) -> Option<AudioPortInfo> {
372            if is_input {
373                (index < IN).then_some(if index == 0 {
374                    AudioPortInfo {
375                        id: index.try_into().unwrap(),
376                        name: "Main In".to_owned(),
377                        flags: AudioPortFlags::IsMain as u32,
378                        channel_count: 2,
379                        port_type: Some(AudioPortType::Stereo),
380                        in_place_pair: None,
381                    }
382                } else {
383                    AudioPortInfo {
384                        id: index.try_into().unwrap(),
385                        name: format!("In {index}"),
386                        flags: 0,
387                        channel_count: 2,
388                        port_type: Some(AudioPortType::Stereo),
389                        in_place_pair: None,
390                    }
391                })
392            } else {
393                (index < OUT).then_some(if index == 0 {
394                    AudioPortInfo {
395                        id: (IN + index).try_into().unwrap(),
396                        name: "Main Out".to_owned(),
397                        flags: AudioPortFlags::IsMain as u32,
398                        channel_count: 2,
399                        port_type: Some(AudioPortType::Stereo),
400                        in_place_pair: None,
401                    }
402                } else {
403                    AudioPortInfo {
404                        id: (IN + index).try_into().unwrap(),
405                        name: format!("Out {index}"),
406                        flags: 0,
407                        channel_count: 2,
408                        port_type: Some(AudioPortType::Stereo),
409                        in_place_pair: None,
410                    }
411                })
412            }
413        }
414    }
415}
416
417#[derive(Debug)]
418pub enum Error {
419    PortType,
420}
421
422impl Display for Error {
423    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
424        match self {
425            Error::PortType => write!(f, "unknown port type"),
426        }
427    }
428}
429
430impl std::error::Error for Error {}
431
432impl From<Error> for crate::Error {
433    fn from(value: Error) -> Self {
434        match value {
435            Error::PortType => crate::ext::Error::AudioPorts(value).into(),
436        }
437    }
438}