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