Skip to main content

maolan_engine/plugins/
clap.rs

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