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