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