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