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