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