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::cell::Cell;
14use std::collections::{HashMap, HashSet};
15use std::ffi::{CStr, CString, c_char, c_void};
16use std::fmt;
17use std::path::{Path, PathBuf};
18use std::sync::Arc;
19use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
20use std::time::{Duration, Instant};
21
22#[derive(Clone, Debug, PartialEq)]
23pub struct ClapParameterInfo {
24    pub id: u32,
25    pub name: String,
26    pub module: String,
27    pub min_value: f64,
28    pub max_value: f64,
29    pub default_value: f64,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33pub struct ClapPluginState {
34    pub bytes: Vec<u8>,
35}
36
37type AudioPortLayout = (Vec<usize>, usize);
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct ClapMidiOutputEvent {
41    pub port: usize,
42    pub event: MidiEvent,
43}
44
45#[derive(Clone, Copy, Debug, Default)]
46pub struct ClapTransportInfo {
47    pub transport_sample: usize,
48    pub playing: bool,
49    pub loop_enabled: bool,
50    pub loop_range_samples: Option<(usize, usize)>,
51    pub bpm: f64,
52    pub tsig_num: u16,
53    pub tsig_denom: u16,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub struct ClapGuiInfo {
58    pub api: String,
59    pub supports_embedded: bool,
60}
61
62#[derive(Clone, Copy, Debug)]
63struct PendingParamValue {
64    param_id: u32,
65    value: f64,
66}
67
68#[derive(Clone, Copy, Debug)]
69struct PendingParamGesture {
70    param_id: u32,
71    is_begin: bool,
72}
73
74#[derive(Clone, Copy, Debug)]
75pub struct ClapParamUpdate {
76    pub param_id: u32,
77    pub value: f64,
78}
79
80#[derive(Clone, Copy, Debug)]
81enum PendingParamEvent {
82    Value {
83        param_id: u32,
84        value: f64,
85        frame: u32,
86    },
87    GestureBegin {
88        param_id: u32,
89        frame: u32,
90    },
91    GestureEnd {
92        param_id: u32,
93        frame: u32,
94    },
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
98pub struct ClapPluginInfo {
99    pub name: String,
100    pub path: String,
101    pub capabilities: Option<ClapPluginCapabilities>,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
105pub struct ClapPluginCapabilities {
106    pub has_gui: bool,
107    pub gui_apis: Vec<String>,
108    pub supports_embedded: bool,
109    pub supports_floating: bool,
110    pub has_params: bool,
111    pub has_state: bool,
112    pub audio_inputs: usize,
113    pub audio_outputs: usize,
114    pub midi_inputs: usize,
115    pub midi_outputs: usize,
116}
117
118pub struct ClapProcessor {
119    path: String,
120    plugin_id: String,
121    name: String,
122    sample_rate: f64,
123    buffer_size: usize,
124    audio_inputs: Vec<Arc<AudioIO>>,
125    audio_outputs: Vec<Arc<AudioIO>>,
126    input_port_channels: Vec<usize>,
127    output_port_channels: Vec<usize>,
128    midi_input_ports: usize,
129    midi_output_ports: usize,
130    main_audio_inputs: usize,
131    main_audio_outputs: usize,
132    host_runtime: Arc<HostRuntime>,
133    plugin_handle: Arc<PluginHandle>,
134    param_infos: Arc<Vec<ClapParameterInfo>>,
135    param_values: UnsafeMutex<HashMap<u32, f64>>,
136    pending_param_events: UnsafeMutex<Vec<PendingParamEvent>>,
137    pending_param_events_ui: UnsafeMutex<Vec<PendingParamEvent>>,
138    active_local_gestures: UnsafeMutex<HashSet<u32>>,
139    bypassed: Arc<AtomicBool>,
140}
141
142pub type SharedClapProcessor = Arc<UnsafeMutex<ClapProcessor>>;
143
144impl fmt::Debug for ClapProcessor {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.debug_struct("ClapProcessor")
147            .field("path", &self.path)
148            .field("plugin_id", &self.plugin_id)
149            .field("name", &self.name)
150            .field("audio_inputs", &self.audio_inputs.len())
151            .field("audio_outputs", &self.audio_outputs.len())
152            .field("input_port_channels", &self.input_port_channels)
153            .field("output_port_channels", &self.output_port_channels)
154            .field("midi_input_ports", &self.midi_input_ports)
155            .field("midi_output_ports", &self.midi_output_ports)
156            .field("main_audio_inputs", &self.main_audio_inputs)
157            .field("main_audio_outputs", &self.main_audio_outputs)
158            .finish()
159    }
160}
161
162impl ClapProcessor {
163    pub fn new(
164        sample_rate: f64,
165        buffer_size: usize,
166        plugin_spec: &str,
167        input_count: usize,
168        output_count: usize,
169    ) -> Result<Self, String> {
170        let _thread_scope = HostThreadScope::enter_main();
171        let (plugin_path, plugin_id) = split_plugin_spec(plugin_spec);
172        let host_runtime = Arc::new(HostRuntime::new()?);
173        let plugin_handle = Arc::new(PluginHandle::load(
174            plugin_path,
175            plugin_id,
176            host_runtime.clone(),
177            sample_rate,
178            buffer_size as u32,
179        )?);
180        let (input_layout_opt, output_layout_opt) = plugin_handle.audio_port_channels();
181        let input_port_channels_opt = input_layout_opt.as_ref().map(|(c, _)| c.clone());
182        let output_port_channels_opt = output_layout_opt.as_ref().map(|(c, _)| c.clone());
183        let discovered_inputs = input_layout_opt.as_ref().map(|(c, _)| c.len());
184        let discovered_outputs = output_layout_opt.as_ref().map(|(c, _)| c.len());
185        let (discovered_midi_inputs, discovered_midi_outputs) = plugin_handle.note_port_layout();
186        let resolved_inputs = discovered_inputs.unwrap_or(input_count);
187        let resolved_outputs = discovered_outputs.unwrap_or(output_count);
188        let main_audio_inputs = input_layout_opt
189            .as_ref()
190            .map(|(_, main)| *main)
191            .unwrap_or(input_count);
192        let main_audio_outputs = output_layout_opt
193            .as_ref()
194            .map(|(_, main)| *main)
195            .unwrap_or(output_count);
196        let audio_inputs = (0..resolved_inputs)
197            .map(|_| Arc::new(AudioIO::new(buffer_size)))
198            .collect();
199        let audio_outputs = (0..resolved_outputs)
200            .map(|_| Arc::new(AudioIO::new(buffer_size)))
201            .collect();
202        let param_infos = Arc::new(plugin_handle.parameter_infos());
203        let param_values = UnsafeMutex::new(plugin_handle.parameter_values(&param_infos));
204        Ok(Self {
205            path: plugin_spec.to_string(),
206            plugin_id: plugin_handle.plugin_id().to_string(),
207            name: plugin_handle.plugin_name().to_string(),
208            sample_rate,
209            buffer_size: buffer_size.max(1),
210            audio_inputs,
211            audio_outputs,
212            input_port_channels: input_port_channels_opt
213                .unwrap_or_else(|| vec![1; resolved_inputs]),
214            output_port_channels: output_port_channels_opt
215                .unwrap_or_else(|| vec![1; resolved_outputs]),
216            midi_input_ports: discovered_midi_inputs.unwrap_or(0),
217            midi_output_ports: discovered_midi_outputs.unwrap_or(0),
218            main_audio_inputs,
219            main_audio_outputs,
220            host_runtime,
221            plugin_handle,
222            param_infos,
223            param_values,
224            pending_param_events: UnsafeMutex::new(Vec::new()),
225            pending_param_events_ui: UnsafeMutex::new(Vec::new()),
226            active_local_gestures: UnsafeMutex::new(HashSet::new()),
227            bypassed: Arc::new(AtomicBool::new(false)),
228        })
229    }
230
231    pub fn setup_audio_ports(&self) {
232        for port in &self.audio_inputs {
233            port.setup();
234        }
235        for port in &self.audio_outputs {
236            port.setup();
237        }
238    }
239
240    pub fn process_with_audio_io(&self, frames: usize) {
241        let _ = self.process_with_midi(frames, &[], ClapTransportInfo::default());
242    }
243
244    pub fn set_bypassed(&self, bypassed: bool) {
245        self.bypassed.store(bypassed, Ordering::Relaxed);
246    }
247
248    pub fn is_bypassed(&self) -> bool {
249        self.bypassed.load(Ordering::Relaxed)
250    }
251
252    fn bypass_copy_inputs_to_outputs(&self) {
253        for (input, output) in self.audio_inputs.iter().zip(self.audio_outputs.iter()) {
254            let src = input.buffer.lock();
255            let dst = output.buffer.lock();
256            dst.fill(0.0);
257            for (d, s) in dst.iter_mut().zip(src.iter()) {
258                *d = *s;
259            }
260            *output.finished.lock() = true;
261        }
262        for output in self.audio_outputs.iter().skip(self.audio_inputs.len()) {
263            output.buffer.lock().fill(0.0);
264            *output.finished.lock() = true;
265        }
266    }
267
268    pub fn process_with_midi(
269        &self,
270        frames: usize,
271        midi_in: &[MidiEvent],
272        transport: ClapTransportInfo,
273    ) -> Vec<ClapMidiOutputEvent> {
274        let started = Instant::now();
275        for port in &self.audio_inputs {
276            if port.ready() {
277                port.process();
278            }
279        }
280        if self.bypassed.load(Ordering::Relaxed) {
281            self.bypass_copy_inputs_to_outputs();
282            return Vec::new();
283        }
284        let (processed, processed_midi) = match self.process_native(frames, midi_in, transport) {
285            Ok(ok) => ok,
286            Err(err) => {
287                tracing::warn!(
288                    "CLAP process failed for '{}' ({}): {}",
289                    self.name,
290                    self.path,
291                    err
292                );
293                (false, Vec::new())
294            }
295        };
296        let elapsed = started.elapsed();
297        if elapsed > Duration::from_millis(20) {
298            tracing::warn!(
299                "Slow CLAP process '{}' ({}) took {:.3} ms for {} frames",
300                self.name,
301                self.path,
302                elapsed.as_secs_f64() * 1000.0,
303                frames
304            );
305        }
306        if !processed {
307            for out in &self.audio_outputs {
308                let out_buf = out.buffer.lock();
309                out_buf.fill(0.0);
310                *out.finished.lock() = true;
311            }
312        }
313        processed_midi
314    }
315
316    pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
317        self.param_infos.as_ref().clone()
318    }
319
320    pub fn parameter_values(&self) -> HashMap<u32, f64> {
321        self.param_values.lock().clone()
322    }
323
324    pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
325        self.set_parameter_at(param_id, value, 0)
326    }
327
328    pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
329        let _thread_scope = HostThreadScope::enter_main();
330        let Some(info) = self.param_infos.iter().find(|p| p.id == param_id) else {
331            return Err(format!("Unknown CLAP parameter id: {param_id}"));
332        };
333        let clamped = value.clamp(info.min_value, info.max_value);
334        if self.is_parameter_edit_active(param_id) {
335            self.param_values.lock().insert(param_id, clamped);
336            return Ok(());
337        }
338        self.pending_param_events
339            .lock()
340            .push(PendingParamEvent::Value {
341                param_id,
342                value: clamped,
343                frame,
344            });
345        self.pending_param_events_ui
346            .lock()
347            .push(PendingParamEvent::Value {
348                param_id,
349                value: clamped,
350                frame,
351            });
352        self.param_values.lock().insert(param_id, clamped);
353        Ok(())
354    }
355
356    pub fn begin_parameter_edit(&self, param_id: u32) -> Result<(), String> {
357        self.begin_parameter_edit_at(param_id, 0)
358    }
359
360    pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
361        let _thread_scope = HostThreadScope::enter_main();
362        if !self.param_infos.iter().any(|p| p.id == param_id) {
363            return Err(format!("Unknown CLAP parameter id: {param_id}"));
364        }
365        self.pending_param_events
366            .lock()
367            .push(PendingParamEvent::GestureBegin { param_id, frame });
368        self.pending_param_events_ui
369            .lock()
370            .push(PendingParamEvent::GestureBegin { param_id, frame });
371        self.active_local_gestures.lock().insert(param_id);
372        Ok(())
373    }
374
375    pub fn end_parameter_edit(&self, param_id: u32) -> Result<(), String> {
376        self.end_parameter_edit_at(param_id, 0)
377    }
378
379    pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
380        let _thread_scope = HostThreadScope::enter_main();
381        if !self.param_infos.iter().any(|p| p.id == param_id) {
382            return Err(format!("Unknown CLAP parameter id: {param_id}"));
383        }
384        self.pending_param_events
385            .lock()
386            .push(PendingParamEvent::GestureEnd { param_id, frame });
387        self.pending_param_events_ui
388            .lock()
389            .push(PendingParamEvent::GestureEnd { param_id, frame });
390        self.active_local_gestures.lock().remove(&param_id);
391        Ok(())
392    }
393
394    pub fn is_parameter_edit_active(&self, param_id: u32) -> bool {
395        self.active_local_gestures.lock().contains(&param_id)
396    }
397
398    pub fn snapshot_state(&self) -> Result<ClapPluginState, String> {
399        let _thread_scope = HostThreadScope::enter_main();
400        self.plugin_handle.snapshot_state()
401    }
402
403    pub fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
404        let _thread_scope = HostThreadScope::enter_main();
405        self.plugin_handle.restore_state(state)
406    }
407
408    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
409        &self.audio_inputs
410    }
411
412    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
413        &self.audio_outputs
414    }
415
416    pub fn main_audio_input_count(&self) -> usize {
417        self.main_audio_inputs
418    }
419
420    pub fn main_audio_output_count(&self) -> usize {
421        self.main_audio_outputs
422    }
423
424    pub fn midi_input_count(&self) -> usize {
425        self.midi_input_ports
426    }
427
428    pub fn midi_output_count(&self) -> usize {
429        self.midi_output_ports
430    }
431
432    pub fn path(&self) -> &str {
433        &self.path
434    }
435
436    pub fn plugin_id(&self) -> &str {
437        &self.plugin_id
438    }
439
440    pub fn name(&self) -> &str {
441        &self.name
442    }
443
444    pub fn ui_begin_session(&self) {
445        self.host_runtime.begin_ui_session();
446    }
447
448    pub fn ui_end_session(&self) {
449        self.host_runtime.end_ui_session();
450    }
451
452    pub fn ui_should_close(&self) -> bool {
453        self.host_runtime.ui_should_close()
454    }
455
456    pub fn ui_take_due_timers(&self) -> Vec<u32> {
457        self.host_runtime.ui_take_due_timers()
458    }
459
460    pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
461        let pending_ui_events = std::mem::take(self.pending_param_events_ui.lock());
462        if pending_ui_events.is_empty() && !self.host_runtime.ui_take_param_flush_requested() {
463            return Vec::new();
464        }
465        let _thread_scope = HostThreadScope::enter_main();
466        let (updates, gestures) = self.plugin_handle.flush_params(&pending_ui_events);
467        for gesture in gestures {
468            if gesture.is_begin {
469                self.active_local_gestures.lock().insert(gesture.param_id);
470            } else {
471                self.active_local_gestures.lock().remove(&gesture.param_id);
472            }
473        }
474        if updates.is_empty() {
475            return Vec::new();
476        }
477        let values = &mut *self.param_values.lock();
478        let mut out = Vec::with_capacity(updates.len());
479        for update in updates {
480            values.insert(update.param_id, update.value);
481            out.push(ClapParamUpdate {
482                param_id: update.param_id,
483                value: update.value,
484            });
485        }
486        out
487    }
488
489    pub fn ui_take_state_update(&self) -> Option<ClapPluginState> {
490        if !self.host_runtime.ui_take_state_dirty_requested() {
491            return None;
492        }
493        let _thread_scope = HostThreadScope::enter_main();
494        self.plugin_handle.snapshot_state().ok()
495    }
496
497    pub fn gui_info(&self) -> Result<ClapGuiInfo, String> {
498        let _thread_scope = HostThreadScope::enter_main();
499        self.plugin_handle.gui_info()
500    }
501
502    pub fn gui_create(&self, api: &str, is_floating: bool) -> Result<(), String> {
503        let _thread_scope = HostThreadScope::enter_main();
504        self.plugin_handle.gui_create(api, is_floating)
505    }
506
507    pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
508        let _thread_scope = HostThreadScope::enter_main();
509        self.plugin_handle.gui_get_size()
510    }
511
512    pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
513        let _thread_scope = HostThreadScope::enter_main();
514        self.plugin_handle.gui_set_parent_x11(window)
515    }
516
517    pub fn gui_show(&self) -> Result<(), String> {
518        let _thread_scope = HostThreadScope::enter_main();
519        self.plugin_handle.gui_show()
520    }
521
522    pub fn gui_hide(&self) {
523        let _thread_scope = HostThreadScope::enter_main();
524        self.plugin_handle.gui_hide();
525    }
526
527    pub fn gui_destroy(&self) {
528        let _thread_scope = HostThreadScope::enter_main();
529        self.plugin_handle.gui_destroy();
530    }
531
532    pub fn gui_on_main_thread(&self) {
533        let _thread_scope = HostThreadScope::enter_main();
534        self.plugin_handle.on_main_thread();
535    }
536
537    pub fn gui_on_timer(&self, timer_id: u32) {
538        let _thread_scope = HostThreadScope::enter_main();
539        self.plugin_handle.gui_on_timer(timer_id);
540    }
541
542    pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
543        self.plugin_handle.get_note_names()
544    }
545
546    pub fn run_host_callbacks_main_thread(&self) {
547        let host_flags = self.host_runtime.take_callback_flags();
548        if host_flags.restart {
549            let _thread_scope = HostThreadScope::enter_main();
550            self.plugin_handle.reset();
551        }
552        if host_flags.callback {
553            let _thread_scope = HostThreadScope::enter_main();
554            self.plugin_handle.on_main_thread();
555        }
556        if host_flags.process {}
557    }
558
559    pub fn reconfigure_ports_if_needed(&mut self) -> Result<bool, String> {
560        let audio_rescan = self
561            .host_runtime
562            .state
563            .audio_ports_rescan_requested
564            .swap(0, Ordering::Acquire)
565            != 0;
566        let note_rescan = self
567            .host_runtime
568            .state
569            .note_ports_rescan_requested
570            .swap(0, Ordering::Acquire)
571            != 0;
572
573        if !audio_rescan && !note_rescan {
574            return Ok(false);
575        }
576
577        let _thread_scope = HostThreadScope::enter_main();
578
579        // Deactivate
580        self.plugin_handle.stop_processing();
581        self.plugin_handle.deactivate();
582
583        // Re-query ports
584        let (input_layout_opt, output_layout_opt) = self.plugin_handle.audio_port_channels();
585        let (discovered_midi_inputs, discovered_midi_outputs) =
586            self.plugin_handle.note_port_layout();
587
588        let input_port_channels_opt = input_layout_opt.as_ref().map(|(c, _)| c.clone());
589        let output_port_channels_opt = output_layout_opt.as_ref().map(|(c, _)| c.clone());
590        let discovered_inputs = input_layout_opt.as_ref().map(|(c, _)| c.len());
591        let discovered_outputs = output_layout_opt.as_ref().map(|(c, _)| c.len());
592
593        let resolved_inputs = discovered_inputs.unwrap_or(self.audio_inputs.len());
594        let resolved_outputs = discovered_outputs.unwrap_or(self.audio_outputs.len());
595        let main_audio_inputs = input_layout_opt
596            .as_ref()
597            .map(|(_, main)| *main)
598            .unwrap_or(self.main_audio_inputs);
599        let main_audio_outputs = output_layout_opt
600            .as_ref()
601            .map(|(_, main)| *main)
602            .unwrap_or(self.main_audio_outputs);
603
604        // Resize audio input buffers
605        while self.audio_inputs.len() < resolved_inputs {
606            self.audio_inputs
607                .push(Arc::new(AudioIO::new(self.buffer_size)));
608        }
609        self.audio_inputs.truncate(resolved_inputs);
610
611        // Resize audio output buffers
612        while self.audio_outputs.len() < resolved_outputs {
613            self.audio_outputs
614                .push(Arc::new(AudioIO::new(self.buffer_size)));
615        }
616        self.audio_outputs.truncate(resolved_outputs);
617
618        self.input_port_channels =
619            input_port_channels_opt.unwrap_or_else(|| vec![1; resolved_inputs]);
620        self.output_port_channels =
621            output_port_channels_opt.unwrap_or_else(|| vec![1; resolved_outputs]);
622        self.midi_input_ports = discovered_midi_inputs.unwrap_or(self.midi_input_ports);
623        self.midi_output_ports = discovered_midi_outputs.unwrap_or(self.midi_output_ports);
624        self.main_audio_inputs = main_audio_inputs;
625        self.main_audio_outputs = main_audio_outputs;
626
627        // Reactivate
628        self.plugin_handle.activate(
629            self.sample_rate,
630            self.buffer_size as u32,
631            self.buffer_size as u32,
632        )?;
633        self.plugin_handle.start_processing()?;
634
635        Ok(true)
636    }
637
638    fn process_native(
639        &self,
640        frames: usize,
641        midi_in: &[MidiEvent],
642        transport: ClapTransportInfo,
643    ) -> Result<(bool, Vec<ClapMidiOutputEvent>), String> {
644        if frames == 0 {
645            return Ok((true, Vec::new()));
646        }
647
648        let mut in_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_inputs.len());
649        let mut out_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_outputs.len());
650        let mut in_channel_scratch: Vec<Vec<f32>> = Vec::new();
651        let mut out_channel_scratch: Vec<Vec<f32>> = Vec::new();
652        let mut out_channel_scratch_ranges: Vec<(usize, usize)> =
653            Vec::with_capacity(self.audio_outputs.len());
654        let mut in_buffers = Vec::with_capacity(self.audio_inputs.len());
655        let mut out_buffers = Vec::with_capacity(self.audio_outputs.len());
656
657        for (port_idx, input) in self.audio_inputs.iter().enumerate() {
658            let buf = input.buffer.lock();
659            let channel_count = self
660                .input_port_channels
661                .get(port_idx)
662                .copied()
663                .unwrap_or(1)
664                .max(1);
665            let mut ptrs = Vec::with_capacity(channel_count);
666            ptrs.push(buf.as_ptr() as *mut f32);
667            for _ in 1..channel_count {
668                in_channel_scratch.push(buf.to_vec());
669                let idx = in_channel_scratch.len().saturating_sub(1);
670                ptrs.push(in_channel_scratch[idx].as_mut_ptr());
671            }
672            in_channel_ptrs.push(ptrs);
673            in_buffers.push(buf);
674        }
675        for (port_idx, output) in self.audio_outputs.iter().enumerate() {
676            let buf = output.buffer.lock();
677            let channel_count = self
678                .output_port_channels
679                .get(port_idx)
680                .copied()
681                .unwrap_or(1)
682                .max(1);
683            let mut ptrs = Vec::with_capacity(channel_count);
684            ptrs.push(buf.as_mut_ptr());
685            let scratch_start = out_channel_scratch.len();
686            for _ in 1..channel_count {
687                out_channel_scratch.push(vec![0.0; frames]);
688                let idx = out_channel_scratch.len().saturating_sub(1);
689                ptrs.push(out_channel_scratch[idx].as_mut_ptr());
690            }
691            let scratch_end = out_channel_scratch.len();
692            out_channel_scratch_ranges.push((scratch_start, scratch_end));
693            out_channel_ptrs.push(ptrs);
694            out_buffers.push(buf);
695        }
696
697        let mut in_audio = Vec::with_capacity(self.audio_inputs.len());
698        let mut out_audio = Vec::with_capacity(self.audio_outputs.len());
699
700        for ptrs in &mut in_channel_ptrs {
701            in_audio.push(ClapAudioBuffer {
702                data32: ptrs.as_mut_ptr(),
703                data64: std::ptr::null_mut(),
704                channel_count: ptrs.len() as u32,
705                latency: 0,
706                constant_mask: 0,
707            });
708        }
709        for ptrs in &mut out_channel_ptrs {
710            out_audio.push(ClapAudioBuffer {
711                data32: ptrs.as_mut_ptr(),
712                data64: std::ptr::null_mut(),
713                channel_count: ptrs.len() as u32,
714                latency: 0,
715                constant_mask: 0,
716            });
717        }
718
719        let pending_params = std::mem::take(self.pending_param_events.lock());
720        let (in_events, in_ctx) = input_events_from(
721            midi_in,
722            &pending_params,
723            self.sample_rate,
724            transport,
725            self.midi_input_ports > 0,
726        );
727        let out_cap = midi_in
728            .len()
729            .saturating_add(self.midi_output_ports.saturating_mul(64));
730        let (mut out_events, mut out_ctx) = output_events_ctx(out_cap);
731
732        let mut process = ClapProcess {
733            steady_time: -1,
734            frames_count: frames as u32,
735            transport: std::ptr::null(),
736            audio_inputs: in_audio.as_mut_ptr(),
737            audio_outputs: out_audio.as_mut_ptr(),
738            audio_inputs_count: in_audio.len() as u32,
739            audio_outputs_count: out_audio.len() as u32,
740            in_events: &in_events,
741            out_events: &mut out_events,
742        };
743
744        let _thread_scope = HostThreadScope::enter_audio();
745        let result = self.plugin_handle.process(&mut process);
746        drop(in_ctx);
747        for output in &self.audio_outputs {
748            *output.finished.lock() = true;
749        }
750        let processed = result?;
751        if processed {
752            for (port_idx, out_buf) in out_buffers.iter_mut().enumerate() {
753                let Some((scratch_start, scratch_end)) = out_channel_scratch_ranges.get(port_idx)
754                else {
755                    continue;
756                };
757                let scratch_count = scratch_end.saturating_sub(*scratch_start);
758                if scratch_count == 0 {
759                    continue;
760                }
761                let ch_count = scratch_count + 1;
762                for scratch in &out_channel_scratch[*scratch_start..*scratch_end] {
763                    let len = frames.min(scratch.len()).min(out_buf.len());
764                    crate::simd::add_inplace(&mut out_buf[..len], &scratch[..len]);
765                }
766                let inv = 1.0_f32 / ch_count as f32;
767                crate::simd::mul_inplace(&mut out_buf[..frames], inv);
768            }
769            for update in &out_ctx.param_values {
770                self.param_values
771                    .lock()
772                    .insert(update.param_id, update.value);
773            }
774            for gesture in &out_ctx.param_gestures {
775                if gesture.is_begin {
776                    self.active_local_gestures.lock().insert(gesture.param_id);
777                } else {
778                    self.active_local_gestures.lock().remove(&gesture.param_id);
779                }
780            }
781            Ok((true, std::mem::take(&mut out_ctx.midi_events)))
782        } else {
783            Ok((false, Vec::new()))
784        }
785    }
786}
787
788#[repr(C)]
789#[derive(Clone, Copy)]
790struct ClapVersion {
791    major: u32,
792    minor: u32,
793    revision: u32,
794}
795
796const CLAP_VERSION: ClapVersion = ClapVersion {
797    major: 1,
798    minor: 2,
799    revision: 0,
800};
801
802#[repr(C)]
803struct ClapHost {
804    clap_version: ClapVersion,
805    host_data: *mut c_void,
806    name: *const c_char,
807    vendor: *const c_char,
808    url: *const c_char,
809    version: *const c_char,
810    get_extension: Option<unsafe extern "C" fn(*const ClapHost, *const c_char) -> *const c_void>,
811    request_restart: Option<unsafe extern "C" fn(*const ClapHost)>,
812    request_process: Option<unsafe extern "C" fn(*const ClapHost)>,
813    request_callback: Option<unsafe extern "C" fn(*const ClapHost)>,
814}
815
816#[repr(C)]
817struct ClapPluginEntry {
818    clap_version: ClapVersion,
819    init: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
820    deinit: Option<unsafe extern "C" fn()>,
821    get_factory: Option<unsafe extern "C" fn(*const c_char) -> *const c_void>,
822}
823
824#[repr(C)]
825struct ClapPluginFactory {
826    get_plugin_count: Option<unsafe extern "C" fn(*const ClapPluginFactory) -> u32>,
827    get_plugin_descriptor:
828        Option<unsafe extern "C" fn(*const ClapPluginFactory, u32) -> *const ClapPluginDescriptor>,
829    create_plugin: Option<
830        unsafe extern "C" fn(
831            *const ClapPluginFactory,
832            *const ClapHost,
833            *const c_char,
834        ) -> *const ClapPlugin,
835    >,
836}
837
838#[repr(C)]
839struct ClapPluginDescriptor {
840    clap_version: ClapVersion,
841    id: *const c_char,
842    name: *const c_char,
843    vendor: *const c_char,
844    url: *const c_char,
845    manual_url: *const c_char,
846    support_url: *const c_char,
847    version: *const c_char,
848    description: *const c_char,
849    features: *const *const c_char,
850}
851
852#[repr(C)]
853struct ClapPlugin {
854    desc: *const ClapPluginDescriptor,
855    plugin_data: *mut c_void,
856    init: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
857    destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
858    activate: Option<unsafe extern "C" fn(*const ClapPlugin, f64, u32, u32) -> bool>,
859    deactivate: Option<unsafe extern "C" fn(*const ClapPlugin)>,
860    start_processing: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
861    stop_processing: Option<unsafe extern "C" fn(*const ClapPlugin)>,
862    reset: Option<unsafe extern "C" fn(*const ClapPlugin)>,
863    process: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapProcess) -> i32>,
864    get_extension: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char) -> *const c_void>,
865    on_main_thread: Option<unsafe extern "C" fn(*const ClapPlugin)>,
866}
867
868#[repr(C)]
869struct ClapInputEvents {
870    ctx: *const c_void,
871    size: Option<unsafe extern "C" fn(*const ClapInputEvents) -> u32>,
872    get: Option<unsafe extern "C" fn(*const ClapInputEvents, u32) -> *const ClapEventHeader>,
873}
874
875#[repr(C)]
876struct ClapOutputEvents {
877    ctx: *mut c_void,
878    try_push: Option<unsafe extern "C" fn(*const ClapOutputEvents, *const ClapEventHeader) -> bool>,
879}
880
881#[repr(C)]
882struct ClapEventHeader {
883    size: u32,
884    time: u32,
885    space_id: u16,
886    type_: u16,
887    flags: u32,
888}
889
890const CLAP_CORE_EVENT_SPACE_ID: u16 = 0;
891const CLAP_EVENT_NOTE_ON: u16 = 0;
892const CLAP_EVENT_NOTE_OFF: u16 = 1;
893const CLAP_EVENT_MIDI: u16 = 10;
894const CLAP_EVENT_PARAM_VALUE: u16 = 5;
895const CLAP_EVENT_PARAM_GESTURE_BEGIN: u16 = 6;
896const CLAP_EVENT_PARAM_GESTURE_END: u16 = 7;
897const CLAP_EVENT_IS_LIVE: u32 = 1 << 0;
898const CLAP_EVENT_TRANSPORT: u16 = 9;
899const CLAP_TRANSPORT_HAS_TEMPO: u32 = 1 << 0;
900const CLAP_TRANSPORT_HAS_BEATS_TIMELINE: u32 = 1 << 1;
901const CLAP_TRANSPORT_HAS_SECONDS_TIMELINE: u32 = 1 << 2;
902const CLAP_TRANSPORT_HAS_TIME_SIGNATURE: u32 = 1 << 3;
903const CLAP_TRANSPORT_IS_PLAYING: u32 = 1 << 4;
904const CLAP_TRANSPORT_IS_LOOP_ACTIVE: u32 = 1 << 6;
905const CLAP_BEATTIME_FACTOR: i64 = 1_i64 << 31;
906const CLAP_SECTIME_FACTOR: i64 = 1_i64 << 31;
907
908#[repr(C)]
909struct ClapEventMidi {
910    header: ClapEventHeader,
911    port_index: u16,
912    data: [u8; 3],
913}
914
915#[repr(C)]
916struct ClapEventNote {
917    header: ClapEventHeader,
918    note_id: i32,
919    port_index: i16,
920    channel: i16,
921    key: i16,
922    velocity: f64,
923}
924
925#[repr(C)]
926struct ClapEventParamValue {
927    header: ClapEventHeader,
928    param_id: u32,
929    cookie: *mut c_void,
930    note_id: i32,
931    port_index: i16,
932    channel: i16,
933    key: i16,
934    value: f64,
935}
936
937#[repr(C)]
938struct ClapEventParamGesture {
939    header: ClapEventHeader,
940    param_id: u32,
941}
942
943#[repr(C)]
944struct ClapEventTransport {
945    header: ClapEventHeader,
946    flags: u32,
947    song_pos_beats: i64,
948    song_pos_seconds: i64,
949    tempo: f64,
950    tempo_inc: f64,
951    loop_start_beats: i64,
952    loop_end_beats: i64,
953    loop_start_seconds: i64,
954    loop_end_seconds: i64,
955    bar_start: i64,
956    bar_number: i32,
957    tsig_num: u16,
958    tsig_denom: u16,
959}
960
961#[repr(C)]
962struct ClapParamInfoRaw {
963    id: u32,
964    flags: u32,
965    cookie: *mut c_void,
966    name: [c_char; 256],
967    module: [c_char; 1024],
968    min_value: f64,
969    max_value: f64,
970    default_value: f64,
971}
972
973#[repr(C)]
974struct ClapPluginParams {
975    count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
976    get_info: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapParamInfoRaw) -> bool>,
977    get_value: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut f64) -> bool>,
978    value_to_text:
979        Option<unsafe extern "C" fn(*const ClapPlugin, u32, f64, *mut c_char, u32) -> bool>,
980    text_to_value:
981        Option<unsafe extern "C" fn(*const ClapPlugin, u32, *const c_char, *mut f64) -> bool>,
982    flush: Option<
983        unsafe extern "C" fn(*const ClapPlugin, *const ClapInputEvents, *const ClapOutputEvents),
984    >,
985}
986
987#[repr(C)]
988struct ClapPluginStateExt {
989    save: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapOStream) -> bool>,
990    load: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapIStream) -> bool>,
991}
992
993#[repr(C)]
994struct ClapAudioPortInfoRaw {
995    id: u32,
996    name: [c_char; 256],
997    flags: u32,
998    channel_count: u32,
999    port_type: *const c_char,
1000    in_place_pair: u32,
1001}
1002
1003#[repr(C)]
1004struct ClapPluginAudioPorts {
1005    count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
1006    get: Option<
1007        unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapAudioPortInfoRaw) -> bool,
1008    >,
1009}
1010
1011#[repr(C)]
1012struct ClapNotePortInfoRaw {
1013    id: u16,
1014    supported_dialects: u32,
1015    preferred_dialect: u32,
1016    name: [c_char; 256],
1017}
1018
1019#[repr(C)]
1020struct ClapPluginNotePorts {
1021    count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
1022    get: Option<
1023        unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapNotePortInfoRaw) -> bool,
1024    >,
1025}
1026
1027#[repr(C)]
1028struct ClapPluginGui {
1029    is_api_supported: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
1030    get_preferred_api:
1031        Option<unsafe extern "C" fn(*const ClapPlugin, *mut *const c_char, *mut bool) -> bool>,
1032    create: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
1033    destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
1034    set_scale: Option<unsafe extern "C" fn(*const ClapPlugin, f64) -> bool>,
1035    get_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
1036    can_resize: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
1037    get_resize_hints: Option<unsafe extern "C" fn(*const ClapPlugin, *mut c_void) -> bool>,
1038    adjust_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
1039    set_size: Option<unsafe extern "C" fn(*const ClapPlugin, u32, u32) -> bool>,
1040    set_parent: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
1041    set_transient: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapWindow) -> bool>,
1042    suggest_title: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char)>,
1043    show: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
1044    hide: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
1045}
1046
1047#[repr(C)]
1048union ClapWindowHandle {
1049    x11: usize,
1050    native: *mut c_void,
1051    cocoa: *mut c_void,
1052}
1053
1054#[repr(C)]
1055struct ClapWindow {
1056    api: *const c_char,
1057    handle: ClapWindowHandle,
1058}
1059
1060#[repr(C)]
1061struct ClapPluginTimerSupport {
1062    on_timer: Option<unsafe extern "C" fn(*const ClapPlugin, u32)>,
1063}
1064
1065#[repr(C)]
1066struct ClapHostThreadCheck {
1067    is_main_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
1068    is_audio_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
1069}
1070
1071#[repr(C)]
1072struct ClapHostLatency {
1073    changed: Option<unsafe extern "C" fn(*const ClapHost)>,
1074}
1075
1076#[repr(C)]
1077struct ClapHostTail {
1078    changed: Option<unsafe extern "C" fn(*const ClapHost)>,
1079}
1080
1081#[repr(C)]
1082struct ClapHostTimerSupport {
1083    register_timer: Option<unsafe extern "C" fn(*const ClapHost, u32, *mut u32) -> bool>,
1084    unregister_timer: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
1085}
1086
1087#[repr(C)]
1088struct ClapHostGui {
1089    resize_hints_changed: Option<unsafe extern "C" fn(*const ClapHost)>,
1090    request_resize: Option<unsafe extern "C" fn(*const ClapHost, u32, u32) -> bool>,
1091    request_show: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
1092    request_hide: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
1093    closed: Option<unsafe extern "C" fn(*const ClapHost, bool)>,
1094}
1095
1096#[repr(C)]
1097struct ClapHostParams {
1098    rescan: Option<unsafe extern "C" fn(*const ClapHost, u32)>,
1099    clear: Option<unsafe extern "C" fn(*const ClapHost, u32, u32)>,
1100    request_flush: Option<unsafe extern "C" fn(*const ClapHost)>,
1101}
1102
1103#[repr(C)]
1104struct ClapHostState {
1105    mark_dirty: Option<unsafe extern "C" fn(*const ClapHost)>,
1106}
1107
1108#[repr(C)]
1109struct ClapHostAudioPorts {
1110    is_rescan_flag_supported: Option<unsafe extern "C" fn(*const ClapHost, flag: u32) -> bool>,
1111    rescan: Option<unsafe extern "C" fn(*const ClapHost, flags: u32)>,
1112}
1113
1114#[repr(C)]
1115struct ClapHostNotePorts {
1116    supported_dialects: Option<unsafe extern "C" fn(*const ClapHost) -> u32>,
1117    rescan: Option<unsafe extern "C" fn(*const ClapHost, flags: u32)>,
1118}
1119
1120#[repr(C)]
1121struct ClapNoteName {
1122    name: [c_char; 256],
1123    port: i16,
1124    key: i16,
1125    channel: i16,
1126}
1127
1128#[repr(C)]
1129struct ClapPluginNoteName {
1130    count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
1131    get: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapNoteName) -> bool>,
1132}
1133
1134#[repr(C)]
1135struct ClapHostNoteName {
1136    changed: Option<unsafe extern "C" fn(*const ClapHost)>,
1137}
1138
1139#[repr(C)]
1140struct ClapOStream {
1141    ctx: *mut c_void,
1142    write: Option<unsafe extern "C" fn(*const ClapOStream, *const c_void, u64) -> i64>,
1143}
1144
1145#[repr(C)]
1146struct ClapIStream {
1147    ctx: *mut c_void,
1148    read: Option<unsafe extern "C" fn(*const ClapIStream, *mut c_void, u64) -> i64>,
1149}
1150
1151#[repr(C)]
1152struct ClapAudioBuffer {
1153    data32: *mut *mut f32,
1154    data64: *mut *mut f64,
1155    channel_count: u32,
1156    latency: u32,
1157    constant_mask: u64,
1158}
1159
1160#[repr(C)]
1161struct ClapProcess {
1162    steady_time: i64,
1163    frames_count: u32,
1164    transport: *const c_void,
1165    audio_inputs: *mut ClapAudioBuffer,
1166    audio_outputs: *mut ClapAudioBuffer,
1167    audio_inputs_count: u32,
1168    audio_outputs_count: u32,
1169    in_events: *const ClapInputEvents,
1170    out_events: *mut ClapOutputEvents,
1171}
1172
1173enum ClapInputEvent {
1174    Note(ClapEventNote),
1175    Midi(ClapEventMidi),
1176    ParamValue(ClapEventParamValue),
1177    ParamGesture(ClapEventParamGesture),
1178    Transport(ClapEventTransport),
1179}
1180
1181impl UnsafeMutex<ClapProcessor> {
1182    pub fn setup_audio_ports(&self) {
1183        self.lock().setup_audio_ports();
1184    }
1185
1186    pub fn process_with_midi(
1187        &self,
1188        frames: usize,
1189        midi_events: &[MidiEvent],
1190        transport: ClapTransportInfo,
1191    ) -> Vec<ClapMidiOutputEvent> {
1192        self.lock()
1193            .process_with_midi(frames, midi_events, transport)
1194    }
1195
1196    pub fn set_bypassed(&self, bypassed: bool) {
1197        self.lock().set_bypassed(bypassed);
1198    }
1199
1200    pub fn is_bypassed(&self) -> bool {
1201        self.lock().is_bypassed()
1202    }
1203
1204    pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
1205        self.lock().parameter_infos()
1206    }
1207
1208    pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
1209        self.lock().set_parameter(param_id, value)
1210    }
1211
1212    pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
1213        self.lock().set_parameter_at(param_id, value, frame)
1214    }
1215
1216    pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
1217        self.lock().begin_parameter_edit_at(param_id, frame)
1218    }
1219
1220    pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
1221        self.lock().end_parameter_edit_at(param_id, frame)
1222    }
1223
1224    pub fn snapshot_state(&self) -> Result<ClapPluginState, String> {
1225        self.lock().snapshot_state()
1226    }
1227
1228    pub fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
1229        self.lock().restore_state(state)
1230    }
1231
1232    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
1233        self.lock().audio_inputs()
1234    }
1235
1236    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
1237        self.lock().audio_outputs()
1238    }
1239
1240    pub fn main_audio_input_count(&self) -> usize {
1241        self.lock().main_audio_input_count()
1242    }
1243
1244    pub fn main_audio_output_count(&self) -> usize {
1245        self.lock().main_audio_output_count()
1246    }
1247
1248    pub fn midi_input_count(&self) -> usize {
1249        self.lock().midi_input_count()
1250    }
1251
1252    pub fn midi_output_count(&self) -> usize {
1253        self.lock().midi_output_count()
1254    }
1255
1256    pub fn path(&self) -> String {
1257        self.lock().path().to_string()
1258    }
1259
1260    pub fn plugin_id(&self) -> String {
1261        self.lock().plugin_id().to_string()
1262    }
1263
1264    pub fn name(&self) -> String {
1265        self.lock().name().to_string()
1266    }
1267
1268    pub fn run_host_callbacks_main_thread(&self) {
1269        self.lock().run_host_callbacks_main_thread();
1270    }
1271
1272    pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
1273        self.lock().reconfigure_ports_if_needed()
1274    }
1275
1276    pub fn ui_begin_session(&self) {
1277        self.lock().ui_begin_session();
1278    }
1279
1280    pub fn ui_end_session(&self) {
1281        self.lock().ui_end_session();
1282    }
1283
1284    pub fn ui_should_close(&self) -> bool {
1285        self.lock().ui_should_close()
1286    }
1287
1288    pub fn ui_take_due_timers(&self) -> Vec<u32> {
1289        self.lock().ui_take_due_timers()
1290    }
1291
1292    pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
1293        self.lock().ui_take_param_updates()
1294    }
1295
1296    pub fn ui_take_state_update(&self) -> Option<ClapPluginState> {
1297        self.lock().ui_take_state_update()
1298    }
1299
1300    pub fn gui_info(&self) -> Result<ClapGuiInfo, String> {
1301        self.lock().gui_info()
1302    }
1303
1304    pub fn gui_create(&self, api: &str, is_floating: bool) -> Result<(), String> {
1305        self.lock().gui_create(api, is_floating)
1306    }
1307
1308    pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
1309        self.lock().gui_get_size()
1310    }
1311
1312    pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
1313        self.lock().gui_set_parent_x11(window)
1314    }
1315
1316    pub fn gui_show(&self) -> Result<(), String> {
1317        self.lock().gui_show()
1318    }
1319
1320    pub fn gui_hide(&self) {
1321        self.lock().gui_hide();
1322    }
1323
1324    pub fn gui_destroy(&self) {
1325        self.lock().gui_destroy();
1326    }
1327
1328    pub fn gui_on_main_thread(&self) {
1329        self.lock().gui_on_main_thread();
1330    }
1331
1332    pub fn gui_on_timer(&self, timer_id: u32) {
1333        self.lock().gui_on_timer(timer_id);
1334    }
1335
1336    pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
1337        self.lock().note_names()
1338    }
1339}
1340
1341impl ClapInputEvent {
1342    fn header_ptr(&self) -> *const ClapEventHeader {
1343        match self {
1344            Self::Note(e) => &e.header as *const ClapEventHeader,
1345            Self::Midi(e) => &e.header as *const ClapEventHeader,
1346            Self::ParamValue(e) => &e.header as *const ClapEventHeader,
1347            Self::ParamGesture(e) => &e.header as *const ClapEventHeader,
1348            Self::Transport(e) => &e.header as *const ClapEventHeader,
1349        }
1350    }
1351}
1352
1353struct ClapInputEventsCtx {
1354    events: Vec<ClapInputEvent>,
1355}
1356
1357struct ClapOutputEventsCtx {
1358    midi_events: Vec<ClapMidiOutputEvent>,
1359    param_values: Vec<PendingParamValue>,
1360    param_gestures: Vec<PendingParamGesture>,
1361}
1362
1363struct ClapIStreamCtx<'a> {
1364    bytes: &'a [u8],
1365    offset: usize,
1366}
1367
1368#[derive(Default, Clone, Copy)]
1369struct HostCallbackFlags {
1370    restart: bool,
1371    process: bool,
1372    callback: bool,
1373}
1374
1375#[derive(Clone, Copy)]
1376struct HostTimer {
1377    id: u32,
1378    period: Duration,
1379    next_tick: Instant,
1380}
1381
1382struct HostRuntimeState {
1383    callback_flags: UnsafeMutex<HostCallbackFlags>,
1384    timers: UnsafeMutex<Vec<HostTimer>>,
1385    ui_should_close: AtomicU32,
1386    ui_active: AtomicU32,
1387    param_flush_requested: AtomicU32,
1388    state_dirty_requested: AtomicU32,
1389    note_names_dirty: AtomicU32,
1390    audio_ports_rescan_requested: AtomicU32,
1391    note_ports_rescan_requested: AtomicU32,
1392}
1393
1394thread_local! {
1395    static CLAP_HOST_MAIN_THREAD: Cell<bool> = const { Cell::new(true) };
1396    static CLAP_HOST_AUDIO_THREAD: Cell<bool> = const { Cell::new(false) };
1397}
1398
1399struct HostThreadScope {
1400    main: bool,
1401    prev: bool,
1402}
1403
1404impl HostThreadScope {
1405    fn enter_main() -> Self {
1406        let prev = CLAP_HOST_MAIN_THREAD.with(|flag| {
1407            let prev = flag.get();
1408            flag.set(true);
1409            prev
1410        });
1411        Self { main: true, prev }
1412    }
1413
1414    fn enter_audio() -> Self {
1415        let prev = CLAP_HOST_AUDIO_THREAD.with(|flag| {
1416            let prev = flag.get();
1417            flag.set(true);
1418            prev
1419        });
1420        Self { main: false, prev }
1421    }
1422}
1423
1424impl Drop for HostThreadScope {
1425    fn drop(&mut self) {
1426        if self.main {
1427            CLAP_HOST_MAIN_THREAD.with(|flag| flag.set(self.prev));
1428        } else {
1429            CLAP_HOST_AUDIO_THREAD.with(|flag| flag.set(self.prev));
1430        }
1431    }
1432}
1433
1434struct HostRuntime {
1435    state: Box<HostRuntimeState>,
1436    host: ClapHost,
1437}
1438
1439impl HostRuntime {
1440    fn new() -> Result<Self, String> {
1441        let mut state = Box::new(HostRuntimeState {
1442            callback_flags: UnsafeMutex::new(HostCallbackFlags::default()),
1443            timers: UnsafeMutex::new(Vec::new()),
1444            ui_should_close: AtomicU32::new(0),
1445            ui_active: AtomicU32::new(0),
1446            param_flush_requested: AtomicU32::new(0),
1447            state_dirty_requested: AtomicU32::new(0),
1448            note_names_dirty: AtomicU32::new(0),
1449            audio_ports_rescan_requested: AtomicU32::new(0),
1450            note_ports_rescan_requested: AtomicU32::new(0),
1451        });
1452        let host = ClapHost {
1453            clap_version: CLAP_VERSION,
1454            host_data: (&mut *state as *mut HostRuntimeState).cast::<c_void>(),
1455            name: c"Maolan".as_ptr(),
1456            vendor: c"Maolan".as_ptr(),
1457            url: c"https://example.invalid".as_ptr(),
1458            version: c"0.0.1".as_ptr(),
1459            get_extension: Some(host_get_extension),
1460            request_restart: Some(host_request_restart),
1461            request_process: Some(host_request_process),
1462            request_callback: Some(host_request_callback),
1463        };
1464        Ok(Self { state, host })
1465    }
1466
1467    fn take_callback_flags(&self) -> HostCallbackFlags {
1468        let flags = self.state.callback_flags.lock();
1469        let out = *flags;
1470        *flags = HostCallbackFlags::default();
1471        out
1472    }
1473
1474    fn begin_ui_session(&self) {
1475        self.state.ui_should_close.store(0, Ordering::Release);
1476        self.state.ui_active.store(1, Ordering::Release);
1477        self.state.param_flush_requested.store(0, Ordering::Release);
1478        self.state.state_dirty_requested.store(0, Ordering::Release);
1479        self.state.timers.lock().clear();
1480    }
1481
1482    fn end_ui_session(&self) {
1483        self.state.ui_active.store(0, Ordering::Release);
1484        self.state.ui_should_close.store(0, Ordering::Release);
1485        self.state.param_flush_requested.store(0, Ordering::Release);
1486        self.state.state_dirty_requested.store(0, Ordering::Release);
1487        self.state.timers.lock().clear();
1488    }
1489
1490    fn ui_should_close(&self) -> bool {
1491        self.state.ui_should_close.load(Ordering::Acquire) != 0
1492    }
1493
1494    fn ui_take_due_timers(&self) -> Vec<u32> {
1495        let now = Instant::now();
1496        let timers = &mut *self.state.timers.lock();
1497        let mut due = Vec::new();
1498        for timer in timers.iter_mut() {
1499            if now >= timer.next_tick {
1500                due.push(timer.id);
1501                timer.next_tick = now + timer.period;
1502            }
1503        }
1504        due
1505    }
1506
1507    fn ui_take_param_flush_requested(&self) -> bool {
1508        self.state.param_flush_requested.swap(0, Ordering::AcqRel) != 0
1509    }
1510
1511    fn ui_take_state_dirty_requested(&self) -> bool {
1512        self.state.state_dirty_requested.swap(0, Ordering::AcqRel) != 0
1513    }
1514}
1515
1516unsafe impl Send for HostRuntime {}
1517
1518unsafe impl Sync for HostRuntime {}
1519
1520struct PluginHandle {
1521    _library: Library,
1522    entry: *const ClapPluginEntry,
1523    plugin: *const ClapPlugin,
1524    plugin_id: String,
1525    plugin_name: String,
1526}
1527
1528unsafe impl Send for PluginHandle {}
1529
1530unsafe impl Sync for PluginHandle {}
1531
1532impl PluginHandle {
1533    fn load(
1534        plugin_path: &str,
1535        plugin_id: Option<&str>,
1536        host_runtime: Arc<HostRuntime>,
1537        sample_rate: f64,
1538        frames: u32,
1539    ) -> Result<Self, String> {
1540        let factory_id = c"clap.plugin-factory";
1541
1542        let library = unsafe { Library::new(plugin_path) }.map_err(|e| e.to_string())?;
1543
1544        let entry_ptr = unsafe {
1545            let sym = library
1546                .get::<*const ClapPluginEntry>(b"clap_entry\0")
1547                .map_err(|e| e.to_string())?;
1548            *sym
1549        };
1550        if entry_ptr.is_null() {
1551            return Err("CLAP entry symbol is null".to_string());
1552        }
1553
1554        let entry = unsafe { &*entry_ptr };
1555        let init = entry
1556            .init
1557            .ok_or_else(|| "CLAP entry missing init()".to_string())?;
1558        let host_ptr = &host_runtime.host as *const ClapHost;
1559
1560        if unsafe { !init(host_ptr) } {
1561            return Err(format!("CLAP entry init failed for {plugin_path}"));
1562        }
1563        let get_factory = entry
1564            .get_factory
1565            .ok_or_else(|| "CLAP entry missing get_factory()".to_string())?;
1566
1567        let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
1568        if factory.is_null() {
1569            return Err("CLAP plugin factory not found".to_string());
1570        }
1571
1572        let factory_ref = unsafe { &*factory };
1573        let get_count = factory_ref
1574            .get_plugin_count
1575            .ok_or_else(|| "CLAP factory missing get_plugin_count()".to_string())?;
1576        let get_desc = factory_ref
1577            .get_plugin_descriptor
1578            .ok_or_else(|| "CLAP factory missing get_plugin_descriptor()".to_string())?;
1579        let create = factory_ref
1580            .create_plugin
1581            .ok_or_else(|| "CLAP factory missing create_plugin()".to_string())?;
1582
1583        let count = unsafe { get_count(factory) };
1584        if count == 0 {
1585            return Err("CLAP factory returned zero plugins".to_string());
1586        }
1587        let mut selected_id = None::<CString>;
1588        let mut selected_name = None::<String>;
1589        for i in 0..count {
1590            let desc = unsafe { get_desc(factory, i) };
1591            if desc.is_null() {
1592                continue;
1593            }
1594
1595            let desc = unsafe { &*desc };
1596            if desc.id.is_null() {
1597                continue;
1598            }
1599
1600            let id = unsafe { CStr::from_ptr(desc.id) };
1601            let id_str = id.to_string_lossy();
1602            let name_str = if desc.name.is_null() {
1603                String::new()
1604            } else {
1605                unsafe { CStr::from_ptr(desc.name) }
1606                    .to_string_lossy()
1607                    .into_owned()
1608            };
1609            if plugin_id.is_none() || plugin_id == Some(id_str.as_ref()) {
1610                selected_id = Some(
1611                    CString::new(id_str.as_ref()).map_err(|e| format!("Invalid plugin id: {e}"))?,
1612                );
1613                selected_name = Some(name_str);
1614                break;
1615            }
1616        }
1617        let selected_id = selected_id.ok_or_else(|| {
1618            if let Some(id) = plugin_id {
1619                format!("CLAP descriptor id not found in bundle: {id}")
1620            } else {
1621                "CLAP descriptor not found".to_string()
1622            }
1623        })?;
1624        let plugin_name = selected_name.unwrap_or_else(|| {
1625            Path::new(plugin_path)
1626                .file_stem()
1627                .map(|s| s.to_string_lossy().to_string())
1628                .unwrap_or_else(|| plugin_path.to_string())
1629        });
1630
1631        let plugin = unsafe { create(factory, &host_runtime.host, selected_id.as_ptr()) };
1632        if plugin.is_null() {
1633            return Err("CLAP factory create_plugin failed".to_string());
1634        }
1635
1636        let plugin_ref = unsafe { &*plugin };
1637        let plugin_init = plugin_ref
1638            .init
1639            .ok_or_else(|| "CLAP plugin missing init()".to_string())?;
1640
1641        if unsafe { !plugin_init(plugin) } {
1642            return Err("CLAP plugin init() failed".to_string());
1643        }
1644        if let Some(activate) = plugin_ref.activate
1645            && unsafe { !activate(plugin, sample_rate, frames.max(1), frames.max(1)) }
1646        {
1647            return Err("CLAP plugin activate() failed".to_string());
1648        }
1649        if let Some(start_processing) = plugin_ref.start_processing
1650            && unsafe { !start_processing(plugin) }
1651        {
1652            return Err("CLAP plugin start_processing() failed".to_string());
1653        }
1654        let plugin_id_str = selected_id.to_string_lossy().into_owned();
1655        Ok(Self {
1656            _library: library,
1657            entry: entry_ptr,
1658            plugin,
1659            plugin_id: plugin_id_str,
1660            plugin_name,
1661        })
1662    }
1663
1664    fn plugin_id(&self) -> &str {
1665        &self.plugin_id
1666    }
1667
1668    fn plugin_name(&self) -> &str {
1669        &self.plugin_name
1670    }
1671
1672    fn process(&self, process: &mut ClapProcess) -> Result<bool, String> {
1673        let plugin = unsafe { &*self.plugin };
1674        let Some(process_fn) = plugin.process else {
1675            return Ok(false);
1676        };
1677
1678        let _status = unsafe { process_fn(self.plugin, process as *const _) };
1679        Ok(true)
1680    }
1681
1682    fn reset(&self) {
1683        let plugin = unsafe { &*self.plugin };
1684        if let Some(reset) = plugin.reset {
1685            unsafe { reset(self.plugin) };
1686        }
1687    }
1688
1689    fn on_main_thread(&self) {
1690        let plugin = unsafe { &*self.plugin };
1691        if let Some(on_main_thread) = plugin.on_main_thread {
1692            unsafe { on_main_thread(self.plugin) };
1693        }
1694    }
1695
1696    fn params_ext(&self) -> Option<&ClapPluginParams> {
1697        let ext_id = c"clap.params";
1698
1699        let plugin = unsafe { &*self.plugin };
1700        let get_extension = plugin.get_extension?;
1701
1702        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1703        if ext_ptr.is_null() {
1704            return None;
1705        }
1706
1707        Some(unsafe { &*(ext_ptr as *const ClapPluginParams) })
1708    }
1709
1710    fn state_ext(&self) -> Option<&ClapPluginStateExt> {
1711        let ext_id = c"clap.state";
1712
1713        let plugin = unsafe { &*self.plugin };
1714        let get_extension = plugin.get_extension?;
1715
1716        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1717        if ext_ptr.is_null() {
1718            return None;
1719        }
1720
1721        Some(unsafe { &*(ext_ptr as *const ClapPluginStateExt) })
1722    }
1723
1724    fn audio_ports_ext(&self) -> Option<&ClapPluginAudioPorts> {
1725        let ext_id = c"clap.audio-ports";
1726
1727        let plugin = unsafe { &*self.plugin };
1728        let get_extension = plugin.get_extension?;
1729
1730        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1731        if ext_ptr.is_null() {
1732            return None;
1733        }
1734
1735        Some(unsafe { &*(ext_ptr as *const ClapPluginAudioPorts) })
1736    }
1737
1738    fn note_ports_ext(&self) -> Option<&ClapPluginNotePorts> {
1739        let ext_id = c"clap.note-ports";
1740
1741        let plugin = unsafe { &*self.plugin };
1742        let get_extension = plugin.get_extension?;
1743
1744        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1745        if ext_ptr.is_null() {
1746            return None;
1747        }
1748
1749        Some(unsafe { &*(ext_ptr as *const ClapPluginNotePorts) })
1750    }
1751
1752    fn note_name_ext(&self) -> Option<&ClapPluginNoteName> {
1753        let ext_id = c"clap.note-name";
1754        let plugin = unsafe { &*self.plugin };
1755        let get_extension = plugin.get_extension?;
1756        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1757        if ext_ptr.is_null() {
1758            return None;
1759        }
1760        Some(unsafe { &*(ext_ptr as *const ClapPluginNoteName) })
1761    }
1762
1763    fn get_note_names(&self) -> std::collections::HashMap<u8, String> {
1764        let mut result = std::collections::HashMap::new();
1765        let Some(ext) = self.note_name_ext() else {
1766            return result;
1767        };
1768        let Some(count_fn) = ext.count else {
1769            return result;
1770        };
1771        let Some(get_fn) = ext.get else {
1772            return result;
1773        };
1774        let count = unsafe { count_fn(self.plugin) };
1775        for i in 0..count {
1776            let mut nn = ClapNoteName {
1777                name: [0; 256],
1778                port: -1,
1779                key: -1,
1780                channel: -1,
1781            };
1782            if unsafe { get_fn(self.plugin, i, &mut nn) } {
1783                let name = unsafe {
1784                    std::ffi::CStr::from_ptr(nn.name.as_ptr())
1785                        .to_string_lossy()
1786                        .into_owned()
1787                };
1788                if nn.key >= 0 && nn.key <= 127 && !name.is_empty() {
1789                    result.insert(nn.key as u8, name);
1790                }
1791            }
1792        }
1793        result
1794    }
1795
1796    fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
1797        let Some(params) = self.params_ext() else {
1798            return Vec::new();
1799        };
1800        let Some(count_fn) = params.count else {
1801            return Vec::new();
1802        };
1803        let Some(get_info_fn) = params.get_info else {
1804            return Vec::new();
1805        };
1806
1807        let count = unsafe { count_fn(self.plugin) };
1808        let mut out = Vec::with_capacity(count as usize);
1809        for idx in 0..count {
1810            let mut info = ClapParamInfoRaw {
1811                id: 0,
1812                flags: 0,
1813                cookie: std::ptr::null_mut(),
1814                name: [0; 256],
1815                module: [0; 1024],
1816                min_value: 0.0,
1817                max_value: 1.0,
1818                default_value: 0.0,
1819            };
1820
1821            if unsafe { !get_info_fn(self.plugin, idx, &mut info as *mut _) } {
1822                continue;
1823            }
1824            out.push(ClapParameterInfo {
1825                id: info.id,
1826                name: c_char_buf_to_string(&info.name),
1827                module: c_char_buf_to_string(&info.module),
1828                min_value: info.min_value,
1829                max_value: info.max_value,
1830                default_value: info.default_value,
1831            });
1832        }
1833        out
1834    }
1835
1836    fn parameter_values(&self, infos: &[ClapParameterInfo]) -> HashMap<u32, f64> {
1837        let mut out = HashMap::new();
1838        let Some(params) = self.params_ext() else {
1839            for info in infos {
1840                out.insert(info.id, info.default_value);
1841            }
1842            return out;
1843        };
1844        let Some(get_value_fn) = params.get_value else {
1845            for info in infos {
1846                out.insert(info.id, info.default_value);
1847            }
1848            return out;
1849        };
1850        for info in infos {
1851            let mut value = info.default_value;
1852
1853            if unsafe { !get_value_fn(self.plugin, info.id, &mut value as *mut _) } {
1854                value = info.default_value;
1855            }
1856            out.insert(info.id, value);
1857        }
1858        out
1859    }
1860
1861    fn flush_params(
1862        &self,
1863        param_events: &[PendingParamEvent],
1864    ) -> (Vec<PendingParamValue>, Vec<PendingParamGesture>) {
1865        let Some(params) = self.params_ext() else {
1866            return (Vec::new(), Vec::new());
1867        };
1868        let Some(flush_fn) = params.flush else {
1869            return (Vec::new(), Vec::new());
1870        };
1871        let (in_events, _in_ctx) = param_input_events_from(param_events);
1872        let out_cap = param_events.len().max(32);
1873        let (out_events, mut out_ctx) = output_events_ctx(out_cap);
1874
1875        unsafe {
1876            flush_fn(self.plugin, &in_events, &out_events);
1877        }
1878        (
1879            std::mem::take(&mut out_ctx.param_values),
1880            std::mem::take(&mut out_ctx.param_gestures),
1881        )
1882    }
1883
1884    fn snapshot_state(&self) -> Result<ClapPluginState, String> {
1885        let Some(state_ext) = self.state_ext() else {
1886            return Ok(ClapPluginState { bytes: Vec::new() });
1887        };
1888        let Some(save_fn) = state_ext.save else {
1889            return Ok(ClapPluginState { bytes: Vec::new() });
1890        };
1891        let mut bytes = Vec::<u8>::new();
1892        let mut stream = ClapOStream {
1893            ctx: (&mut bytes as *mut Vec<u8>).cast::<c_void>(),
1894            write: Some(clap_ostream_write),
1895        };
1896
1897        if unsafe {
1898            !save_fn(
1899                self.plugin,
1900                &mut stream as *mut ClapOStream as *const ClapOStream,
1901            )
1902        } {
1903            return Err("CLAP state save failed".to_string());
1904        }
1905        Ok(ClapPluginState { bytes })
1906    }
1907
1908    fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
1909        let Some(state_ext) = self.state_ext() else {
1910            return Ok(());
1911        };
1912        let Some(load_fn) = state_ext.load else {
1913            return Ok(());
1914        };
1915        let mut ctx = ClapIStreamCtx {
1916            bytes: &state.bytes,
1917            offset: 0,
1918        };
1919        let mut stream = ClapIStream {
1920            ctx: (&mut ctx as *mut ClapIStreamCtx).cast::<c_void>(),
1921            read: Some(clap_istream_read),
1922        };
1923
1924        if unsafe {
1925            !load_fn(
1926                self.plugin,
1927                &mut stream as *mut ClapIStream as *const ClapIStream,
1928            )
1929        } {
1930            return Err("CLAP state load failed".to_string());
1931        }
1932        Ok(())
1933    }
1934
1935    const CLAP_AUDIO_PORT_IS_MAIN: u32 = 1;
1936
1937    fn audio_port_channels(&self) -> (Option<AudioPortLayout>, Option<AudioPortLayout>) {
1938        let Some(ext) = self.audio_ports_ext() else {
1939            return (None, None);
1940        };
1941        let Some(count_fn) = ext.count else {
1942            return (None, None);
1943        };
1944        let Some(get_fn) = ext.get else {
1945            return (None, None);
1946        };
1947
1948        let read_ports = |is_input: bool| -> AudioPortLayout {
1949            let mut channels = Vec::new();
1950            let mut main_count = 0;
1951            let count = unsafe { count_fn(self.plugin, is_input) } as usize;
1952            channels.reserve(count);
1953            for idx in 0..count {
1954                let mut info = ClapAudioPortInfoRaw {
1955                    id: 0,
1956                    name: [0; 256],
1957                    flags: 0,
1958                    channel_count: 1,
1959                    port_type: std::ptr::null(),
1960                    in_place_pair: u32::MAX,
1961                };
1962                if unsafe { get_fn(self.plugin, idx as u32, is_input, &mut info as *mut _) } {
1963                    channels.push((info.channel_count as usize).max(1));
1964                    if info.flags & Self::CLAP_AUDIO_PORT_IS_MAIN != 0 {
1965                        main_count += 1;
1966                    }
1967                }
1968            }
1969            (channels, main_count)
1970        };
1971        (Some(read_ports(true)), Some(read_ports(false)))
1972    }
1973
1974    fn note_port_layout(&self) -> (Option<usize>, Option<usize>) {
1975        let Some(ext) = self.note_ports_ext() else {
1976            return (None, None);
1977        };
1978        let Some(count_fn) = ext.count else {
1979            return (None, None);
1980        };
1981
1982        let in_count = unsafe { count_fn(self.plugin, true) } as usize;
1983
1984        let out_count = unsafe { count_fn(self.plugin, false) } as usize;
1985        (Some(in_count), Some(out_count))
1986    }
1987
1988    fn gui_ext(&self) -> Option<&ClapPluginGui> {
1989        let ext_id = c"clap.gui";
1990        let plugin = unsafe { &*self.plugin };
1991        let get_extension = plugin.get_extension?;
1992        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1993        if ext_ptr.is_null() {
1994            return None;
1995        }
1996        Some(unsafe { &*(ext_ptr as *const ClapPluginGui) })
1997    }
1998
1999    fn gui_timer_support_ext(&self) -> Option<&ClapPluginTimerSupport> {
2000        let ext_id = c"clap.timer-support";
2001        let plugin = unsafe { &*self.plugin };
2002        let get_extension = plugin.get_extension?;
2003        let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
2004        if ext_ptr.is_null() {
2005            return None;
2006        }
2007        Some(unsafe { &*(ext_ptr as *const ClapPluginTimerSupport) })
2008    }
2009
2010    fn gui_info(&self) -> Result<ClapGuiInfo, String> {
2011        let gui = self
2012            .gui_ext()
2013            .ok_or_else(|| "CLAP plugin does not expose clap.gui".to_string())?;
2014        let is_api_supported = gui
2015            .is_api_supported
2016            .ok_or_else(|| "CLAP gui.is_api_supported is unavailable".to_string())?;
2017        for (api, supports_embedded) in [
2018            ("x11", true),
2019            ("cocoa", true),
2020            ("x11", false),
2021            ("cocoa", false),
2022        ] {
2023            let api_c = CString::new(api).map_err(|e| e.to_string())?;
2024            if unsafe { is_api_supported(self.plugin, api_c.as_ptr(), !supports_embedded) } {
2025                return Ok(ClapGuiInfo {
2026                    api: api.to_string(),
2027                    supports_embedded,
2028                });
2029            }
2030        }
2031        Err("No supported CLAP GUI API found".to_string())
2032    }
2033
2034    fn gui_create(&self, api: &str, is_floating: bool) -> Result<(), String> {
2035        let gui = self
2036            .gui_ext()
2037            .ok_or_else(|| "CLAP plugin does not expose clap.gui".to_string())?;
2038        let create = gui
2039            .create
2040            .ok_or_else(|| "CLAP gui.create is unavailable".to_string())?;
2041        let api_c = CString::new(api).map_err(|e| e.to_string())?;
2042        if unsafe { !create(self.plugin, api_c.as_ptr(), is_floating) } {
2043            return Err("CLAP gui.create failed".to_string());
2044        }
2045        Ok(())
2046    }
2047
2048    fn gui_get_size(&self) -> Result<(u32, u32), String> {
2049        let gui = self
2050            .gui_ext()
2051            .ok_or_else(|| "CLAP plugin does not expose clap.gui".to_string())?;
2052        let get_size = gui
2053            .get_size
2054            .ok_or_else(|| "CLAP gui.get_size is unavailable".to_string())?;
2055        let mut width = 0;
2056        let mut height = 0;
2057        if unsafe { !get_size(self.plugin, &mut width, &mut height) } {
2058            return Err("CLAP gui.get_size failed".to_string());
2059        }
2060        Ok((width, height))
2061    }
2062
2063    fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
2064        let gui = self
2065            .gui_ext()
2066            .ok_or_else(|| "CLAP plugin does not expose clap.gui".to_string())?;
2067        let set_parent = gui
2068            .set_parent
2069            .ok_or_else(|| "CLAP gui.set_parent is unavailable".to_string())?;
2070        let clap_window = ClapWindow {
2071            api: c"x11".as_ptr(),
2072            handle: ClapWindowHandle { x11: window },
2073        };
2074        if unsafe { !set_parent(self.plugin, &clap_window) } {
2075            return Err("CLAP gui.set_parent failed".to_string());
2076        }
2077        Ok(())
2078    }
2079
2080    fn gui_show(&self) -> Result<(), String> {
2081        let gui = self
2082            .gui_ext()
2083            .ok_or_else(|| "CLAP plugin does not expose clap.gui".to_string())?;
2084        let show = gui
2085            .show
2086            .ok_or_else(|| "CLAP gui.show is unavailable".to_string())?;
2087        if unsafe { !show(self.plugin) } {
2088            return Err("CLAP gui.show failed".to_string());
2089        }
2090        Ok(())
2091    }
2092
2093    fn gui_hide(&self) {
2094        if let Some(gui) = self.gui_ext()
2095            && let Some(hide) = gui.hide
2096        {
2097            unsafe { hide(self.plugin) };
2098        }
2099    }
2100
2101    fn gui_destroy(&self) {
2102        if let Some(gui) = self.gui_ext()
2103            && let Some(destroy) = gui.destroy
2104        {
2105            unsafe { destroy(self.plugin) };
2106        }
2107    }
2108
2109    fn gui_on_timer(&self, timer_id: u32) {
2110        if let Some(timer_ext) = self.gui_timer_support_ext()
2111            && let Some(on_timer) = timer_ext.on_timer
2112        {
2113            unsafe { on_timer(self.plugin, timer_id) };
2114        }
2115    }
2116
2117    fn stop_processing(&self) {
2118        unsafe {
2119            if !self.plugin.is_null() {
2120                let plugin = &*self.plugin;
2121                if let Some(stop) = plugin.stop_processing {
2122                    stop(self.plugin);
2123                }
2124            }
2125        }
2126    }
2127
2128    fn deactivate(&self) {
2129        unsafe {
2130            if !self.plugin.is_null() {
2131                let plugin = &*self.plugin;
2132                if let Some(deactivate) = plugin.deactivate {
2133                    deactivate(self.plugin);
2134                }
2135            }
2136        }
2137    }
2138
2139    fn activate(&self, sample_rate: f64, min_frames: u32, max_frames: u32) -> Result<(), String> {
2140        unsafe {
2141            if !self.plugin.is_null() {
2142                let plugin = &*self.plugin;
2143                if let Some(activate) = plugin.activate
2144                    && !activate(self.plugin, sample_rate, min_frames, max_frames)
2145                {
2146                    return Err("CLAP plugin activate() failed".to_string());
2147                }
2148            }
2149            Ok(())
2150        }
2151    }
2152
2153    fn start_processing(&self) -> Result<(), String> {
2154        unsafe {
2155            if !self.plugin.is_null() {
2156                let plugin = &*self.plugin;
2157                if let Some(start) = plugin.start_processing
2158                    && !start(self.plugin)
2159                {
2160                    return Err("CLAP plugin start_processing() failed".to_string());
2161                }
2162            }
2163            Ok(())
2164        }
2165    }
2166}
2167
2168impl Drop for PluginHandle {
2169    fn drop(&mut self) {
2170        unsafe {
2171            if !self.plugin.is_null() {
2172                let plugin = &*self.plugin;
2173                if let Some(stop_processing) = plugin.stop_processing {
2174                    stop_processing(self.plugin);
2175                }
2176                if let Some(deactivate) = plugin.deactivate {
2177                    deactivate(self.plugin);
2178                }
2179                if let Some(destroy) = plugin.destroy {
2180                    destroy(self.plugin);
2181                }
2182            }
2183            if !self.entry.is_null() {
2184                let entry = &*self.entry;
2185                if let Some(deinit) = entry.deinit {
2186                    deinit();
2187                }
2188            }
2189        }
2190    }
2191}
2192
2193static HOST_THREAD_CHECK_EXT: ClapHostThreadCheck = ClapHostThreadCheck {
2194    is_main_thread: Some(host_is_main_thread),
2195    is_audio_thread: Some(host_is_audio_thread),
2196};
2197static HOST_LATENCY_EXT: ClapHostLatency = ClapHostLatency {
2198    changed: Some(host_latency_changed),
2199};
2200static HOST_TAIL_EXT: ClapHostTail = ClapHostTail {
2201    changed: Some(host_tail_changed),
2202};
2203static HOST_TIMER_EXT: ClapHostTimerSupport = ClapHostTimerSupport {
2204    register_timer: Some(host_timer_register),
2205    unregister_timer: Some(host_timer_unregister),
2206};
2207static HOST_GUI_EXT: ClapHostGui = ClapHostGui {
2208    resize_hints_changed: Some(host_gui_resize_hints_changed),
2209    request_resize: Some(host_gui_request_resize),
2210    request_show: Some(host_gui_request_show),
2211    request_hide: Some(host_gui_request_hide),
2212    closed: Some(host_gui_closed),
2213};
2214static HOST_PARAMS_EXT: ClapHostParams = ClapHostParams {
2215    rescan: Some(host_params_rescan),
2216    clear: Some(host_params_clear),
2217    request_flush: Some(host_params_request_flush),
2218};
2219static HOST_STATE_EXT: ClapHostState = ClapHostState {
2220    mark_dirty: Some(host_state_mark_dirty),
2221};
2222static HOST_NOTE_NAME_EXT: ClapHostNoteName = ClapHostNoteName {
2223    changed: Some(host_note_name_changed),
2224};
2225static HOST_AUDIO_PORTS_EXT: ClapHostAudioPorts = ClapHostAudioPorts {
2226    is_rescan_flag_supported: Some(host_audio_ports_is_rescan_flag_supported),
2227    rescan: Some(host_audio_ports_rescan),
2228};
2229static HOST_NOTE_PORTS_EXT: ClapHostNotePorts = ClapHostNotePorts {
2230    supported_dialects: Some(host_note_ports_supported_dialects),
2231    rescan: Some(host_note_ports_rescan),
2232};
2233static NEXT_TIMER_ID: AtomicU32 = AtomicU32::new(1);
2234
2235fn host_runtime_state(host: *const ClapHost) -> Option<&'static HostRuntimeState> {
2236    if host.is_null() {
2237        return None;
2238    }
2239    let state_ptr = unsafe { (*host).host_data as *const HostRuntimeState };
2240    if state_ptr.is_null() {
2241        return None;
2242    }
2243    Some(unsafe { &*state_ptr })
2244}
2245
2246unsafe extern "C" fn host_get_extension(
2247    _host: *const ClapHost,
2248    _extension_id: *const c_char,
2249) -> *const c_void {
2250    if _extension_id.is_null() {
2251        return std::ptr::null();
2252    }
2253
2254    let id = unsafe { CStr::from_ptr(_extension_id) }.to_string_lossy();
2255    match id.as_ref() {
2256        "clap.host.thread-check" => {
2257            (&HOST_THREAD_CHECK_EXT as *const ClapHostThreadCheck).cast::<c_void>()
2258        }
2259        "clap.host.latency" => (&HOST_LATENCY_EXT as *const ClapHostLatency).cast::<c_void>(),
2260        "clap.host.tail" => (&HOST_TAIL_EXT as *const ClapHostTail).cast::<c_void>(),
2261        "clap.host.timer-support" => {
2262            (&HOST_TIMER_EXT as *const ClapHostTimerSupport).cast::<c_void>()
2263        }
2264        "clap.host.gui" => host_runtime_state(_host)
2265            .filter(|state| state.ui_active.load(Ordering::Acquire) != 0)
2266            .map(|_| (&HOST_GUI_EXT as *const ClapHostGui).cast::<c_void>())
2267            .unwrap_or(std::ptr::null()),
2268        "clap.host.params" => host_runtime_state(_host)
2269            .filter(|state| state.ui_active.load(Ordering::Acquire) != 0)
2270            .map(|_| (&HOST_PARAMS_EXT as *const ClapHostParams).cast::<c_void>())
2271            .unwrap_or(std::ptr::null()),
2272        "clap.host.state" => host_runtime_state(_host)
2273            .filter(|state| state.ui_active.load(Ordering::Acquire) != 0)
2274            .map(|_| (&HOST_STATE_EXT as *const ClapHostState).cast::<c_void>())
2275            .unwrap_or(std::ptr::null()),
2276        "clap.host.note-name" => (&HOST_NOTE_NAME_EXT as *const ClapHostNoteName).cast::<c_void>(),
2277        "clap.host.audio-ports" => {
2278            (&HOST_AUDIO_PORTS_EXT as *const ClapHostAudioPorts).cast::<c_void>()
2279        }
2280        "clap.host.note-ports" => {
2281            (&HOST_NOTE_PORTS_EXT as *const ClapHostNotePorts).cast::<c_void>()
2282        }
2283        _ => std::ptr::null(),
2284    }
2285}
2286
2287unsafe extern "C" fn host_request_process(_host: *const ClapHost) {
2288    if let Some(state) = host_runtime_state(_host) {
2289        state.callback_flags.lock().process = true;
2290    }
2291}
2292
2293unsafe extern "C" fn host_request_callback(_host: *const ClapHost) {
2294    if let Some(state) = host_runtime_state(_host) {
2295        state.callback_flags.lock().callback = true;
2296    }
2297}
2298
2299unsafe extern "C" fn host_request_restart(_host: *const ClapHost) {
2300    if let Some(state) = host_runtime_state(_host) {
2301        state.callback_flags.lock().restart = true;
2302    }
2303}
2304
2305unsafe extern "C" fn host_audio_ports_is_rescan_flag_supported(
2306    _host: *const ClapHost,
2307    _flag: u32,
2308) -> bool {
2309    true
2310}
2311
2312unsafe extern "C" fn host_audio_ports_rescan(_host: *const ClapHost, _flags: u32) {
2313    if let Some(state) = host_runtime_state(_host) {
2314        state
2315            .audio_ports_rescan_requested
2316            .store(1, Ordering::Release);
2317    }
2318}
2319
2320unsafe extern "C" fn host_note_ports_rescan(_host: *const ClapHost, _flags: u32) {
2321    if let Some(state) = host_runtime_state(_host) {
2322        state
2323            .note_ports_rescan_requested
2324            .store(1, Ordering::Release);
2325    }
2326}
2327
2328unsafe extern "C" fn host_note_ports_supported_dialects(_host: *const ClapHost) -> u32 {
2329    let _ = _host;
2330    0x1F
2331}
2332
2333unsafe extern "C" fn host_note_name_changed(_host: *const ClapHost) {
2334    if let Some(state) = host_runtime_state(_host) {
2335        state.note_names_dirty.store(1, Ordering::Release);
2336    }
2337}
2338
2339unsafe extern "C" fn host_is_main_thread(_host: *const ClapHost) -> bool {
2340    CLAP_HOST_MAIN_THREAD.with(Cell::get)
2341}
2342
2343unsafe extern "C" fn host_is_audio_thread(_host: *const ClapHost) -> bool {
2344    CLAP_HOST_AUDIO_THREAD.with(Cell::get)
2345}
2346
2347unsafe extern "C" fn host_latency_changed(_host: *const ClapHost) {}
2348
2349unsafe extern "C" fn host_tail_changed(_host: *const ClapHost) {}
2350
2351unsafe extern "C" fn host_timer_register(
2352    _host: *const ClapHost,
2353    _period_ms: u32,
2354    timer_id: *mut u32,
2355) -> bool {
2356    if timer_id.is_null() {
2357        return false;
2358    }
2359    let id = NEXT_TIMER_ID.fetch_add(1, Ordering::Relaxed);
2360    if let Some(state) = host_runtime_state(_host) {
2361        let period_ms = _period_ms.max(1);
2362        state.timers.lock().push(HostTimer {
2363            id,
2364            period: Duration::from_millis(period_ms as u64),
2365            next_tick: Instant::now() + Duration::from_millis(period_ms as u64),
2366        });
2367    }
2368
2369    unsafe {
2370        *timer_id = id;
2371    }
2372    true
2373}
2374
2375unsafe extern "C" fn host_timer_unregister(_host: *const ClapHost, _timer_id: u32) -> bool {
2376    if let Some(state) = host_runtime_state(_host) {
2377        state.timers.lock().retain(|timer| timer.id != _timer_id);
2378    }
2379    true
2380}
2381
2382unsafe extern "C" fn host_gui_resize_hints_changed(_host: *const ClapHost) {}
2383
2384unsafe extern "C" fn host_gui_request_resize(
2385    _host: *const ClapHost,
2386    _width: u32,
2387    _height: u32,
2388) -> bool {
2389    true
2390}
2391
2392unsafe extern "C" fn host_gui_request_show(_host: *const ClapHost) -> bool {
2393    true
2394}
2395
2396unsafe extern "C" fn host_gui_request_hide(_host: *const ClapHost) -> bool {
2397    if let Some(state) = host_runtime_state(_host) {
2398        if state.ui_active.load(Ordering::Acquire) != 0 {
2399            state.ui_should_close.store(1, Ordering::Release);
2400        }
2401        true
2402    } else {
2403        false
2404    }
2405}
2406
2407unsafe extern "C" fn host_gui_closed(_host: *const ClapHost, _was_destroyed: bool) {
2408    if let Some(state) = host_runtime_state(_host)
2409        && state.ui_active.load(Ordering::Acquire) != 0
2410    {
2411        state.ui_should_close.store(1, Ordering::Release);
2412    }
2413}
2414
2415unsafe extern "C" fn host_params_rescan(_host: *const ClapHost, _flags: u32) {}
2416
2417unsafe extern "C" fn host_params_clear(_host: *const ClapHost, _param_id: u32, _flags: u32) {}
2418
2419unsafe extern "C" fn host_params_request_flush(_host: *const ClapHost) {
2420    if let Some(state) = host_runtime_state(_host) {
2421        state.param_flush_requested.store(1, Ordering::Release);
2422        state.callback_flags.lock().callback = true;
2423    }
2424}
2425
2426unsafe extern "C" fn host_state_mark_dirty(_host: *const ClapHost) {
2427    if let Some(state) = host_runtime_state(_host) {
2428        state.state_dirty_requested.store(1, Ordering::Release);
2429        state.callback_flags.lock().callback = true;
2430    }
2431}
2432
2433unsafe extern "C" fn input_events_size(_list: *const ClapInputEvents) -> u32 {
2434    if _list.is_null() {
2435        return 0;
2436    }
2437
2438    let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
2439    if ctx.is_null() {
2440        return 0;
2441    }
2442
2443    unsafe { (*ctx).events.len() as u32 }
2444}
2445
2446unsafe extern "C" fn input_events_get(
2447    _list: *const ClapInputEvents,
2448    _index: u32,
2449) -> *const ClapEventHeader {
2450    if _list.is_null() {
2451        return std::ptr::null();
2452    }
2453
2454    let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
2455    if ctx.is_null() {
2456        return std::ptr::null();
2457    }
2458
2459    let events = unsafe { &(*ctx).events };
2460    let Some(event) = events.get(_index as usize) else {
2461        return std::ptr::null();
2462    };
2463    event.header_ptr()
2464}
2465
2466unsafe extern "C" fn output_events_try_push(
2467    _list: *const ClapOutputEvents,
2468    _event: *const ClapEventHeader,
2469) -> bool {
2470    if _list.is_null() || _event.is_null() {
2471        return false;
2472    }
2473
2474    let ctx = unsafe { (*_list).ctx as *mut ClapOutputEventsCtx };
2475    if ctx.is_null() {
2476        return false;
2477    }
2478
2479    let header = unsafe { &*_event };
2480    if header.space_id != CLAP_CORE_EVENT_SPACE_ID {
2481        return false;
2482    }
2483    match header.type_ {
2484        CLAP_EVENT_MIDI => {
2485            if (header.size as usize) < std::mem::size_of::<ClapEventMidi>() {
2486                return false;
2487            }
2488
2489            let midi = unsafe { &*(_event as *const ClapEventMidi) };
2490
2491            unsafe {
2492                (*ctx).midi_events.push(ClapMidiOutputEvent {
2493                    port: midi.port_index as usize,
2494                    event: MidiEvent::new(header.time, midi.data.to_vec()),
2495                });
2496            }
2497            true
2498        }
2499        CLAP_EVENT_PARAM_VALUE => {
2500            if (header.size as usize) < std::mem::size_of::<ClapEventParamValue>() {
2501                return false;
2502            }
2503
2504            let param = unsafe { &*(_event as *const ClapEventParamValue) };
2505
2506            unsafe {
2507                (*ctx).param_values.push(PendingParamValue {
2508                    param_id: param.param_id,
2509                    value: param.value,
2510                });
2511            }
2512            true
2513        }
2514        CLAP_EVENT_PARAM_GESTURE_BEGIN | CLAP_EVENT_PARAM_GESTURE_END => {
2515            if (header.size as usize) < std::mem::size_of::<ClapEventParamGesture>() {
2516                return false;
2517            }
2518            let gesture = unsafe { &*(_event as *const ClapEventParamGesture) };
2519            unsafe {
2520                (*ctx).param_gestures.push(PendingParamGesture {
2521                    param_id: gesture.param_id,
2522                    is_begin: header.type_ == CLAP_EVENT_PARAM_GESTURE_BEGIN,
2523                });
2524            }
2525            true
2526        }
2527        _ => false,
2528    }
2529}
2530
2531fn input_events_from(
2532    midi_events: &[MidiEvent],
2533    param_events: &[PendingParamEvent],
2534    sample_rate: f64,
2535    transport: ClapTransportInfo,
2536    has_note_ports: bool,
2537) -> (ClapInputEvents, Box<ClapInputEventsCtx>) {
2538    let mut events = Vec::with_capacity(midi_events.len() + param_events.len() + 1);
2539    let bpm = transport.bpm.max(1.0);
2540    let sample_rate = sample_rate.max(1.0);
2541    let seconds = transport.transport_sample as f64 / sample_rate;
2542    let song_pos_seconds = (seconds * CLAP_SECTIME_FACTOR as f64) as i64;
2543    let beats = seconds * (bpm / 60.0);
2544    let song_pos_beats = (beats * CLAP_BEATTIME_FACTOR as f64) as i64;
2545    let mut flags = CLAP_TRANSPORT_HAS_TEMPO
2546        | CLAP_TRANSPORT_HAS_BEATS_TIMELINE
2547        | CLAP_TRANSPORT_HAS_SECONDS_TIMELINE
2548        | CLAP_TRANSPORT_HAS_TIME_SIGNATURE;
2549    if transport.playing {
2550        flags |= CLAP_TRANSPORT_IS_PLAYING;
2551    }
2552    let (loop_start_seconds, loop_end_seconds, loop_start_beats, loop_end_beats) =
2553        if transport.loop_enabled {
2554            if let Some((loop_start, loop_end)) = transport.loop_range_samples {
2555                flags |= CLAP_TRANSPORT_IS_LOOP_ACTIVE;
2556                let ls_sec = loop_start as f64 / sample_rate;
2557                let le_sec = loop_end as f64 / sample_rate;
2558                let ls_beats = ls_sec * (bpm / 60.0);
2559                let le_beats = le_sec * (bpm / 60.0);
2560                (
2561                    (ls_sec * CLAP_SECTIME_FACTOR as f64) as i64,
2562                    (le_sec * CLAP_SECTIME_FACTOR as f64) as i64,
2563                    (ls_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
2564                    (le_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
2565                )
2566            } else {
2567                (0, 0, 0, 0)
2568            }
2569        } else {
2570            (0, 0, 0, 0)
2571        };
2572    let ts_num = transport.tsig_num.max(1);
2573    let ts_denom = transport.tsig_denom.max(1);
2574    let beats_per_bar = ts_num as f64 * (4.0 / ts_denom as f64);
2575    let bar_number = if beats_per_bar > 0.0 {
2576        (beats / beats_per_bar).floor().max(0.0) as i32
2577    } else {
2578        0
2579    };
2580    let bar_start_beats = (bar_number as f64 * beats_per_bar * CLAP_BEATTIME_FACTOR as f64) as i64;
2581    events.push(ClapInputEvent::Transport(ClapEventTransport {
2582        header: ClapEventHeader {
2583            size: std::mem::size_of::<ClapEventTransport>() as u32,
2584            time: 0,
2585            space_id: CLAP_CORE_EVENT_SPACE_ID,
2586            type_: CLAP_EVENT_TRANSPORT,
2587            flags: 0,
2588        },
2589        flags,
2590        song_pos_beats,
2591        song_pos_seconds,
2592        tempo: bpm,
2593        tempo_inc: 0.0,
2594        loop_start_beats,
2595        loop_end_beats,
2596        loop_start_seconds,
2597        loop_end_seconds,
2598        bar_start: bar_start_beats,
2599        bar_number,
2600        tsig_num: ts_num,
2601        tsig_denom: ts_denom,
2602    }));
2603    for event in midi_events {
2604        if event.data.is_empty() {
2605            continue;
2606        }
2607        let mut data = [0_u8; 3];
2608        let bytes = event.data.len().min(3);
2609        data[..bytes].copy_from_slice(&event.data[..bytes]);
2610        let status = data[0];
2611        let is_note_on = (0x90..=0x9F).contains(&status);
2612        let is_note_off = (0x80..=0x8F).contains(&status);
2613        if has_note_ports && (is_note_on || is_note_off) {
2614            let channel = (status & 0x0F) as i16;
2615            let key = data.get(1).copied().unwrap_or(0).min(127) as i16;
2616            let velocity_byte = data.get(2).copied().unwrap_or(0);
2617            let velocity = if is_note_on && velocity_byte == 0 {
2618                events.push(ClapInputEvent::Note(ClapEventNote {
2619                    header: ClapEventHeader {
2620                        size: std::mem::size_of::<ClapEventNote>() as u32,
2621                        time: event.frame,
2622                        space_id: CLAP_CORE_EVENT_SPACE_ID,
2623                        type_: CLAP_EVENT_NOTE_OFF,
2624                        flags: 0,
2625                    },
2626                    note_id: -1,
2627                    port_index: 0,
2628                    channel,
2629                    key,
2630                    velocity: 0.0,
2631                }));
2632                continue;
2633            } else {
2634                (velocity_byte as f64 / 127.0).clamp(0.0, 1.0)
2635            };
2636            events.push(ClapInputEvent::Note(ClapEventNote {
2637                header: ClapEventHeader {
2638                    size: std::mem::size_of::<ClapEventNote>() as u32,
2639                    time: event.frame,
2640                    space_id: CLAP_CORE_EVENT_SPACE_ID,
2641                    type_: if is_note_on {
2642                        CLAP_EVENT_NOTE_ON
2643                    } else {
2644                        CLAP_EVENT_NOTE_OFF
2645                    },
2646                    flags: 0,
2647                },
2648                note_id: -1,
2649                port_index: 0,
2650                channel,
2651                key,
2652                velocity,
2653            }));
2654        } else {
2655            events.push(ClapInputEvent::Midi(ClapEventMidi {
2656                header: ClapEventHeader {
2657                    size: std::mem::size_of::<ClapEventMidi>() as u32,
2658                    time: event.frame,
2659                    space_id: CLAP_CORE_EVENT_SPACE_ID,
2660                    type_: CLAP_EVENT_MIDI,
2661                    flags: 0,
2662                },
2663                port_index: 0,
2664                data,
2665            }));
2666        }
2667    }
2668    for param in param_events {
2669        match *param {
2670            PendingParamEvent::Value {
2671                param_id,
2672                value,
2673                frame,
2674            } => events.push(ClapInputEvent::ParamValue(ClapEventParamValue {
2675                header: ClapEventHeader {
2676                    size: std::mem::size_of::<ClapEventParamValue>() as u32,
2677                    time: frame,
2678                    space_id: CLAP_CORE_EVENT_SPACE_ID,
2679                    type_: CLAP_EVENT_PARAM_VALUE,
2680                    flags: CLAP_EVENT_IS_LIVE,
2681                },
2682                param_id,
2683                cookie: std::ptr::null_mut(),
2684                note_id: -1,
2685                port_index: -1,
2686                channel: -1,
2687                key: -1,
2688                value,
2689            })),
2690            PendingParamEvent::GestureBegin { param_id, frame } => {
2691                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
2692                    header: ClapEventHeader {
2693                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
2694                        time: frame,
2695                        space_id: CLAP_CORE_EVENT_SPACE_ID,
2696                        type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
2697                        flags: CLAP_EVENT_IS_LIVE,
2698                    },
2699                    param_id,
2700                }))
2701            }
2702            PendingParamEvent::GestureEnd { param_id, frame } => {
2703                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
2704                    header: ClapEventHeader {
2705                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
2706                        time: frame,
2707                        space_id: CLAP_CORE_EVENT_SPACE_ID,
2708                        type_: CLAP_EVENT_PARAM_GESTURE_END,
2709                        flags: CLAP_EVENT_IS_LIVE,
2710                    },
2711                    param_id,
2712                }))
2713            }
2714        }
2715    }
2716    events.sort_by_key(|event| match event {
2717        ClapInputEvent::Note(e) => e.header.time,
2718        ClapInputEvent::Midi(e) => e.header.time,
2719        ClapInputEvent::ParamValue(e) => e.header.time,
2720        ClapInputEvent::ParamGesture(e) => e.header.time,
2721        ClapInputEvent::Transport(e) => e.header.time,
2722    });
2723    let mut ctx = Box::new(ClapInputEventsCtx { events });
2724    let list = ClapInputEvents {
2725        ctx: (&mut *ctx as *mut ClapInputEventsCtx).cast::<c_void>(),
2726        size: Some(input_events_size),
2727        get: Some(input_events_get),
2728    };
2729    (list, ctx)
2730}
2731
2732fn param_input_events_from(
2733    param_events: &[PendingParamEvent],
2734) -> (ClapInputEvents, Box<ClapInputEventsCtx>) {
2735    let mut events = Vec::with_capacity(param_events.len());
2736    for param in param_events {
2737        match *param {
2738            PendingParamEvent::Value {
2739                param_id,
2740                value,
2741                frame,
2742            } => events.push(ClapInputEvent::ParamValue(ClapEventParamValue {
2743                header: ClapEventHeader {
2744                    size: std::mem::size_of::<ClapEventParamValue>() as u32,
2745                    time: frame,
2746                    space_id: CLAP_CORE_EVENT_SPACE_ID,
2747                    type_: CLAP_EVENT_PARAM_VALUE,
2748                    flags: CLAP_EVENT_IS_LIVE,
2749                },
2750                param_id,
2751                cookie: std::ptr::null_mut(),
2752                note_id: -1,
2753                port_index: -1,
2754                channel: -1,
2755                key: -1,
2756                value,
2757            })),
2758            PendingParamEvent::GestureBegin { param_id, frame } => {
2759                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
2760                    header: ClapEventHeader {
2761                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
2762                        time: frame,
2763                        space_id: CLAP_CORE_EVENT_SPACE_ID,
2764                        type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
2765                        flags: CLAP_EVENT_IS_LIVE,
2766                    },
2767                    param_id,
2768                }))
2769            }
2770            PendingParamEvent::GestureEnd { param_id, frame } => {
2771                events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
2772                    header: ClapEventHeader {
2773                        size: std::mem::size_of::<ClapEventParamGesture>() as u32,
2774                        time: frame,
2775                        space_id: CLAP_CORE_EVENT_SPACE_ID,
2776                        type_: CLAP_EVENT_PARAM_GESTURE_END,
2777                        flags: CLAP_EVENT_IS_LIVE,
2778                    },
2779                    param_id,
2780                }))
2781            }
2782        }
2783    }
2784    events.sort_by_key(|event| match event {
2785        ClapInputEvent::Note(e) => e.header.time,
2786        ClapInputEvent::Midi(e) => e.header.time,
2787        ClapInputEvent::ParamValue(e) => e.header.time,
2788        ClapInputEvent::ParamGesture(e) => e.header.time,
2789        ClapInputEvent::Transport(e) => e.header.time,
2790    });
2791    let mut ctx = Box::new(ClapInputEventsCtx { events });
2792    let list = ClapInputEvents {
2793        ctx: (&mut *ctx as *mut ClapInputEventsCtx).cast::<c_void>(),
2794        size: Some(input_events_size),
2795        get: Some(input_events_get),
2796    };
2797    (list, ctx)
2798}
2799
2800fn output_events_ctx(capacity: usize) -> (ClapOutputEvents, Box<ClapOutputEventsCtx>) {
2801    let mut ctx = Box::new(ClapOutputEventsCtx {
2802        midi_events: Vec::with_capacity(capacity),
2803        param_values: Vec::with_capacity(capacity / 2),
2804        param_gestures: Vec::with_capacity(capacity / 4),
2805    });
2806    let list = ClapOutputEvents {
2807        ctx: (&mut *ctx as *mut ClapOutputEventsCtx).cast::<c_void>(),
2808        try_push: Some(output_events_try_push),
2809    };
2810    (list, ctx)
2811}
2812
2813fn c_char_buf_to_string<const N: usize>(buf: &[c_char; N]) -> String {
2814    let bytes = buf
2815        .iter()
2816        .take_while(|&&b| b != 0)
2817        .map(|&b| b as u8)
2818        .collect::<Vec<u8>>();
2819    String::from_utf8_lossy(&bytes).to_string()
2820}
2821
2822fn split_plugin_spec(spec: &str) -> (&str, Option<&str>) {
2823    if let Some((path, id)) = spec.split_once("::")
2824        && !id.trim().is_empty()
2825    {
2826        return (path, Some(id.trim()));
2827    }
2828    (spec, None)
2829}
2830
2831unsafe extern "C" fn clap_ostream_write(
2832    stream: *const ClapOStream,
2833    buffer: *const c_void,
2834    size: u64,
2835) -> i64 {
2836    if stream.is_null() || buffer.is_null() {
2837        return -1;
2838    }
2839
2840    let ctx = unsafe { (*stream).ctx as *mut Vec<u8> };
2841    if ctx.is_null() {
2842        return -1;
2843    }
2844    let n = (size as usize).min(isize::MAX as usize);
2845
2846    let src = unsafe { std::slice::from_raw_parts(buffer.cast::<u8>(), n) };
2847
2848    unsafe {
2849        (*ctx).extend_from_slice(src);
2850    }
2851    n as i64
2852}
2853
2854unsafe extern "C" fn clap_istream_read(
2855    stream: *const ClapIStream,
2856    buffer: *mut c_void,
2857    size: u64,
2858) -> i64 {
2859    if stream.is_null() || buffer.is_null() {
2860        return -1;
2861    }
2862
2863    let ctx = unsafe { (*stream).ctx as *mut ClapIStreamCtx<'_> };
2864    if ctx.is_null() {
2865        return -1;
2866    }
2867
2868    let ctx = unsafe { &mut *ctx };
2869    let remaining = ctx.bytes.len().saturating_sub(ctx.offset);
2870    if remaining == 0 {
2871        return 0;
2872    }
2873    let n = remaining.min(size as usize);
2874
2875    let dst = unsafe { std::slice::from_raw_parts_mut(buffer.cast::<u8>(), n) };
2876    dst.copy_from_slice(&ctx.bytes[ctx.offset..ctx.offset + n]);
2877    ctx.offset += n;
2878    n as i64
2879}
2880
2881pub fn list_plugins() -> Vec<ClapPluginInfo> {
2882    list_plugins_with_capabilities(false)
2883}
2884
2885pub fn is_supported_clap_binary(path: &Path) -> bool {
2886    path.extension()
2887        .is_some_and(|ext| ext.eq_ignore_ascii_case("clap"))
2888}
2889
2890pub fn list_plugins_with_capabilities(scan_capabilities: bool) -> Vec<ClapPluginInfo> {
2891    let mut roots = default_clap_search_roots();
2892
2893    if let Ok(extra) = std::env::var("CLAP_PATH") {
2894        for p in std::env::split_paths(&extra) {
2895            if !p.as_os_str().is_empty() {
2896                roots.push(p);
2897            }
2898        }
2899    }
2900
2901    let mut out = Vec::new();
2902    for root in roots {
2903        collect_clap_plugins(&root, &mut out, scan_capabilities);
2904    }
2905
2906    out.sort_by_key(|a| a.name.to_lowercase());
2907    out.dedup_by(|a, b| a.path.eq_ignore_ascii_case(&b.path));
2908    out
2909}
2910
2911fn collect_clap_plugins(root: &Path, out: &mut Vec<ClapPluginInfo>, scan_capabilities: bool) {
2912    let Ok(entries) = std::fs::read_dir(root) else {
2913        return;
2914    };
2915    for entry in entries.flatten() {
2916        let path = entry.path();
2917        let Ok(ft) = entry.file_type() else {
2918            continue;
2919        };
2920        if ft.is_dir() {
2921            if path
2922                .file_name()
2923                .and_then(|name| name.to_str())
2924                .is_some_and(|name| {
2925                    matches!(
2926                        name,
2927                        "deps" | "build" | "incremental" | ".fingerprint" | "examples"
2928                    )
2929                })
2930            {
2931                continue;
2932            }
2933            collect_clap_plugins(&path, out, scan_capabilities);
2934            continue;
2935        }
2936
2937        if is_supported_clap_binary(&path) {
2938            let infos = scan_bundle_descriptors(&path, scan_capabilities);
2939            if infos.is_empty() {
2940                let name = path
2941                    .file_stem()
2942                    .map(|s| s.to_string_lossy().to_string())
2943                    .unwrap_or_else(|| path.to_string_lossy().to_string());
2944                out.push(ClapPluginInfo {
2945                    name,
2946                    path: path.to_string_lossy().to_string(),
2947                    capabilities: None,
2948                });
2949            } else {
2950                out.extend(infos);
2951            }
2952        }
2953    }
2954}
2955
2956fn scan_bundle_descriptors(path: &Path, scan_capabilities: bool) -> Vec<ClapPluginInfo> {
2957    let path_str = path.to_string_lossy().to_string();
2958    let factory_id = c"clap.plugin-factory";
2959    let host_runtime = match HostRuntime::new() {
2960        Ok(runtime) => runtime,
2961        Err(_) => return Vec::new(),
2962    };
2963
2964    let library = match unsafe { Library::new(path) } {
2965        Ok(lib) => lib,
2966        Err(_) => return Vec::new(),
2967    };
2968
2969    let entry_ptr = unsafe {
2970        match library.get::<*const ClapPluginEntry>(b"clap_entry\0") {
2971            Ok(sym) => *sym,
2972            Err(_) => return Vec::new(),
2973        }
2974    };
2975    if entry_ptr.is_null() {
2976        return Vec::new();
2977    }
2978
2979    let entry = unsafe { &*entry_ptr };
2980    let Some(init) = entry.init else {
2981        return Vec::new();
2982    };
2983    let host_ptr = &host_runtime.host;
2984
2985    if unsafe { !init(host_ptr) } {
2986        return Vec::new();
2987    }
2988    let mut out = Vec::new();
2989    if let Some(get_factory) = entry.get_factory {
2990        let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
2991        if !factory.is_null() {
2992            let factory_ref = unsafe { &*factory };
2993            if let (Some(get_count), Some(get_desc)) = (
2994                factory_ref.get_plugin_count,
2995                factory_ref.get_plugin_descriptor,
2996            ) {
2997                let count = unsafe { get_count(factory) };
2998                for i in 0..count {
2999                    let desc = unsafe { get_desc(factory, i) };
3000                    if desc.is_null() {
3001                        continue;
3002                    }
3003
3004                    let desc = unsafe { &*desc };
3005                    if desc.id.is_null() || desc.name.is_null() {
3006                        continue;
3007                    }
3008
3009                    let id = unsafe { CStr::from_ptr(desc.id) }
3010                        .to_string_lossy()
3011                        .to_string();
3012
3013                    let name = unsafe { CStr::from_ptr(desc.name) }
3014                        .to_string_lossy()
3015                        .to_string();
3016
3017                    let capabilities = if scan_capabilities {
3018                        scan_plugin_capabilities(factory_ref, factory, &host_runtime.host, &id)
3019                    } else {
3020                        None
3021                    };
3022
3023                    out.push(ClapPluginInfo {
3024                        name,
3025                        path: format!("{path_str}::{id}"),
3026                        capabilities,
3027                    });
3028                }
3029            }
3030        }
3031    }
3032
3033    if let Some(deinit) = entry.deinit {
3034        unsafe { deinit() };
3035    }
3036    out
3037}
3038
3039fn scan_plugin_capabilities(
3040    factory: &ClapPluginFactory,
3041    factory_ptr: *const ClapPluginFactory,
3042    host: &ClapHost,
3043    plugin_id: &str,
3044) -> Option<ClapPluginCapabilities> {
3045    let create = factory.create_plugin?;
3046
3047    let id_cstring = CString::new(plugin_id).ok()?;
3048
3049    let plugin = unsafe { create(factory_ptr, host, id_cstring.as_ptr()) };
3050    if plugin.is_null() {
3051        return None;
3052    }
3053
3054    let plugin_ref = unsafe { &*plugin };
3055    let plugin_init = plugin_ref.init?;
3056
3057    if unsafe { !plugin_init(plugin) } {
3058        return None;
3059    }
3060
3061    let mut capabilities = ClapPluginCapabilities {
3062        has_gui: false,
3063        gui_apis: Vec::new(),
3064        supports_embedded: false,
3065        supports_floating: false,
3066        has_params: false,
3067        has_state: false,
3068        audio_inputs: 0,
3069        audio_outputs: 0,
3070        midi_inputs: 0,
3071        midi_outputs: 0,
3072    };
3073
3074    if let Some(get_extension) = plugin_ref.get_extension {
3075        let gui_ext_id = c"clap.gui";
3076
3077        let gui_ptr = unsafe { get_extension(plugin, gui_ext_id.as_ptr()) };
3078        if !gui_ptr.is_null() {
3079            capabilities.has_gui = true;
3080
3081            let gui = unsafe { &*(gui_ptr as *const ClapPluginGui) };
3082
3083            if let Some(is_api_supported) = gui.is_api_supported {
3084                for api in ["x11", "cocoa"] {
3085                    if let Ok(api_cstr) = CString::new(api) {
3086                        if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), false) } {
3087                            capabilities.gui_apis.push(format!("{} (embedded)", api));
3088                            capabilities.supports_embedded = true;
3089                        }
3090
3091                        if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), true) } {
3092                            if !capabilities.supports_embedded {
3093                                capabilities.gui_apis.push(format!("{} (floating)", api));
3094                            }
3095                            capabilities.supports_floating = true;
3096                        }
3097                    }
3098                }
3099            }
3100        }
3101
3102        let params_ext_id = c"clap.params";
3103
3104        let params_ptr = unsafe { get_extension(plugin, params_ext_id.as_ptr()) };
3105        capabilities.has_params = !params_ptr.is_null();
3106
3107        let state_ext_id = c"clap.state";
3108
3109        let state_ptr = unsafe { get_extension(plugin, state_ext_id.as_ptr()) };
3110        capabilities.has_state = !state_ptr.is_null();
3111
3112        let audio_ports_ext_id = c"clap.audio-ports";
3113
3114        let audio_ports_ptr = unsafe { get_extension(plugin, audio_ports_ext_id.as_ptr()) };
3115        if !audio_ports_ptr.is_null() {
3116            let audio_ports = unsafe { &*(audio_ports_ptr as *const ClapPluginAudioPorts) };
3117            if let Some(count_fn) = audio_ports.count {
3118                capabilities.audio_inputs = unsafe { count_fn(plugin, true) } as usize;
3119
3120                capabilities.audio_outputs = unsafe { count_fn(plugin, false) } as usize;
3121            }
3122        }
3123
3124        let note_ports_ext_id = c"clap.note-ports";
3125
3126        let note_ports_ptr = unsafe { get_extension(plugin, note_ports_ext_id.as_ptr()) };
3127        if !note_ports_ptr.is_null() {
3128            let note_ports = unsafe { &*(note_ports_ptr as *const ClapPluginNotePorts) };
3129            if let Some(count_fn) = note_ports.count {
3130                capabilities.midi_inputs = unsafe { count_fn(plugin, true) } as usize;
3131
3132                capabilities.midi_outputs = unsafe { count_fn(plugin, false) } as usize;
3133            }
3134        }
3135    }
3136
3137    if let Some(destroy) = plugin_ref.destroy {
3138        unsafe { destroy(plugin) };
3139    }
3140
3141    Some(capabilities)
3142}
3143
3144fn default_clap_search_roots() -> Vec<PathBuf> {
3145    #[cfg(target_os = "macos")]
3146    {
3147        let mut roots = Vec::new();
3148        paths::push_macos_audio_plugin_roots(&mut roots, "CLAP");
3149        roots
3150    }
3151    #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
3152    {
3153        let mut roots = Vec::new();
3154        paths::push_unix_plugin_roots(&mut roots, "clap");
3155        roots
3156    }
3157    #[cfg(not(any(
3158        target_os = "macos",
3159        target_os = "linux",
3160        target_os = "freebsd",
3161        target_os = "openbsd"
3162    )))]
3163    {
3164        Vec::new()
3165    }
3166}
3167
3168#[cfg(test)]
3169mod tests {
3170    #[cfg(unix)]
3171    use super::collect_clap_plugins;
3172    #[cfg(unix)]
3173    use std::fs;
3174    #[cfg(unix)]
3175    use std::path::PathBuf;
3176    #[cfg(unix)]
3177    use std::time::{SystemTime, UNIX_EPOCH};
3178
3179    #[cfg(unix)]
3180    fn make_symlink(src: &PathBuf, dst: &PathBuf) {
3181        std::os::unix::fs::symlink(src, dst).expect("should create symlink");
3182    }
3183
3184    #[cfg(unix)]
3185    #[test]
3186    fn collect_clap_plugins_includes_symlinked_clap_files() {
3187        let nanos = SystemTime::now()
3188            .duration_since(UNIX_EPOCH)
3189            .expect("time should be valid")
3190            .as_nanos();
3191        let root = std::env::temp_dir().join(format!(
3192            "maolan-clap-symlink-test-{}-{nanos}",
3193            std::process::id()
3194        ));
3195        fs::create_dir_all(&root).expect("should create temp dir");
3196
3197        let target_file = root.join("librural_modeler.so");
3198        fs::write(&target_file, b"not a real clap binary").expect("should create target file");
3199        let clap_link = root.join("RuralModeler.clap");
3200        make_symlink(&PathBuf::from("librural_modeler.so"), &clap_link);
3201
3202        let mut out = Vec::new();
3203        collect_clap_plugins(&root, &mut out, false);
3204
3205        assert!(
3206            out.iter()
3207                .any(|info| info.path == clap_link.to_string_lossy()),
3208            "scanner should include symlinked .clap files"
3209        );
3210
3211        fs::remove_dir_all(&root).expect("should remove temp dir");
3212    }
3213}