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