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