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