Skip to main content

maolan_engine/plugins/
clap.rs

1use crate::audio::io::AudioIO;
2use crate::midi::io::MidiEvent;
3use crate::mutex::UnsafeMutex;
4#[cfg(any(
5    target_os = "macos",
6    target_os = "linux",
7    target_os = "freebsd",
8    target_os = "openbsd"
9))]
10use crate::plugins::paths;
11use libloading::Library;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::ffi::{CStr, CString, c_char, c_void};
15use std::fmt;
16use std::path::{Path, PathBuf};
17use std::sync::Arc;
18use std::sync::atomic::{AtomicU32, Ordering};
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct ClapParameterInfo {
22    pub id: u32,
23    pub name: String,
24    pub module: String,
25    pub min_value: f64,
26    pub max_value: f64,
27    pub default_value: f64,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub struct ClapPluginState {
32    pub bytes: Vec<u8>,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct ClapMidiOutputEvent {
37    pub port: usize,
38    pub event: MidiEvent,
39}
40
41#[derive(Clone, Copy, Debug, Default)]
42pub struct ClapTransportInfo {
43    pub transport_sample: usize,
44    pub playing: bool,
45    pub loop_enabled: bool,
46    pub loop_range_samples: Option<(usize, usize)>,
47    pub bpm: f64,
48    pub tsig_num: u16,
49    pub tsig_denom: u16,
50}
51
52#[derive(Clone, Copy, Debug)]
53struct PendingParamValue {
54    param_id: u32,
55    value: f64,
56}
57
58#[derive(Clone, Copy, Debug)]
59enum PendingParamEvent {
60    Value {
61        param_id: u32,
62        value: f64,
63        frame: u32,
64    },
65    GestureBegin {
66        param_id: u32,
67        frame: u32,
68    },
69    GestureEnd {
70        param_id: u32,
71        frame: u32,
72    },
73}
74
75#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
76pub struct ClapPluginInfo {
77    pub name: String,
78    pub path: String,
79    pub capabilities: Option<ClapPluginCapabilities>,
80}
81
82#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
83pub struct ClapPluginCapabilities {
84    pub has_gui: bool,
85    pub gui_apis: Vec<String>,
86    pub supports_embedded: bool,
87    pub supports_floating: bool,
88    pub has_params: bool,
89    pub has_state: bool,
90    pub has_audio_ports: bool,
91    pub has_note_ports: bool,
92}
93
94#[derive(Clone)]
95pub struct ClapProcessor {
96    path: String,
97    name: String,
98    sample_rate: f64,
99    audio_inputs: Vec<Arc<AudioIO>>,
100    audio_outputs: Vec<Arc<AudioIO>>,
101    midi_input_ports: usize,
102    midi_output_ports: usize,
103    main_audio_inputs: usize,
104    main_audio_outputs: usize,
105    host_runtime: Arc<HostRuntime>,
106    plugin_handle: Arc<PluginHandle>,
107    param_infos: Arc<Vec<ClapParameterInfo>>,
108    param_values: Arc<UnsafeMutex<HashMap<u32, f64>>>,
109    pending_param_events: Arc<UnsafeMutex<Vec<PendingParamEvent>>>,
110}
111
112impl fmt::Debug for ClapProcessor {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        f.debug_struct("ClapProcessor")
115            .field("path", &self.path)
116            .field("name", &self.name)
117            .field("audio_inputs", &self.audio_inputs.len())
118            .field("audio_outputs", &self.audio_outputs.len())
119            .field("midi_input_ports", &self.midi_input_ports)
120            .field("midi_output_ports", &self.midi_output_ports)
121            .field("main_audio_inputs", &self.main_audio_inputs)
122            .field("main_audio_outputs", &self.main_audio_outputs)
123            .finish()
124    }
125}
126
127impl ClapProcessor {
128    pub fn new(
129        sample_rate: f64,
130        buffer_size: usize,
131        plugin_spec: &str,
132        input_count: usize,
133        output_count: usize,
134    ) -> Result<Self, String> {
135        let (plugin_path, plugin_id) = split_plugin_spec(plugin_spec);
136        let name = Path::new(plugin_path)
137            .file_stem()
138            .map(|s| s.to_string_lossy().to_string())
139            .unwrap_or_else(|| plugin_spec.to_string());
140        let host_runtime = Arc::new(HostRuntime::new()?);
141        let plugin_handle = Arc::new(PluginHandle::load(
142            plugin_path,
143            plugin_id,
144            host_runtime.clone(),
145            sample_rate,
146            buffer_size as u32,
147        )?);
148        let (discovered_inputs, discovered_outputs) = plugin_handle.audio_port_layout();
149        let (discovered_midi_inputs, discovered_midi_outputs) = plugin_handle.note_port_layout();
150        let resolved_inputs = discovered_inputs.unwrap_or(input_count).max(1);
151        let resolved_outputs = discovered_outputs.unwrap_or(output_count).max(1);
152        let main_audio_inputs = if discovered_inputs.is_some() {
153            usize::from(resolved_inputs > 0)
154        } else {
155            input_count.max(1)
156        };
157        let main_audio_outputs = if discovered_outputs.is_some() {
158            usize::from(resolved_outputs > 0)
159        } else {
160            output_count.max(1)
161        };
162        let audio_inputs = (0..resolved_inputs)
163            .map(|_| Arc::new(AudioIO::new(buffer_size)))
164            .collect();
165        let audio_outputs = (0..resolved_outputs)
166            .map(|_| Arc::new(AudioIO::new(buffer_size)))
167            .collect();
168        let param_infos = Arc::new(plugin_handle.parameter_infos());
169        let param_values = Arc::new(UnsafeMutex::new(
170            plugin_handle.parameter_values(&param_infos),
171        ));
172        Ok(Self {
173            path: plugin_spec.to_string(),
174            name,
175            sample_rate,
176            audio_inputs,
177            audio_outputs,
178            midi_input_ports: discovered_midi_inputs.unwrap_or(1).max(1),
179            midi_output_ports: discovered_midi_outputs.unwrap_or(1).max(1),
180            main_audio_inputs,
181            main_audio_outputs,
182            host_runtime,
183            plugin_handle,
184            param_infos,
185            param_values,
186            pending_param_events: Arc::new(UnsafeMutex::new(Vec::new())),
187        })
188    }
189
190    pub fn setup_audio_ports(&self) {
191        for port in &self.audio_inputs {
192            port.setup();
193        }
194        for port in &self.audio_outputs {
195            port.setup();
196        }
197    }
198
199    pub fn process_with_audio_io(&self, frames: usize) {
200        let _ = self.process_with_midi(frames, &[], ClapTransportInfo::default());
201    }
202
203    pub fn process_with_midi(
204        &self,
205        frames: usize,
206        midi_in: &[MidiEvent],
207        transport: ClapTransportInfo,
208    ) -> Vec<ClapMidiOutputEvent> {
209        for port in &self.audio_inputs {
210            if port.ready() {
211                port.process();
212            }
213        }
214        let (processed, processed_midi) = match self.process_native(frames, midi_in, transport) {
215            Ok(v) => v,
216            Err(_) => (false, Vec::new()),
217        };
218        if !processed {
219            for out in &self.audio_outputs {
220                let out_buf = out.buffer.lock();
221                out_buf.fill(0.0);
222                *out.finished.lock() = true;
223            }
224        }
225        processed_midi
226    }
227
228    pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
229        self.param_infos.as_ref().clone()
230    }
231
232    pub fn parameter_values(&self) -> HashMap<u32, f64> {
233        self.param_values.lock().clone()
234    }
235
236    pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
237        self.set_parameter_at(param_id, value, 0)
238    }
239
240    pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
241        let Some(info) = self.param_infos.iter().find(|p| p.id == param_id) else {
242            return Err(format!("Unknown CLAP parameter id: {param_id}"));
243        };
244        let clamped = value.clamp(info.min_value, info.max_value);
245        self.pending_param_events
246            .lock()
247            .push(PendingParamEvent::Value {
248                param_id,
249                value: clamped,
250                frame,
251            });
252        self.param_values.lock().insert(param_id, clamped);
253        Ok(())
254    }
255
256    pub fn begin_parameter_edit(&self, param_id: u32) -> Result<(), String> {
257        self.begin_parameter_edit_at(param_id, 0)
258    }
259
260    pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
261        if !self.param_infos.iter().any(|p| p.id == param_id) {
262            return Err(format!("Unknown CLAP parameter id: {param_id}"));
263        }
264        self.pending_param_events
265            .lock()
266            .push(PendingParamEvent::GestureBegin { param_id, frame });
267        Ok(())
268    }
269
270    pub fn end_parameter_edit(&self, param_id: u32) -> Result<(), String> {
271        self.end_parameter_edit_at(param_id, 0)
272    }
273
274    pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
275        if !self.param_infos.iter().any(|p| p.id == param_id) {
276            return Err(format!("Unknown CLAP parameter id: {param_id}"));
277        }
278        self.pending_param_events
279            .lock()
280            .push(PendingParamEvent::GestureEnd { param_id, frame });
281        Ok(())
282    }
283
284    pub fn snapshot_state(&self) -> Result<ClapPluginState, String> {
285        self.plugin_handle.snapshot_state()
286    }
287
288    pub fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
289        self.plugin_handle.restore_state(state)
290    }
291
292    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
293        &self.audio_inputs
294    }
295
296    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
297        &self.audio_outputs
298    }
299
300    pub fn main_audio_input_count(&self) -> usize {
301        self.main_audio_inputs
302    }
303
304    pub fn main_audio_output_count(&self) -> usize {
305        self.main_audio_outputs
306    }
307
308    pub fn midi_input_count(&self) -> usize {
309        self.midi_input_ports
310    }
311
312    pub fn midi_output_count(&self) -> usize {
313        self.midi_output_ports
314    }
315
316    pub fn path(&self) -> &str {
317        &self.path
318    }
319
320    pub fn name(&self) -> &str {
321        &self.name
322    }
323
324    fn process_native(
325        &self,
326        frames: usize,
327        midi_in: &[MidiEvent],
328        transport: ClapTransportInfo,
329    ) -> Result<(bool, Vec<ClapMidiOutputEvent>), String> {
330        if frames == 0 {
331            return Ok((true, Vec::new()));
332        }
333
334        let mut in_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_inputs.len());
335        let mut out_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_outputs.len());
336        let mut in_buffers = Vec::with_capacity(self.audio_inputs.len());
337        let mut out_buffers = Vec::with_capacity(self.audio_outputs.len());
338
339        for input in &self.audio_inputs {
340            let buf = input.buffer.lock();
341            in_channel_ptrs.push(vec![buf.as_ptr() as *mut f32]);
342            in_buffers.push(buf);
343        }
344        for output in &self.audio_outputs {
345            let buf = output.buffer.lock();
346            out_channel_ptrs.push(vec![buf.as_ptr() as *mut f32]);
347            out_buffers.push(buf);
348        }
349
350        let mut in_audio = Vec::with_capacity(self.audio_inputs.len());
351        let mut out_audio = Vec::with_capacity(self.audio_outputs.len());
352
353        for ptrs in &mut in_channel_ptrs {
354            in_audio.push(ClapAudioBuffer {
355                data32: ptrs.as_mut_ptr(),
356                data64: std::ptr::null_mut(),
357                channel_count: 1,
358                latency: 0,
359                constant_mask: 0,
360            });
361        }
362        for ptrs in &mut out_channel_ptrs {
363            out_audio.push(ClapAudioBuffer {
364                data32: ptrs.as_mut_ptr(),
365                data64: std::ptr::null_mut(),
366                channel_count: 1,
367                latency: 0,
368                constant_mask: 0,
369            });
370        }
371
372        let pending_params = std::mem::take(self.pending_param_events.lock());
373        let (in_events, in_ctx) =
374            input_events_from(midi_in, &pending_params, self.sample_rate, transport);
375        let out_cap = midi_in
376            .len()
377            .saturating_add(self.midi_output_ports.saturating_mul(64));
378        let (mut out_events, mut out_ctx) = output_events_ctx(out_cap);
379
380        let mut process = ClapProcess {
381            steady_time: -1,
382            frames_count: frames as u32,
383            transport: std::ptr::null(),
384            audio_inputs: in_audio.as_mut_ptr(),
385            audio_outputs: out_audio.as_mut_ptr(),
386            audio_inputs_count: in_audio.len() as u32,
387            audio_outputs_count: out_audio.len() as u32,
388            in_events: &in_events,
389            out_events: &mut out_events,
390        };
391
392        let result = self.plugin_handle.process(&mut process);
393        drop(in_ctx);
394        for output in &self.audio_outputs {
395            *output.finished.lock() = true;
396        }
397        let processed = result?;
398        let host_flags = self.host_runtime.take_callback_flags();
399        if host_flags.restart {
400            self.plugin_handle.reset();
401        }
402        if host_flags.callback {
403            self.plugin_handle.on_main_thread();
404        }
405        if host_flags.process {
406            // Host already continuously schedules process blocks.
407        }
408        if processed {
409            for update in &out_ctx.param_values {
410                self.param_values
411                    .lock()
412                    .insert(update.param_id, update.value);
413            }
414            Ok((true, std::mem::take(&mut out_ctx.midi_events)))
415        } else {
416            Ok((false, Vec::new()))
417        }
418    }
419}
420
421#[repr(C)]
422#[derive(Clone, Copy)]
423struct ClapVersion {
424    major: u32,
425    minor: u32,
426    revision: u32,
427}
428
429const CLAP_VERSION: ClapVersion = ClapVersion {
430    major: 1,
431    minor: 2,
432    revision: 0,
433};
434
435#[repr(C)]
436struct ClapHost {
437    clap_version: ClapVersion,
438    host_data: *mut c_void,
439    name: *const c_char,
440    vendor: *const c_char,
441    url: *const c_char,
442    version: *const c_char,
443    get_extension: Option<unsafe extern "C" fn(*const ClapHost, *const c_char) -> *const c_void>,
444    request_restart: Option<unsafe extern "C" fn(*const ClapHost)>,
445    request_process: Option<unsafe extern "C" fn(*const ClapHost)>,
446    request_callback: Option<unsafe extern "C" fn(*const ClapHost)>,
447}
448
449#[repr(C)]
450struct ClapPluginEntry {
451    clap_version: ClapVersion,
452    init: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
453    deinit: Option<unsafe extern "C" fn()>,
454    get_factory: Option<unsafe extern "C" fn(*const c_char) -> *const c_void>,
455}
456
457#[repr(C)]
458struct ClapPluginFactory {
459    get_plugin_count: Option<unsafe extern "C" fn(*const ClapPluginFactory) -> u32>,
460    get_plugin_descriptor:
461        Option<unsafe extern "C" fn(*const ClapPluginFactory, u32) -> *const ClapPluginDescriptor>,
462    create_plugin: Option<
463        unsafe extern "C" fn(
464            *const ClapPluginFactory,
465            *const ClapHost,
466            *const c_char,
467        ) -> *const ClapPlugin,
468    >,
469}
470
471#[repr(C)]
472struct ClapPluginDescriptor {
473    clap_version: ClapVersion,
474    id: *const c_char,
475    name: *const c_char,
476    vendor: *const c_char,
477    url: *const c_char,
478    manual_url: *const c_char,
479    support_url: *const c_char,
480    version: *const c_char,
481    description: *const c_char,
482    features: *const *const c_char,
483}
484
485#[repr(C)]
486struct ClapPlugin {
487    desc: *const ClapPluginDescriptor,
488    plugin_data: *mut c_void,
489    init: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
490    destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
491    activate: Option<unsafe extern "C" fn(*const ClapPlugin, f64, u32, u32) -> bool>,
492    deactivate: Option<unsafe extern "C" fn(*const ClapPlugin)>,
493    start_processing: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
494    stop_processing: Option<unsafe extern "C" fn(*const ClapPlugin)>,
495    reset: Option<unsafe extern "C" fn(*const ClapPlugin)>,
496    process: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapProcess) -> i32>,
497    get_extension: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char) -> *const c_void>,
498    on_main_thread: Option<unsafe extern "C" fn(*const ClapPlugin)>,
499}
500
501#[repr(C)]
502struct ClapInputEvents {
503    ctx: *const c_void,
504    size: Option<unsafe extern "C" fn(*const ClapInputEvents) -> u32>,
505    get: Option<unsafe extern "C" fn(*const ClapInputEvents, u32) -> *const ClapEventHeader>,
506}
507
508#[repr(C)]
509struct ClapOutputEvents {
510    ctx: *mut c_void,
511    try_push: Option<unsafe extern "C" fn(*const ClapOutputEvents, *const ClapEventHeader) -> bool>,
512}
513
514#[repr(C)]
515struct ClapEventHeader {
516    size: u32,
517    time: u32,
518    space_id: u16,
519    type_: u16,
520    flags: u32,
521}
522
523const CLAP_CORE_EVENT_SPACE_ID: u16 = 0;
524const CLAP_EVENT_MIDI: u16 = 10;
525const CLAP_EVENT_PARAM_VALUE: u16 = 5;
526const CLAP_EVENT_PARAM_GESTURE_BEGIN: u16 = 6;
527const CLAP_EVENT_PARAM_GESTURE_END: u16 = 7;
528const CLAP_EVENT_TRANSPORT: u16 = 9;
529const CLAP_TRANSPORT_HAS_TEMPO: u32 = 1 << 0;
530const CLAP_TRANSPORT_HAS_BEATS_TIMELINE: u32 = 1 << 1;
531const CLAP_TRANSPORT_HAS_SECONDS_TIMELINE: u32 = 1 << 2;
532const CLAP_TRANSPORT_HAS_TIME_SIGNATURE: u32 = 1 << 3;
533const CLAP_TRANSPORT_IS_PLAYING: u32 = 1 << 4;
534const CLAP_TRANSPORT_IS_LOOP_ACTIVE: u32 = 1 << 6;
535const CLAP_BEATTIME_FACTOR: i64 = 1_i64 << 31;
536const CLAP_SECTIME_FACTOR: i64 = 1_i64 << 31;
537
538#[repr(C)]
539struct ClapEventMidi {
540    header: ClapEventHeader,
541    port_index: u16,
542    data: [u8; 3],
543}
544
545#[repr(C)]
546struct ClapEventParamValue {
547    header: ClapEventHeader,
548    param_id: u32,
549    cookie: *mut c_void,
550    note_id: i32,
551    port_index: i16,
552    channel: i16,
553    key: i16,
554    value: f64,
555}
556
557#[repr(C)]
558struct ClapEventParamGesture {
559    header: ClapEventHeader,
560    param_id: u32,
561}
562
563#[repr(C)]
564struct ClapEventTransport {
565    header: ClapEventHeader,
566    flags: u32,
567    song_pos_beats: i64,
568    song_pos_seconds: i64,
569    tempo: f64,
570    tempo_inc: f64,
571    loop_start_beats: i64,
572    loop_end_beats: i64,
573    loop_start_seconds: i64,
574    loop_end_seconds: i64,
575    bar_start: i64,
576    bar_number: i32,
577    tsig_num: u16,
578    tsig_denom: u16,
579}
580
581#[repr(C)]
582struct ClapParamInfoRaw {
583    id: u32,
584    flags: u32,
585    cookie: *mut c_void,
586    name: [c_char; 256],
587    module: [c_char; 1024],
588    min_value: f64,
589    max_value: f64,
590    default_value: f64,
591}
592
593#[repr(C)]
594struct ClapPluginParams {
595    count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
596    get_info: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapParamInfoRaw) -> bool>,
597    get_value: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut f64) -> bool>,
598    value_to_text:
599        Option<unsafe extern "C" fn(*const ClapPlugin, u32, f64, *mut c_char, u32) -> bool>,
600    text_to_value:
601        Option<unsafe extern "C" fn(*const ClapPlugin, u32, *const c_char, *mut f64) -> bool>,
602    flush: Option<
603        unsafe extern "C" fn(*const ClapPlugin, *const ClapInputEvents, *const ClapOutputEvents),
604    >,
605}
606
607#[repr(C)]
608struct ClapPluginStateExt {
609    save: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapOStream) -> bool>,
610    load: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapIStream) -> bool>,
611}
612
613#[repr(C)]
614struct ClapAudioPortInfoRaw {
615    id: u32,
616    name: [c_char; 256],
617    flags: u32,
618    channel_count: u32,
619    port_type: *const c_char,
620    in_place_pair: u32,
621}
622
623#[repr(C)]
624struct ClapPluginAudioPorts {
625    count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
626    get: Option<
627        unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapAudioPortInfoRaw) -> bool,
628    >,
629}
630
631#[repr(C)]
632struct ClapNotePortInfoRaw {
633    id: u16,
634    supported_dialects: u32,
635    preferred_dialect: u32,
636    name: [c_char; 256],
637}
638
639#[repr(C)]
640struct ClapPluginNotePorts {
641    count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
642    get: Option<
643        unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapNotePortInfoRaw) -> bool,
644    >,
645}
646
647#[repr(C)]
648struct ClapPluginGui {
649    is_api_supported: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
650    get_preferred_api:
651        Option<unsafe extern "C" fn(*const ClapPlugin, *mut *const c_char, *mut bool) -> bool>,
652    create: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
653    destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
654    set_scale: Option<unsafe extern "C" fn(*const ClapPlugin, f64) -> bool>,
655    get_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
656    can_resize: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
657    get_resize_hints: Option<unsafe extern "C" fn(*const ClapPlugin, *mut c_void) -> bool>,
658    adjust_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
659    set_size: Option<unsafe extern "C" fn(*const ClapPlugin, u32, u32) -> bool>,
660    set_parent: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_void) -> bool>,
661    set_transient: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_void) -> bool>,
662    suggest_title: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char)>,
663    show: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
664    hide: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
665}
666
667#[repr(C)]
668struct ClapHostThreadCheck {
669    is_main_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
670    is_audio_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
671}
672
673#[repr(C)]
674struct ClapHostLatency {
675    changed: Option<unsafe extern "C" fn(*const ClapHost)>,
676}
677
678#[repr(C)]
679struct ClapHostTail {
680    changed: Option<unsafe extern "C" fn(*const ClapHost)>,
681}
682
683#[repr(C)]
684struct ClapHostTimerSupport {
685    register_timer: Option<unsafe extern "C" fn(*const ClapHost, u32, *mut u32) -> bool>,
686    unregister_timer: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
687}
688
689#[repr(C)]
690struct ClapOStream {
691    ctx: *mut c_void,
692    write: Option<unsafe extern "C" fn(*const ClapOStream, *const c_void, u64) -> i64>,
693}
694
695#[repr(C)]
696struct ClapIStream {
697    ctx: *mut c_void,
698    read: Option<unsafe extern "C" fn(*const ClapIStream, *mut c_void, u64) -> i64>,
699}
700
701#[repr(C)]
702struct ClapAudioBuffer {
703    data32: *mut *mut f32,
704    data64: *mut *mut f64,
705    channel_count: u32,
706    latency: u32,
707    constant_mask: u64,
708}
709
710#[repr(C)]
711struct ClapProcess {
712    steady_time: i64,
713    frames_count: u32,
714    transport: *const c_void,
715    audio_inputs: *mut ClapAudioBuffer,
716    audio_outputs: *mut ClapAudioBuffer,
717    audio_inputs_count: u32,
718    audio_outputs_count: u32,
719    in_events: *const ClapInputEvents,
720    out_events: *mut ClapOutputEvents,
721}
722
723enum ClapInputEvent {
724    Midi(ClapEventMidi),
725    ParamValue(ClapEventParamValue),
726    ParamGesture(ClapEventParamGesture),
727    Transport(ClapEventTransport),
728}
729
730impl ClapInputEvent {
731    fn header_ptr(&self) -> *const ClapEventHeader {
732        match self {
733            Self::Midi(e) => &e.header as *const ClapEventHeader,
734            Self::ParamValue(e) => &e.header as *const ClapEventHeader,
735            Self::ParamGesture(e) => &e.header as *const ClapEventHeader,
736            Self::Transport(e) => &e.header as *const ClapEventHeader,
737        }
738    }
739}
740
741struct ClapInputEventsCtx {
742    events: Vec<ClapInputEvent>,
743}
744
745struct ClapOutputEventsCtx {
746    midi_events: Vec<ClapMidiOutputEvent>,
747    param_values: Vec<PendingParamValue>,
748}
749
750struct ClapIStreamCtx<'a> {
751    bytes: &'a [u8],
752    offset: usize,
753}
754
755struct HostRuntime {
756    callback_flags: Box<UnsafeMutex<HostCallbackFlags>>,
757    host: ClapHost,
758}
759
760#[derive(Default, Clone, Copy)]
761struct HostCallbackFlags {
762    restart: bool,
763    process: bool,
764    callback: bool,
765}
766
767impl HostRuntime {
768    fn new() -> Result<Self, String> {
769        let mut callback_flags = Box::new(UnsafeMutex::new(HostCallbackFlags::default()));
770        let host = ClapHost {
771            clap_version: CLAP_VERSION,
772            host_data: (&mut *callback_flags as *mut UnsafeMutex<HostCallbackFlags>)
773                .cast::<c_void>(),
774            name: c"Maolan".as_ptr(),
775            vendor: c"Maolan".as_ptr(),
776            url: c"https://example.invalid".as_ptr(),
777            version: c"0.0.1".as_ptr(),
778            get_extension: Some(host_get_extension),
779            request_restart: Some(host_request_restart),
780            request_process: Some(host_request_process),
781            request_callback: Some(host_request_callback),
782        };
783        Ok(Self {
784            callback_flags,
785            host,
786        })
787    }
788
789    fn take_callback_flags(&self) -> HostCallbackFlags {
790        let flags = self.callback_flags.lock();
791        let out = *flags;
792        *flags = HostCallbackFlags::default();
793        out
794    }
795}
796
797// SAFETY: HostRuntime owns stable CString storage and a CLAP host struct that
798// contains raw pointers into that owned storage. The data is immutable after
799// construction and safe to share/move across threads.
800unsafe impl Send for HostRuntime {}
801// SAFETY: See Send rationale above; HostRuntime has no interior mutation.
802unsafe impl Sync for HostRuntime {}
803
804struct PluginHandle {
805    _library: Library,
806    entry: *const ClapPluginEntry,
807    plugin: *const ClapPlugin,
808}
809
810// SAFETY: PluginHandle only stores pointers/libraries managed by the CLAP ABI.
811// Access to plugin processing is synchronized by the engine track scheduling.
812unsafe impl Send for PluginHandle {}
813// SAFETY: Shared references do not mutate PluginHandle fields directly.
814unsafe impl Sync for PluginHandle {}
815
816impl PluginHandle {
817    fn load(
818        plugin_path: &str,
819        plugin_id: Option<&str>,
820        host_runtime: Arc<HostRuntime>,
821        sample_rate: f64,
822        frames: u32,
823    ) -> Result<Self, String> {
824        let factory_id = c"clap.plugin-factory";
825
826        // SAFETY: We keep `library` alive for at least as long as plugin and entry pointers.
827        let library = unsafe { Library::new(plugin_path) }.map_err(|e| e.to_string())?;
828        // SAFETY: Symbol name and type follow CLAP ABI (`clap_entry` global variable).
829        let entry_ptr = unsafe {
830            let sym = library
831                .get::<*const ClapPluginEntry>(b"clap_entry\0")
832                .map_err(|e| e.to_string())?;
833            *sym
834        };
835        if entry_ptr.is_null() {
836            return Err("CLAP entry symbol is null".to_string());
837        }
838        // SAFETY: entry pointer comes from validated CLAP symbol.
839        let entry = unsafe { &*entry_ptr };
840        let init = entry
841            .init
842            .ok_or_else(|| "CLAP entry missing init()".to_string())?;
843        let host_ptr = &host_runtime.host as *const ClapHost;
844        // SAFETY: Valid host pointer for plugin bundle.
845        if unsafe { !init(host_ptr) } {
846            return Err(format!("CLAP entry init failed for {plugin_path}"));
847        }
848        let get_factory = entry
849            .get_factory
850            .ok_or_else(|| "CLAP entry missing get_factory()".to_string())?;
851        // SAFETY: Factory id is a static NUL-terminated C string.
852        let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
853        if factory.is_null() {
854            return Err("CLAP plugin factory not found".to_string());
855        }
856        // SAFETY: factory pointer was validated above.
857        let factory_ref = unsafe { &*factory };
858        let get_count = factory_ref
859            .get_plugin_count
860            .ok_or_else(|| "CLAP factory missing get_plugin_count()".to_string())?;
861        let get_desc = factory_ref
862            .get_plugin_descriptor
863            .ok_or_else(|| "CLAP factory missing get_plugin_descriptor()".to_string())?;
864        let create = factory_ref
865            .create_plugin
866            .ok_or_else(|| "CLAP factory missing create_plugin()".to_string())?;
867
868        // SAFETY: factory function pointers are valid CLAP ABI function pointers.
869        let count = unsafe { get_count(factory) };
870        if count == 0 {
871            return Err("CLAP factory returned zero plugins".to_string());
872        }
873        let mut selected_id = None::<CString>;
874        for i in 0..count {
875            // SAFETY: i < count.
876            let desc = unsafe { get_desc(factory, i) };
877            if desc.is_null() {
878                continue;
879            }
880            // SAFETY: descriptor pointer comes from factory.
881            let desc = unsafe { &*desc };
882            if desc.id.is_null() {
883                continue;
884            }
885            // SAFETY: descriptor id is NUL-terminated per CLAP ABI.
886            let id = unsafe { CStr::from_ptr(desc.id) };
887            let id_str = id.to_string_lossy();
888            if plugin_id.is_none() || plugin_id == Some(id_str.as_ref()) {
889                selected_id = Some(
890                    CString::new(id_str.as_ref()).map_err(|e| format!("Invalid plugin id: {e}"))?,
891                );
892                break;
893            }
894        }
895        let selected_id = selected_id.ok_or_else(|| {
896            if let Some(id) = plugin_id {
897                format!("CLAP descriptor id not found in bundle: {id}")
898            } else {
899                "CLAP descriptor not found".to_string()
900            }
901        })?;
902        // SAFETY: valid host pointer and plugin id.
903        let plugin = unsafe { create(factory, &host_runtime.host, selected_id.as_ptr()) };
904        if plugin.is_null() {
905            return Err("CLAP factory create_plugin failed".to_string());
906        }
907        // SAFETY: plugin pointer validated above.
908        let plugin_ref = unsafe { &*plugin };
909        let plugin_init = plugin_ref
910            .init
911            .ok_or_else(|| "CLAP plugin missing init()".to_string())?;
912        // SAFETY: plugin pointer and function pointer follow CLAP ABI.
913        if unsafe { !plugin_init(plugin) } {
914            return Err("CLAP plugin init() failed".to_string());
915        }
916        if let Some(activate) = plugin_ref.activate {
917            // SAFETY: plugin pointer and arguments are valid for current engine buffer config.
918            if unsafe { !activate(plugin, sample_rate, frames.max(1), frames.max(1)) } {
919                return Err("CLAP plugin activate() failed".to_string());
920            }
921        }
922        if let Some(start_processing) = plugin_ref.start_processing {
923            // SAFETY: plugin activated above.
924            if unsafe { !start_processing(plugin) } {
925                return Err("CLAP plugin start_processing() failed".to_string());
926            }
927        }
928        Ok(Self {
929            _library: library,
930            entry: entry_ptr,
931            plugin,
932        })
933    }
934
935    fn process(&self, process: &mut ClapProcess) -> Result<bool, String> {
936        // SAFETY: plugin pointer is valid for lifetime of self.
937        let plugin = unsafe { &*self.plugin };
938        let Some(process_fn) = plugin.process else {
939            return Ok(false);
940        };
941        // SAFETY: process struct references live buffers for the duration of call.
942        let _status = unsafe { process_fn(self.plugin, process as *const _) };
943        Ok(true)
944    }
945
946    fn reset(&self) {
947        // SAFETY: plugin pointer valid during self lifetime.
948        let plugin = unsafe { &*self.plugin };
949        if let Some(reset) = plugin.reset {
950            // SAFETY: function pointer follows CLAP ABI.
951            unsafe { reset(self.plugin) };
952        }
953    }
954
955    fn on_main_thread(&self) {
956        // SAFETY: plugin pointer valid during self lifetime.
957        let plugin = unsafe { &*self.plugin };
958        if let Some(on_main_thread) = plugin.on_main_thread {
959            // SAFETY: function pointer follows CLAP ABI.
960            unsafe { on_main_thread(self.plugin) };
961        }
962    }
963
964    fn params_ext(&self) -> Option<&ClapPluginParams> {
965        let ext_id = c"clap.params";
966        // SAFETY: plugin pointer is valid while self is alive.
967        let plugin = unsafe { &*self.plugin };
968        let get_extension = plugin.get_extension?;
969        // SAFETY: extension id is a valid static C string.
970        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
971        if ext_ptr.is_null() {
972            return None;
973        }
974        // SAFETY: CLAP guarantees extension pointer layout for requested extension id.
975        Some(unsafe { &*(ext_ptr as *const ClapPluginParams) })
976    }
977
978    fn state_ext(&self) -> Option<&ClapPluginStateExt> {
979        let ext_id = c"clap.state";
980        // SAFETY: plugin pointer is valid while self is alive.
981        let plugin = unsafe { &*self.plugin };
982        let get_extension = plugin.get_extension?;
983        // SAFETY: extension id is valid static C string.
984        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
985        if ext_ptr.is_null() {
986            return None;
987        }
988        // SAFETY: extension pointer layout follows clap.state ABI.
989        Some(unsafe { &*(ext_ptr as *const ClapPluginStateExt) })
990    }
991
992    fn audio_ports_ext(&self) -> Option<&ClapPluginAudioPorts> {
993        let ext_id = c"clap.audio-ports";
994        // SAFETY: plugin pointer is valid while self is alive.
995        let plugin = unsafe { &*self.plugin };
996        let get_extension = plugin.get_extension?;
997        // SAFETY: extension id is valid static C string.
998        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
999        if ext_ptr.is_null() {
1000            return None;
1001        }
1002        // SAFETY: extension pointer layout follows clap.audio-ports ABI.
1003        Some(unsafe { &*(ext_ptr as *const ClapPluginAudioPorts) })
1004    }
1005
1006    fn note_ports_ext(&self) -> Option<&ClapPluginNotePorts> {
1007        let ext_id = c"clap.note-ports";
1008        // SAFETY: plugin pointer is valid while self is alive.
1009        let plugin = unsafe { &*self.plugin };
1010        let get_extension = plugin.get_extension?;
1011        // SAFETY: extension id is valid static C string.
1012        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1013        if ext_ptr.is_null() {
1014            return None;
1015        }
1016        // SAFETY: extension pointer layout follows clap.note-ports ABI.
1017        Some(unsafe { &*(ext_ptr as *const ClapPluginNotePorts) })
1018    }
1019
1020    fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
1021        let Some(params) = self.params_ext() else {
1022            return Vec::new();
1023        };
1024        let Some(count_fn) = params.count else {
1025            return Vec::new();
1026        };
1027        let Some(get_info_fn) = params.get_info else {
1028            return Vec::new();
1029        };
1030        // SAFETY: function pointers come from plugin extension table.
1031        let count = unsafe { count_fn(self.plugin) };
1032        let mut out = Vec::with_capacity(count as usize);
1033        for idx in 0..count {
1034            let mut info = ClapParamInfoRaw {
1035                id: 0,
1036                flags: 0,
1037                cookie: std::ptr::null_mut(),
1038                name: [0; 256],
1039                module: [0; 1024],
1040                min_value: 0.0,
1041                max_value: 1.0,
1042                default_value: 0.0,
1043            };
1044            // SAFETY: info points to valid writable struct.
1045            if unsafe { !get_info_fn(self.plugin, idx, &mut info as *mut _) } {
1046                continue;
1047            }
1048            out.push(ClapParameterInfo {
1049                id: info.id,
1050                name: c_char_buf_to_string(&info.name),
1051                module: c_char_buf_to_string(&info.module),
1052                min_value: info.min_value,
1053                max_value: info.max_value,
1054                default_value: info.default_value,
1055            });
1056        }
1057        out
1058    }
1059
1060    fn parameter_values(&self, infos: &[ClapParameterInfo]) -> HashMap<u32, f64> {
1061        let mut out = HashMap::new();
1062        let Some(params) = self.params_ext() else {
1063            for info in infos {
1064                out.insert(info.id, info.default_value);
1065            }
1066            return out;
1067        };
1068        let Some(get_value_fn) = params.get_value else {
1069            for info in infos {
1070                out.insert(info.id, info.default_value);
1071            }
1072            return out;
1073        };
1074        for info in infos {
1075            let mut value = info.default_value;
1076            // SAFETY: pointer to stack `value` is valid and param id belongs to plugin metadata.
1077            if unsafe { !get_value_fn(self.plugin, info.id, &mut value as *mut _) } {
1078                value = info.default_value;
1079            }
1080            out.insert(info.id, value);
1081        }
1082        out
1083    }
1084
1085    fn snapshot_state(&self) -> Result<ClapPluginState, String> {
1086        let Some(state_ext) = self.state_ext() else {
1087            return Ok(ClapPluginState { bytes: Vec::new() });
1088        };
1089        let Some(save_fn) = state_ext.save else {
1090            return Ok(ClapPluginState { bytes: Vec::new() });
1091        };
1092        let mut bytes = Vec::<u8>::new();
1093        let mut stream = ClapOStream {
1094            ctx: (&mut bytes as *mut Vec<u8>).cast::<c_void>(),
1095            write: Some(clap_ostream_write),
1096        };
1097        // SAFETY: stream callbacks reference `bytes` for duration of call.
1098        if unsafe {
1099            !save_fn(
1100                self.plugin,
1101                &mut stream as *mut ClapOStream as *const ClapOStream,
1102            )
1103        } {
1104            return Err("CLAP state save failed".to_string());
1105        }
1106        Ok(ClapPluginState { bytes })
1107    }
1108
1109    fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
1110        let Some(state_ext) = self.state_ext() else {
1111            return Ok(());
1112        };
1113        let Some(load_fn) = state_ext.load else {
1114            return Ok(());
1115        };
1116        let mut ctx = ClapIStreamCtx {
1117            bytes: &state.bytes,
1118            offset: 0,
1119        };
1120        let mut stream = ClapIStream {
1121            ctx: (&mut ctx as *mut ClapIStreamCtx).cast::<c_void>(),
1122            read: Some(clap_istream_read),
1123        };
1124        // SAFETY: stream callbacks reference `ctx` for duration of call.
1125        if unsafe {
1126            !load_fn(
1127                self.plugin,
1128                &mut stream as *mut ClapIStream as *const ClapIStream,
1129            )
1130        } {
1131            return Err("CLAP state load failed".to_string());
1132        }
1133        Ok(())
1134    }
1135
1136    fn audio_port_layout(&self) -> (Option<usize>, Option<usize>) {
1137        let Some(ext) = self.audio_ports_ext() else {
1138            return (None, None);
1139        };
1140        let Some(count_fn) = ext.count else {
1141            return (None, None);
1142        };
1143        // SAFETY: function pointer comes from plugin extension table.
1144        let in_count = unsafe { count_fn(self.plugin, true) } as usize;
1145        // SAFETY: function pointer comes from plugin extension table.
1146        let out_count = unsafe { count_fn(self.plugin, false) } as usize;
1147        (Some(in_count.max(1)), Some(out_count.max(1)))
1148    }
1149
1150    fn note_port_layout(&self) -> (Option<usize>, Option<usize>) {
1151        let Some(ext) = self.note_ports_ext() else {
1152            return (None, None);
1153        };
1154        let Some(count_fn) = ext.count else {
1155            return (None, None);
1156        };
1157        // SAFETY: function pointer comes from plugin extension table.
1158        let in_count = unsafe { count_fn(self.plugin, true) } as usize;
1159        // SAFETY: function pointer comes from plugin extension table.
1160        let out_count = unsafe { count_fn(self.plugin, false) } as usize;
1161        (Some(in_count.max(1)), Some(out_count.max(1)))
1162    }
1163}
1164
1165impl Drop for PluginHandle {
1166    fn drop(&mut self) {
1167        // SAFETY: pointers were obtained from valid CLAP entry and plugin factory.
1168        unsafe {
1169            if !self.plugin.is_null() {
1170                let plugin = &*self.plugin;
1171                if let Some(stop_processing) = plugin.stop_processing {
1172                    stop_processing(self.plugin);
1173                }
1174                if let Some(deactivate) = plugin.deactivate {
1175                    deactivate(self.plugin);
1176                }
1177                if let Some(destroy) = plugin.destroy {
1178                    destroy(self.plugin);
1179                }
1180            }
1181            if !self.entry.is_null() {
1182                let entry = &*self.entry;
1183                if let Some(deinit) = entry.deinit {
1184                    deinit();
1185                }
1186            }
1187        }
1188    }
1189}
1190
1191static HOST_THREAD_CHECK_EXT: ClapHostThreadCheck = ClapHostThreadCheck {
1192    is_main_thread: Some(host_is_main_thread),
1193    is_audio_thread: Some(host_is_audio_thread),
1194};
1195static HOST_LATENCY_EXT: ClapHostLatency = ClapHostLatency {
1196    changed: Some(host_latency_changed),
1197};
1198static HOST_TAIL_EXT: ClapHostTail = ClapHostTail {
1199    changed: Some(host_tail_changed),
1200};
1201static HOST_TIMER_EXT: ClapHostTimerSupport = ClapHostTimerSupport {
1202    register_timer: Some(host_timer_register),
1203    unregister_timer: Some(host_timer_unregister),
1204};
1205static NEXT_TIMER_ID: AtomicU32 = AtomicU32::new(1);
1206
1207unsafe extern "C" fn host_get_extension(
1208    _host: *const ClapHost,
1209    _extension_id: *const c_char,
1210) -> *const c_void {
1211    if _extension_id.is_null() {
1212        return std::ptr::null();
1213    }
1214    // SAFETY: extension id is expected to be a valid NUL-terminated string.
1215    let id = unsafe { CStr::from_ptr(_extension_id) }.to_string_lossy();
1216    match id.as_ref() {
1217        "clap.host.thread-check" => {
1218            (&HOST_THREAD_CHECK_EXT as *const ClapHostThreadCheck).cast::<c_void>()
1219        }
1220        "clap.host.latency" => (&HOST_LATENCY_EXT as *const ClapHostLatency).cast::<c_void>(),
1221        "clap.host.tail" => (&HOST_TAIL_EXT as *const ClapHostTail).cast::<c_void>(),
1222        "clap.host.timer-support" => {
1223            (&HOST_TIMER_EXT as *const ClapHostTimerSupport).cast::<c_void>()
1224        }
1225        _ => std::ptr::null(),
1226    }
1227}
1228
1229unsafe extern "C" fn host_request_process(_host: *const ClapHost) {
1230    if _host.is_null() {
1231        return;
1232    }
1233    // SAFETY: host_data was initialized to point to HostCallbackFlags storage.
1234    let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1235    if flags_ptr.is_null() {
1236        return;
1237    }
1238    // SAFETY: flags_ptr is valid for plugin lifetime.
1239    unsafe {
1240        (*flags_ptr).lock().process = true;
1241    }
1242}
1243
1244unsafe extern "C" fn host_request_callback(_host: *const ClapHost) {
1245    if _host.is_null() {
1246        return;
1247    }
1248    // SAFETY: host_data was initialized to point to HostCallbackFlags storage.
1249    let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1250    if flags_ptr.is_null() {
1251        return;
1252    }
1253    // SAFETY: flags_ptr is valid for plugin lifetime.
1254    unsafe {
1255        (*flags_ptr).lock().callback = true;
1256    }
1257}
1258
1259unsafe extern "C" fn host_request_restart(_host: *const ClapHost) {
1260    if _host.is_null() {
1261        return;
1262    }
1263    // SAFETY: host_data was initialized to point to HostCallbackFlags storage.
1264    let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1265    if flags_ptr.is_null() {
1266        return;
1267    }
1268    // SAFETY: flags_ptr is valid for plugin lifetime.
1269    unsafe {
1270        (*flags_ptr).lock().restart = true;
1271    }
1272}
1273
1274unsafe extern "C" fn host_is_main_thread(_host: *const ClapHost) -> bool {
1275    true
1276}
1277
1278unsafe extern "C" fn host_is_audio_thread(_host: *const ClapHost) -> bool {
1279    true
1280}
1281
1282unsafe extern "C" fn host_latency_changed(_host: *const ClapHost) {}
1283
1284unsafe extern "C" fn host_tail_changed(_host: *const ClapHost) {}
1285
1286unsafe extern "C" fn host_timer_register(
1287    _host: *const ClapHost,
1288    _period_ms: u32,
1289    timer_id: *mut u32,
1290) -> bool {
1291    if timer_id.is_null() {
1292        return false;
1293    }
1294    let id = NEXT_TIMER_ID.fetch_add(1, Ordering::Relaxed);
1295    // SAFETY: timer_id points to writable u32 provided by plugin.
1296    unsafe {
1297        *timer_id = id;
1298    }
1299    true
1300}
1301
1302unsafe extern "C" fn host_timer_unregister(_host: *const ClapHost, _timer_id: u32) -> bool {
1303    true
1304}
1305
1306unsafe extern "C" fn input_events_size(_list: *const ClapInputEvents) -> u32 {
1307    if _list.is_null() {
1308        return 0;
1309    }
1310    // SAFETY: ctx points to ClapInputEventsCtx owned by process_native.
1311    let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
1312    if ctx.is_null() {
1313        return 0;
1314    }
1315    // SAFETY: ctx is valid during process callback lifetime.
1316    unsafe { (*ctx).events.len() as u32 }
1317}
1318
1319unsafe extern "C" fn input_events_get(
1320    _list: *const ClapInputEvents,
1321    _index: u32,
1322) -> *const ClapEventHeader {
1323    if _list.is_null() {
1324        return std::ptr::null();
1325    }
1326    // SAFETY: ctx points to ClapInputEventsCtx owned by process_native.
1327    let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
1328    if ctx.is_null() {
1329        return std::ptr::null();
1330    }
1331    // SAFETY: ctx is valid during process callback lifetime.
1332    let events = unsafe { &(*ctx).events };
1333    let Some(event) = events.get(_index as usize) else {
1334        return std::ptr::null();
1335    };
1336    event.header_ptr()
1337}
1338
1339unsafe extern "C" fn output_events_try_push(
1340    _list: *const ClapOutputEvents,
1341    _event: *const ClapEventHeader,
1342) -> bool {
1343    if _list.is_null() || _event.is_null() {
1344        return false;
1345    }
1346    // SAFETY: ctx points to ClapOutputEventsCtx owned by process_native.
1347    let ctx = unsafe { (*_list).ctx as *mut ClapOutputEventsCtx };
1348    if ctx.is_null() {
1349        return false;
1350    }
1351    // SAFETY: event pointer is valid for callback lifetime.
1352    let header = unsafe { &*_event };
1353    if header.space_id != CLAP_CORE_EVENT_SPACE_ID {
1354        return false;
1355    }
1356    match header.type_ {
1357        CLAP_EVENT_MIDI => {
1358            if (header.size as usize) < std::mem::size_of::<ClapEventMidi>() {
1359                return false;
1360            }
1361            // SAFETY: validated type/size above.
1362            let midi = unsafe { &*(_event as *const ClapEventMidi) };
1363            // SAFETY: ctx pointer is valid and uniquely owned during processing.
1364            unsafe {
1365                (*ctx).midi_events.push(ClapMidiOutputEvent {
1366                    port: midi.port_index as usize,
1367                    event: MidiEvent::new(header.time, midi.data.to_vec()),
1368                });
1369            }
1370            true
1371        }
1372        CLAP_EVENT_PARAM_VALUE => {
1373            if (header.size as usize) < std::mem::size_of::<ClapEventParamValue>() {
1374                return false;
1375            }
1376            // SAFETY: validated type/size above.
1377            let param = unsafe { &*(_event as *const ClapEventParamValue) };
1378            // SAFETY: ctx pointer is valid and uniquely owned during processing.
1379            unsafe {
1380                (*ctx).param_values.push(PendingParamValue {
1381                    param_id: param.param_id,
1382                    value: param.value,
1383                });
1384            }
1385            true
1386        }
1387        _ => false,
1388    }
1389}
1390
1391fn input_events_from(
1392    midi_events: &[MidiEvent],
1393    param_events: &[PendingParamEvent],
1394    sample_rate: f64,
1395    transport: ClapTransportInfo,
1396) -> (ClapInputEvents, Box<ClapInputEventsCtx>) {
1397    let mut events = Vec::with_capacity(midi_events.len() + param_events.len() + 1);
1398    let bpm = transport.bpm.max(1.0);
1399    let sample_rate = sample_rate.max(1.0);
1400    let seconds = transport.transport_sample as f64 / sample_rate;
1401    let song_pos_seconds = (seconds * CLAP_SECTIME_FACTOR as f64) as i64;
1402    let beats = seconds * (bpm / 60.0);
1403    let song_pos_beats = (beats * CLAP_BEATTIME_FACTOR as f64) as i64;
1404    let mut flags = CLAP_TRANSPORT_HAS_TEMPO
1405        | CLAP_TRANSPORT_HAS_BEATS_TIMELINE
1406        | CLAP_TRANSPORT_HAS_SECONDS_TIMELINE
1407        | CLAP_TRANSPORT_HAS_TIME_SIGNATURE;
1408    if transport.playing {
1409        flags |= CLAP_TRANSPORT_IS_PLAYING;
1410    }
1411    let (loop_start_seconds, loop_end_seconds, loop_start_beats, loop_end_beats) =
1412        if transport.loop_enabled {
1413            if let Some((loop_start, loop_end)) = transport.loop_range_samples {
1414                flags |= CLAP_TRANSPORT_IS_LOOP_ACTIVE;
1415                let ls_sec = loop_start as f64 / sample_rate;
1416                let le_sec = loop_end as f64 / sample_rate;
1417                let ls_beats = ls_sec * (bpm / 60.0);
1418                let le_beats = le_sec * (bpm / 60.0);
1419                (
1420                    (ls_sec * CLAP_SECTIME_FACTOR as f64) as i64,
1421                    (le_sec * CLAP_SECTIME_FACTOR as f64) as i64,
1422                    (ls_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
1423                    (le_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
1424                )
1425            } else {
1426                (0, 0, 0, 0)
1427            }
1428        } else {
1429            (0, 0, 0, 0)
1430        };
1431    let ts_num = transport.tsig_num.max(1);
1432    let ts_denom = transport.tsig_denom.max(1);
1433    let beats_per_bar = ts_num as f64 * (4.0 / ts_denom as f64);
1434    let bar_number = if beats_per_bar > 0.0 {
1435        (beats / beats_per_bar).floor().max(0.0) as i32
1436    } else {
1437        0
1438    };
1439    let bar_start_beats = (bar_number as f64 * beats_per_bar * CLAP_BEATTIME_FACTOR as f64) as i64;
1440    events.push(ClapInputEvent::Transport(ClapEventTransport {
1441        header: ClapEventHeader {
1442            size: std::mem::size_of::<ClapEventTransport>() as u32,
1443            time: 0,
1444            space_id: CLAP_CORE_EVENT_SPACE_ID,
1445            type_: CLAP_EVENT_TRANSPORT,
1446            flags: 0,
1447        },
1448        flags,
1449        song_pos_beats,
1450        song_pos_seconds,
1451        tempo: bpm,
1452        tempo_inc: 0.0,
1453        loop_start_beats,
1454        loop_end_beats,
1455        loop_start_seconds,
1456        loop_end_seconds,
1457        bar_start: bar_start_beats,
1458        bar_number,
1459        tsig_num: ts_num,
1460        tsig_denom: ts_denom,
1461    }));
1462    for event in midi_events {
1463        if event.data.is_empty() {
1464            continue;
1465        }
1466        let mut data = [0_u8; 3];
1467        let bytes = event.data.len().min(3);
1468        data[..bytes].copy_from_slice(&event.data[..bytes]);
1469        events.push(ClapInputEvent::Midi(ClapEventMidi {
1470            header: ClapEventHeader {
1471                size: std::mem::size_of::<ClapEventMidi>() as u32,
1472                time: event.frame,
1473                space_id: CLAP_CORE_EVENT_SPACE_ID,
1474                type_: CLAP_EVENT_MIDI,
1475                flags: 0,
1476            },
1477            port_index: 0,
1478            data,
1479        }));
1480    }
1481    for param in param_events {
1482        match *param {
1483            PendingParamEvent::Value {
1484                param_id,
1485                value,
1486                frame,
1487            } => events.push(ClapInputEvent::ParamValue(ClapEventParamValue {
1488                header: ClapEventHeader {
1489                    size: std::mem::size_of::<ClapEventParamValue>() as u32,
1490                    time: frame,
1491                    space_id: CLAP_CORE_EVENT_SPACE_ID,
1492                    type_: CLAP_EVENT_PARAM_VALUE,
1493                    flags: 0,
1494                },
1495                param_id,
1496                cookie: std::ptr::null_mut(),
1497                note_id: -1,
1498                port_index: -1,
1499                channel: -1,
1500                key: -1,
1501                value,
1502            })),
1503            PendingParamEvent::GestureBegin { param_id, frame } => {
1504                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
1505                    header: ClapEventHeader {
1506                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1507                        time: frame,
1508                        space_id: CLAP_CORE_EVENT_SPACE_ID,
1509                        type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
1510                        flags: 0,
1511                    },
1512                    param_id,
1513                }))
1514            }
1515            PendingParamEvent::GestureEnd { param_id, frame } => {
1516                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
1517                    header: ClapEventHeader {
1518                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1519                        time: frame,
1520                        space_id: CLAP_CORE_EVENT_SPACE_ID,
1521                        type_: CLAP_EVENT_PARAM_GESTURE_END,
1522                        flags: 0,
1523                    },
1524                    param_id,
1525                }))
1526            }
1527        }
1528    }
1529    events.sort_by_key(|event| match event {
1530        ClapInputEvent::Midi(e) => e.header.time,
1531        ClapInputEvent::ParamValue(e) => e.header.time,
1532        ClapInputEvent::ParamGesture(e) => e.header.time,
1533        ClapInputEvent::Transport(e) => e.header.time,
1534    });
1535    let mut ctx = Box::new(ClapInputEventsCtx { events });
1536    let list = ClapInputEvents {
1537        ctx: (&mut *ctx as *mut ClapInputEventsCtx).cast::<c_void>(),
1538        size: Some(input_events_size),
1539        get: Some(input_events_get),
1540    };
1541    (list, ctx)
1542}
1543
1544fn output_events_ctx(capacity: usize) -> (ClapOutputEvents, Box<ClapOutputEventsCtx>) {
1545    let mut ctx = Box::new(ClapOutputEventsCtx {
1546        midi_events: Vec::with_capacity(capacity),
1547        param_values: Vec::with_capacity(capacity / 2),
1548    });
1549    let list = ClapOutputEvents {
1550        ctx: (&mut *ctx as *mut ClapOutputEventsCtx).cast::<c_void>(),
1551        try_push: Some(output_events_try_push),
1552    };
1553    (list, ctx)
1554}
1555
1556fn c_char_buf_to_string<const N: usize>(buf: &[c_char; N]) -> String {
1557    let bytes = buf
1558        .iter()
1559        .take_while(|&&b| b != 0)
1560        .map(|&b| b as u8)
1561        .collect::<Vec<u8>>();
1562    String::from_utf8_lossy(&bytes).to_string()
1563}
1564
1565fn split_plugin_spec(spec: &str) -> (&str, Option<&str>) {
1566    if let Some((path, id)) = spec.split_once("::")
1567        && !id.trim().is_empty()
1568    {
1569        return (path, Some(id.trim()));
1570    }
1571    (spec, None)
1572}
1573
1574unsafe extern "C" fn clap_ostream_write(
1575    stream: *const ClapOStream,
1576    buffer: *const c_void,
1577    size: u64,
1578) -> i64 {
1579    if stream.is_null() || buffer.is_null() {
1580        return -1;
1581    }
1582    // SAFETY: ctx is initialized by snapshot_state and valid during callback.
1583    let ctx = unsafe { (*stream).ctx as *mut Vec<u8> };
1584    if ctx.is_null() {
1585        return -1;
1586    }
1587    let n = (size as usize).min(isize::MAX as usize);
1588    // SAFETY: source pointer is valid for `n` bytes per caller contract.
1589    let src = unsafe { std::slice::from_raw_parts(buffer.cast::<u8>(), n) };
1590    // SAFETY: ctx points to writable Vec<u8>.
1591    unsafe {
1592        (*ctx).extend_from_slice(src);
1593    }
1594    n as i64
1595}
1596
1597unsafe extern "C" fn clap_istream_read(
1598    stream: *const ClapIStream,
1599    buffer: *mut c_void,
1600    size: u64,
1601) -> i64 {
1602    if stream.is_null() || buffer.is_null() {
1603        return -1;
1604    }
1605    // SAFETY: ctx is initialized by restore_state and valid during callback.
1606    let ctx = unsafe { (*stream).ctx as *mut ClapIStreamCtx<'_> };
1607    if ctx.is_null() {
1608        return -1;
1609    }
1610    // SAFETY: ctx points to valid read context.
1611    let ctx = unsafe { &mut *ctx };
1612    let remaining = ctx.bytes.len().saturating_sub(ctx.offset);
1613    if remaining == 0 {
1614        return 0;
1615    }
1616    let n = remaining.min(size as usize);
1617    // SAFETY: destination pointer is valid for `n` bytes per caller contract.
1618    let dst = unsafe { std::slice::from_raw_parts_mut(buffer.cast::<u8>(), n) };
1619    dst.copy_from_slice(&ctx.bytes[ctx.offset..ctx.offset + n]);
1620    ctx.offset += n;
1621    n as i64
1622}
1623
1624pub fn list_plugins() -> Vec<ClapPluginInfo> {
1625    list_plugins_with_capabilities(false)
1626}
1627
1628pub fn list_plugins_with_capabilities(scan_capabilities: bool) -> Vec<ClapPluginInfo> {
1629    let mut roots = default_clap_search_roots();
1630
1631    if let Ok(extra) = std::env::var("CLAP_PATH") {
1632        for p in std::env::split_paths(&extra) {
1633            if !p.as_os_str().is_empty() {
1634                roots.push(p);
1635            }
1636        }
1637    }
1638
1639    let mut out = Vec::new();
1640    for root in roots {
1641        collect_clap_plugins(&root, &mut out, scan_capabilities);
1642    }
1643
1644    out.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
1645    out.dedup_by(|a, b| a.path.eq_ignore_ascii_case(&b.path));
1646    out
1647}
1648
1649fn collect_clap_plugins(root: &Path, out: &mut Vec<ClapPluginInfo>, scan_capabilities: bool) {
1650    let Ok(entries) = std::fs::read_dir(root) else {
1651        return;
1652    };
1653    for entry in entries.flatten() {
1654        let path = entry.path();
1655        let Ok(ft) = entry.file_type() else {
1656            continue;
1657        };
1658        if ft.is_symlink() {
1659            continue;
1660        }
1661
1662        if ft.is_dir() {
1663            collect_clap_plugins(&path, out, scan_capabilities);
1664            continue;
1665        }
1666
1667        if path
1668            .extension()
1669            .is_some_and(|ext| ext.eq_ignore_ascii_case("clap"))
1670        {
1671            let infos = scan_bundle_descriptors(&path, scan_capabilities);
1672            if infos.is_empty() {
1673                let name = path
1674                    .file_stem()
1675                    .map(|s| s.to_string_lossy().to_string())
1676                    .unwrap_or_else(|| path.to_string_lossy().to_string());
1677                out.push(ClapPluginInfo {
1678                    name,
1679                    path: path.to_string_lossy().to_string(),
1680                    capabilities: None,
1681                });
1682            } else {
1683                out.extend(infos);
1684            }
1685        }
1686    }
1687}
1688
1689fn scan_bundle_descriptors(path: &Path, scan_capabilities: bool) -> Vec<ClapPluginInfo> {
1690    let path_str = path.to_string_lossy().to_string();
1691    let factory_id = c"clap.plugin-factory";
1692    let host_runtime = match HostRuntime::new() {
1693        Ok(runtime) => runtime,
1694        Err(_) => return Vec::new(),
1695    };
1696    // SAFETY: path points to plugin module file.
1697    let library = match unsafe { Library::new(path) } {
1698        Ok(lib) => lib,
1699        Err(_) => return Vec::new(),
1700    };
1701    // SAFETY: symbol is CLAP entry pointer.
1702    let entry_ptr = unsafe {
1703        match library.get::<*const ClapPluginEntry>(b"clap_entry\0") {
1704            Ok(sym) => *sym,
1705            Err(_) => return Vec::new(),
1706        }
1707    };
1708    if entry_ptr.is_null() {
1709        return Vec::new();
1710    }
1711    // SAFETY: entry pointer validated above.
1712    let entry = unsafe { &*entry_ptr };
1713    let Some(init) = entry.init else {
1714        return Vec::new();
1715    };
1716    let host_ptr = &host_runtime.host;
1717    // SAFETY: valid host pointer.
1718    if unsafe { !init(host_ptr) } {
1719        return Vec::new();
1720    }
1721    let mut out = Vec::new();
1722    if let Some(get_factory) = entry.get_factory {
1723        // SAFETY: static factory id.
1724        let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
1725        if !factory.is_null() {
1726            // SAFETY: factory pointer validated above.
1727            let factory_ref = unsafe { &*factory };
1728            if let (Some(get_count), Some(get_desc)) = (
1729                factory_ref.get_plugin_count,
1730                factory_ref.get_plugin_descriptor,
1731            ) {
1732                // SAFETY: function pointer from plugin.
1733                let count = unsafe { get_count(factory) };
1734                for i in 0..count {
1735                    // SAFETY: i < count.
1736                    let desc = unsafe { get_desc(factory, i) };
1737                    if desc.is_null() {
1738                        continue;
1739                    }
1740                    // SAFETY: descriptor pointer from plugin factory.
1741                    let desc = unsafe { &*desc };
1742                    if desc.id.is_null() || desc.name.is_null() {
1743                        continue;
1744                    }
1745                    // SAFETY: CLAP descriptor strings are NUL-terminated.
1746                    let id = unsafe { CStr::from_ptr(desc.id) }
1747                        .to_string_lossy()
1748                        .to_string();
1749                    // SAFETY: CLAP descriptor strings are NUL-terminated.
1750                    let name = unsafe { CStr::from_ptr(desc.name) }
1751                        .to_string_lossy()
1752                        .to_string();
1753
1754                    let capabilities = if scan_capabilities {
1755                        scan_plugin_capabilities(factory_ref, factory, &host_runtime.host, &id)
1756                    } else {
1757                        None
1758                    };
1759
1760                    out.push(ClapPluginInfo {
1761                        name,
1762                        path: format!("{path_str}::{id}"),
1763                        capabilities,
1764                    });
1765                }
1766            }
1767        }
1768    }
1769    // SAFETY: deinit belongs to entry and is valid after init.
1770    if let Some(deinit) = entry.deinit {
1771        unsafe { deinit() };
1772    }
1773    out
1774}
1775
1776fn scan_plugin_capabilities(
1777    factory: &ClapPluginFactory,
1778    factory_ptr: *const ClapPluginFactory,
1779    host: &ClapHost,
1780    plugin_id: &str,
1781) -> Option<ClapPluginCapabilities> {
1782    let create = factory.create_plugin?;
1783
1784    let id_cstring = CString::new(plugin_id).ok()?;
1785    // SAFETY: valid factory, host, and id pointers.
1786    let plugin = unsafe { create(factory_ptr, host, id_cstring.as_ptr()) };
1787    if plugin.is_null() {
1788        return None;
1789    }
1790
1791    // SAFETY: plugin pointer validated above.
1792    let plugin_ref = unsafe { &*plugin };
1793    let plugin_init = plugin_ref.init?;
1794
1795    // SAFETY: plugin pointer and function pointer follow CLAP ABI.
1796    if unsafe { !plugin_init(plugin) } {
1797        return None;
1798    }
1799
1800    let mut capabilities = ClapPluginCapabilities {
1801        has_gui: false,
1802        gui_apis: Vec::new(),
1803        supports_embedded: false,
1804        supports_floating: false,
1805        has_params: false,
1806        has_state: false,
1807        has_audio_ports: false,
1808        has_note_ports: false,
1809    };
1810
1811    // Check for extensions
1812    if let Some(get_extension) = plugin_ref.get_extension {
1813        // Check GUI extension
1814        let gui_ext_id = c"clap.gui";
1815        // SAFETY: extension id is valid static C string.
1816        let gui_ptr = unsafe { get_extension(plugin, gui_ext_id.as_ptr()) };
1817        if !gui_ptr.is_null() {
1818            capabilities.has_gui = true;
1819            // SAFETY: CLAP guarantees extension pointer layout for requested extension id.
1820            let gui = unsafe { &*(gui_ptr as *const ClapPluginGui) };
1821
1822            // Check which GUI APIs are supported
1823            if let Some(is_api_supported) = gui.is_api_supported {
1824                for api in ["x11", "cocoa"] {
1825                    if let Ok(api_cstr) = CString::new(api) {
1826                        // Check embedded mode
1827                        // SAFETY: valid plugin and API string pointers.
1828                        if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), false) } {
1829                            capabilities.gui_apis.push(format!("{} (embedded)", api));
1830                            capabilities.supports_embedded = true;
1831                        }
1832                        // Check floating mode
1833                        // SAFETY: valid plugin and API string pointers.
1834                        if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), true) } {
1835                            if !capabilities.supports_embedded {
1836                                capabilities.gui_apis.push(format!("{} (floating)", api));
1837                            }
1838                            capabilities.supports_floating = true;
1839                        }
1840                    }
1841                }
1842            }
1843        }
1844
1845        // Check params extension
1846        let params_ext_id = c"clap.params";
1847        // SAFETY: extension id is valid static C string.
1848        let params_ptr = unsafe { get_extension(plugin, params_ext_id.as_ptr()) };
1849        capabilities.has_params = !params_ptr.is_null();
1850
1851        // Check state extension
1852        let state_ext_id = c"clap.state";
1853        // SAFETY: extension id is valid static C string.
1854        let state_ptr = unsafe { get_extension(plugin, state_ext_id.as_ptr()) };
1855        capabilities.has_state = !state_ptr.is_null();
1856
1857        // Check audio-ports extension
1858        let audio_ports_ext_id = c"clap.audio-ports";
1859        // SAFETY: extension id is valid static C string.
1860        let audio_ports_ptr = unsafe { get_extension(plugin, audio_ports_ext_id.as_ptr()) };
1861        capabilities.has_audio_ports = !audio_ports_ptr.is_null();
1862
1863        // Check note-ports extension
1864        let note_ports_ext_id = c"clap.note-ports";
1865        // SAFETY: extension id is valid static C string.
1866        let note_ports_ptr = unsafe { get_extension(plugin, note_ports_ext_id.as_ptr()) };
1867        capabilities.has_note_ports = !note_ports_ptr.is_null();
1868    }
1869
1870    // Clean up plugin instance
1871    if let Some(destroy) = plugin_ref.destroy {
1872        // SAFETY: plugin pointer is valid.
1873        unsafe { destroy(plugin) };
1874    }
1875
1876    Some(capabilities)
1877}
1878
1879fn default_clap_search_roots() -> Vec<PathBuf> {
1880    let mut roots = Vec::new();
1881
1882    #[cfg(target_os = "macos")]
1883    {
1884        paths::push_macos_audio_plugin_roots(&mut roots, "CLAP");
1885    }
1886
1887    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
1888    {
1889        paths::push_unix_plugin_roots(&mut roots, "clap");
1890    }
1891
1892    roots
1893}