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