1use crate::audio::io::AudioIO;
2use crate::midi::io::MidiEvent;
3use crate::mutex::UnsafeMutex;
4#[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
5use crate::plugins::paths;
6use libloading::Library;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::ffi::{CStr, CString, c_char, c_void};
10use std::fmt;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use std::sync::atomic::{AtomicU32, Ordering};
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct ClapParameterInfo {
17 pub id: u32,
18 pub name: String,
19 pub module: String,
20 pub min_value: f64,
21 pub max_value: f64,
22 pub default_value: f64,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub struct ClapPluginState {
27 pub bytes: Vec<u8>,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct ClapMidiOutputEvent {
32 pub port: usize,
33 pub event: MidiEvent,
34}
35
36#[derive(Clone, Copy, Debug, Default)]
37pub struct ClapTransportInfo {
38 pub transport_sample: usize,
39 pub playing: bool,
40 pub loop_enabled: bool,
41 pub loop_range_samples: Option<(usize, usize)>,
42 pub bpm: f64,
43 pub tsig_num: u16,
44 pub tsig_denom: u16,
45}
46
47#[derive(Clone, Copy, Debug)]
48struct PendingParamValue {
49 param_id: u32,
50 value: f64,
51}
52
53#[derive(Clone, Copy, Debug)]
54enum PendingParamEvent {
55 Value {
56 param_id: u32,
57 value: f64,
58 frame: u32,
59 },
60 GestureBegin {
61 param_id: u32,
62 frame: u32,
63 },
64 GestureEnd {
65 param_id: u32,
66 frame: u32,
67 },
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
71pub struct ClapPluginInfo {
72 pub name: String,
73 pub path: String,
74 pub capabilities: Option<ClapPluginCapabilities>,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78pub struct ClapPluginCapabilities {
79 pub has_gui: bool,
80 pub gui_apis: Vec<String>,
81 pub supports_embedded: bool,
82 pub supports_floating: bool,
83 pub has_params: bool,
84 pub has_state: bool,
85 pub has_audio_ports: bool,
86 pub has_note_ports: bool,
87}
88
89#[derive(Clone)]
90pub struct ClapProcessor {
91 path: String,
92 name: String,
93 sample_rate: f64,
94 audio_inputs: Vec<Arc<AudioIO>>,
95 audio_outputs: Vec<Arc<AudioIO>>,
96 midi_input_ports: usize,
97 midi_output_ports: usize,
98 main_audio_inputs: usize,
99 main_audio_outputs: usize,
100 host_runtime: Arc<HostRuntime>,
101 plugin_handle: Arc<PluginHandle>,
102 param_infos: Arc<Vec<ClapParameterInfo>>,
103 param_values: Arc<UnsafeMutex<HashMap<u32, f64>>>,
104 pending_param_events: Arc<UnsafeMutex<Vec<PendingParamEvent>>>,
105}
106
107impl fmt::Debug for ClapProcessor {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.debug_struct("ClapProcessor")
110 .field("path", &self.path)
111 .field("name", &self.name)
112 .field("audio_inputs", &self.audio_inputs.len())
113 .field("audio_outputs", &self.audio_outputs.len())
114 .field("midi_input_ports", &self.midi_input_ports)
115 .field("midi_output_ports", &self.midi_output_ports)
116 .field("main_audio_inputs", &self.main_audio_inputs)
117 .field("main_audio_outputs", &self.main_audio_outputs)
118 .finish()
119 }
120}
121
122impl ClapProcessor {
123 pub fn new(
124 sample_rate: f64,
125 buffer_size: usize,
126 plugin_spec: &str,
127 input_count: usize,
128 output_count: usize,
129 ) -> Result<Self, String> {
130 let (plugin_path, plugin_id) = split_plugin_spec(plugin_spec);
131 let name = Path::new(plugin_path)
132 .file_stem()
133 .map(|s| s.to_string_lossy().to_string())
134 .unwrap_or_else(|| plugin_spec.to_string());
135 let host_runtime = Arc::new(HostRuntime::new()?);
136 let plugin_handle = Arc::new(PluginHandle::load(
137 plugin_path,
138 plugin_id,
139 host_runtime.clone(),
140 sample_rate,
141 buffer_size as u32,
142 )?);
143 let (discovered_inputs, discovered_outputs) = plugin_handle.audio_port_layout();
144 let (discovered_midi_inputs, discovered_midi_outputs) = plugin_handle.note_port_layout();
145 let resolved_inputs = discovered_inputs.unwrap_or(input_count).max(1);
146 let resolved_outputs = discovered_outputs.unwrap_or(output_count).max(1);
147 let main_audio_inputs = if discovered_inputs.is_some() {
148 usize::from(resolved_inputs > 0)
149 } else {
150 input_count.max(1)
151 };
152 let main_audio_outputs = if discovered_outputs.is_some() {
153 usize::from(resolved_outputs > 0)
154 } else {
155 output_count.max(1)
156 };
157 let audio_inputs = (0..resolved_inputs)
158 .map(|_| Arc::new(AudioIO::new(buffer_size)))
159 .collect();
160 let audio_outputs = (0..resolved_outputs)
161 .map(|_| Arc::new(AudioIO::new(buffer_size)))
162 .collect();
163 let param_infos = Arc::new(plugin_handle.parameter_infos());
164 let param_values = Arc::new(UnsafeMutex::new(
165 plugin_handle.parameter_values(¶m_infos),
166 ));
167 Ok(Self {
168 path: plugin_spec.to_string(),
169 name,
170 sample_rate,
171 audio_inputs,
172 audio_outputs,
173 midi_input_ports: discovered_midi_inputs.unwrap_or(1).max(1),
174 midi_output_ports: discovered_midi_outputs.unwrap_or(1).max(1),
175 main_audio_inputs,
176 main_audio_outputs,
177 host_runtime,
178 plugin_handle,
179 param_infos,
180 param_values,
181 pending_param_events: Arc::new(UnsafeMutex::new(Vec::new())),
182 })
183 }
184
185 pub fn setup_audio_ports(&self) {
186 for port in &self.audio_inputs {
187 port.setup();
188 }
189 for port in &self.audio_outputs {
190 port.setup();
191 }
192 }
193
194 pub fn process_with_audio_io(&self, frames: usize) {
195 let _ = self.process_with_midi(frames, &[], ClapTransportInfo::default());
196 }
197
198 pub fn process_with_midi(
199 &self,
200 frames: usize,
201 midi_in: &[MidiEvent],
202 transport: ClapTransportInfo,
203 ) -> Vec<ClapMidiOutputEvent> {
204 for port in &self.audio_inputs {
205 if port.ready() {
206 port.process();
207 }
208 }
209 let (processed, processed_midi) = match self.process_native(frames, midi_in, transport) {
210 Ok(v) => v,
211 Err(_) => (false, Vec::new()),
212 };
213 if !processed {
214 for out in &self.audio_outputs {
215 let out_buf = out.buffer.lock();
216 out_buf.fill(0.0);
217 *out.finished.lock() = true;
218 }
219 }
220 processed_midi
221 }
222
223 pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
224 self.param_infos.as_ref().clone()
225 }
226
227 pub fn parameter_values(&self) -> HashMap<u32, f64> {
228 self.param_values.lock().clone()
229 }
230
231 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
232 self.set_parameter_at(param_id, value, 0)
233 }
234
235 pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
236 let Some(info) = self.param_infos.iter().find(|p| p.id == param_id) else {
237 return Err(format!("Unknown CLAP parameter id: {param_id}"));
238 };
239 let clamped = value.clamp(info.min_value, info.max_value);
240 self.pending_param_events
241 .lock()
242 .push(PendingParamEvent::Value {
243 param_id,
244 value: clamped,
245 frame,
246 });
247 self.param_values.lock().insert(param_id, clamped);
248 Ok(())
249 }
250
251 pub fn begin_parameter_edit(&self, param_id: u32) -> Result<(), String> {
252 self.begin_parameter_edit_at(param_id, 0)
253 }
254
255 pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
256 if !self.param_infos.iter().any(|p| p.id == param_id) {
257 return Err(format!("Unknown CLAP parameter id: {param_id}"));
258 }
259 self.pending_param_events
260 .lock()
261 .push(PendingParamEvent::GestureBegin { param_id, frame });
262 Ok(())
263 }
264
265 pub fn end_parameter_edit(&self, param_id: u32) -> Result<(), String> {
266 self.end_parameter_edit_at(param_id, 0)
267 }
268
269 pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
270 if !self.param_infos.iter().any(|p| p.id == param_id) {
271 return Err(format!("Unknown CLAP parameter id: {param_id}"));
272 }
273 self.pending_param_events
274 .lock()
275 .push(PendingParamEvent::GestureEnd { param_id, frame });
276 Ok(())
277 }
278
279 pub fn snapshot_state(&self) -> Result<ClapPluginState, String> {
280 self.plugin_handle.snapshot_state()
281 }
282
283 pub fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
284 self.plugin_handle.restore_state(state)
285 }
286
287 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
288 &self.audio_inputs
289 }
290
291 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
292 &self.audio_outputs
293 }
294
295 pub fn main_audio_input_count(&self) -> usize {
296 self.main_audio_inputs
297 }
298
299 pub fn main_audio_output_count(&self) -> usize {
300 self.main_audio_outputs
301 }
302
303 pub fn midi_input_count(&self) -> usize {
304 self.midi_input_ports
305 }
306
307 pub fn midi_output_count(&self) -> usize {
308 self.midi_output_ports
309 }
310
311 pub fn path(&self) -> &str {
312 &self.path
313 }
314
315 pub fn name(&self) -> &str {
316 &self.name
317 }
318
319 fn process_native(
320 &self,
321 frames: usize,
322 midi_in: &[MidiEvent],
323 transport: ClapTransportInfo,
324 ) -> Result<(bool, Vec<ClapMidiOutputEvent>), String> {
325 if frames == 0 {
326 return Ok((true, Vec::new()));
327 }
328
329 let mut in_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_inputs.len());
330 let mut out_channel_ptrs: Vec<Vec<*mut f32>> = Vec::with_capacity(self.audio_outputs.len());
331 let mut in_buffers = Vec::with_capacity(self.audio_inputs.len());
332 let mut out_buffers = Vec::with_capacity(self.audio_outputs.len());
333
334 for input in &self.audio_inputs {
335 let buf = input.buffer.lock();
336 in_channel_ptrs.push(vec![buf.as_ptr() as *mut f32]);
337 in_buffers.push(buf);
338 }
339 for output in &self.audio_outputs {
340 let buf = output.buffer.lock();
341 out_channel_ptrs.push(vec![buf.as_ptr() as *mut f32]);
342 out_buffers.push(buf);
343 }
344
345 let mut in_audio = Vec::with_capacity(self.audio_inputs.len());
346 let mut out_audio = Vec::with_capacity(self.audio_outputs.len());
347
348 for ptrs in &mut in_channel_ptrs {
349 in_audio.push(ClapAudioBuffer {
350 data32: ptrs.as_mut_ptr(),
351 data64: std::ptr::null_mut(),
352 channel_count: 1,
353 latency: 0,
354 constant_mask: 0,
355 });
356 }
357 for ptrs in &mut out_channel_ptrs {
358 out_audio.push(ClapAudioBuffer {
359 data32: ptrs.as_mut_ptr(),
360 data64: std::ptr::null_mut(),
361 channel_count: 1,
362 latency: 0,
363 constant_mask: 0,
364 });
365 }
366
367 let pending_params = std::mem::take(self.pending_param_events.lock());
368 let (in_events, in_ctx) =
369 input_events_from(midi_in, &pending_params, self.sample_rate, transport);
370 let out_cap = midi_in
371 .len()
372 .saturating_add(self.midi_output_ports.saturating_mul(64));
373 let (mut out_events, mut out_ctx) = output_events_ctx(out_cap);
374
375 let mut process = ClapProcess {
376 steady_time: -1,
377 frames_count: frames as u32,
378 transport: std::ptr::null(),
379 audio_inputs: in_audio.as_mut_ptr(),
380 audio_outputs: out_audio.as_mut_ptr(),
381 audio_inputs_count: in_audio.len() as u32,
382 audio_outputs_count: out_audio.len() as u32,
383 in_events: &in_events,
384 out_events: &mut out_events,
385 };
386
387 let result = self.plugin_handle.process(&mut process);
388 drop(in_ctx);
389 for output in &self.audio_outputs {
390 *output.finished.lock() = true;
391 }
392 let processed = result?;
393 let host_flags = self.host_runtime.take_callback_flags();
394 if host_flags.restart {
395 self.plugin_handle.reset();
396 }
397 if host_flags.callback {
398 self.plugin_handle.on_main_thread();
399 }
400 if host_flags.process {
401 }
403 if processed {
404 for update in &out_ctx.param_values {
405 self.param_values
406 .lock()
407 .insert(update.param_id, update.value);
408 }
409 Ok((true, std::mem::take(&mut out_ctx.midi_events)))
410 } else {
411 Ok((false, Vec::new()))
412 }
413 }
414}
415
416#[repr(C)]
417#[derive(Clone, Copy)]
418struct ClapVersion {
419 major: u32,
420 minor: u32,
421 revision: u32,
422}
423
424const CLAP_VERSION: ClapVersion = ClapVersion {
425 major: 1,
426 minor: 2,
427 revision: 0,
428};
429
430#[repr(C)]
431struct ClapHost {
432 clap_version: ClapVersion,
433 host_data: *mut c_void,
434 name: *const c_char,
435 vendor: *const c_char,
436 url: *const c_char,
437 version: *const c_char,
438 get_extension: Option<unsafe extern "C" fn(*const ClapHost, *const c_char) -> *const c_void>,
439 request_restart: Option<unsafe extern "C" fn(*const ClapHost)>,
440 request_process: Option<unsafe extern "C" fn(*const ClapHost)>,
441 request_callback: Option<unsafe extern "C" fn(*const ClapHost)>,
442}
443
444#[repr(C)]
445struct ClapPluginEntry {
446 clap_version: ClapVersion,
447 init: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
448 deinit: Option<unsafe extern "C" fn()>,
449 get_factory: Option<unsafe extern "C" fn(*const c_char) -> *const c_void>,
450}
451
452#[repr(C)]
453struct ClapPluginFactory {
454 get_plugin_count: Option<unsafe extern "C" fn(*const ClapPluginFactory) -> u32>,
455 get_plugin_descriptor:
456 Option<unsafe extern "C" fn(*const ClapPluginFactory, u32) -> *const ClapPluginDescriptor>,
457 create_plugin: Option<
458 unsafe extern "C" fn(
459 *const ClapPluginFactory,
460 *const ClapHost,
461 *const c_char,
462 ) -> *const ClapPlugin,
463 >,
464}
465
466#[repr(C)]
467struct ClapPluginDescriptor {
468 clap_version: ClapVersion,
469 id: *const c_char,
470 name: *const c_char,
471 vendor: *const c_char,
472 url: *const c_char,
473 manual_url: *const c_char,
474 support_url: *const c_char,
475 version: *const c_char,
476 description: *const c_char,
477 features: *const *const c_char,
478}
479
480#[repr(C)]
481struct ClapPlugin {
482 desc: *const ClapPluginDescriptor,
483 plugin_data: *mut c_void,
484 init: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
485 destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
486 activate: Option<unsafe extern "C" fn(*const ClapPlugin, f64, u32, u32) -> bool>,
487 deactivate: Option<unsafe extern "C" fn(*const ClapPlugin)>,
488 start_processing: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
489 stop_processing: Option<unsafe extern "C" fn(*const ClapPlugin)>,
490 reset: Option<unsafe extern "C" fn(*const ClapPlugin)>,
491 process: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapProcess) -> i32>,
492 get_extension: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char) -> *const c_void>,
493 on_main_thread: Option<unsafe extern "C" fn(*const ClapPlugin)>,
494}
495
496#[repr(C)]
497struct ClapInputEvents {
498 ctx: *const c_void,
499 size: Option<unsafe extern "C" fn(*const ClapInputEvents) -> u32>,
500 get: Option<unsafe extern "C" fn(*const ClapInputEvents, u32) -> *const ClapEventHeader>,
501}
502
503#[repr(C)]
504struct ClapOutputEvents {
505 ctx: *mut c_void,
506 try_push: Option<unsafe extern "C" fn(*const ClapOutputEvents, *const ClapEventHeader) -> bool>,
507}
508
509#[repr(C)]
510struct ClapEventHeader {
511 size: u32,
512 time: u32,
513 space_id: u16,
514 type_: u16,
515 flags: u32,
516}
517
518const CLAP_CORE_EVENT_SPACE_ID: u16 = 0;
519const CLAP_EVENT_MIDI: u16 = 10;
520const CLAP_EVENT_PARAM_VALUE: u16 = 5;
521const CLAP_EVENT_PARAM_GESTURE_BEGIN: u16 = 6;
522const CLAP_EVENT_PARAM_GESTURE_END: u16 = 7;
523const CLAP_EVENT_TRANSPORT: u16 = 9;
524const CLAP_TRANSPORT_HAS_TEMPO: u32 = 1 << 0;
525const CLAP_TRANSPORT_HAS_BEATS_TIMELINE: u32 = 1 << 1;
526const CLAP_TRANSPORT_HAS_SECONDS_TIMELINE: u32 = 1 << 2;
527const CLAP_TRANSPORT_HAS_TIME_SIGNATURE: u32 = 1 << 3;
528const CLAP_TRANSPORT_IS_PLAYING: u32 = 1 << 4;
529const CLAP_TRANSPORT_IS_LOOP_ACTIVE: u32 = 1 << 6;
530const CLAP_BEATTIME_FACTOR: i64 = 1_i64 << 31;
531const CLAP_SECTIME_FACTOR: i64 = 1_i64 << 31;
532
533#[repr(C)]
534struct ClapEventMidi {
535 header: ClapEventHeader,
536 port_index: u16,
537 data: [u8; 3],
538}
539
540#[repr(C)]
541struct ClapEventParamValue {
542 header: ClapEventHeader,
543 param_id: u32,
544 cookie: *mut c_void,
545 note_id: i32,
546 port_index: i16,
547 channel: i16,
548 key: i16,
549 value: f64,
550}
551
552#[repr(C)]
553struct ClapEventParamGesture {
554 header: ClapEventHeader,
555 param_id: u32,
556}
557
558#[repr(C)]
559struct ClapEventTransport {
560 header: ClapEventHeader,
561 flags: u32,
562 song_pos_beats: i64,
563 song_pos_seconds: i64,
564 tempo: f64,
565 tempo_inc: f64,
566 loop_start_beats: i64,
567 loop_end_beats: i64,
568 loop_start_seconds: i64,
569 loop_end_seconds: i64,
570 bar_start: i64,
571 bar_number: i32,
572 tsig_num: u16,
573 tsig_denom: u16,
574}
575
576#[repr(C)]
577struct ClapParamInfoRaw {
578 id: u32,
579 flags: u32,
580 cookie: *mut c_void,
581 name: [c_char; 256],
582 module: [c_char; 1024],
583 min_value: f64,
584 max_value: f64,
585 default_value: f64,
586}
587
588#[repr(C)]
589struct ClapPluginParams {
590 count: Option<unsafe extern "C" fn(*const ClapPlugin) -> u32>,
591 get_info: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut ClapParamInfoRaw) -> bool>,
592 get_value: Option<unsafe extern "C" fn(*const ClapPlugin, u32, *mut f64) -> bool>,
593 value_to_text:
594 Option<unsafe extern "C" fn(*const ClapPlugin, u32, f64, *mut c_char, u32) -> bool>,
595 text_to_value:
596 Option<unsafe extern "C" fn(*const ClapPlugin, u32, *const c_char, *mut f64) -> bool>,
597 flush: Option<
598 unsafe extern "C" fn(*const ClapPlugin, *const ClapInputEvents, *const ClapOutputEvents),
599 >,
600}
601
602#[repr(C)]
603struct ClapPluginStateExt {
604 save: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapOStream) -> bool>,
605 load: Option<unsafe extern "C" fn(*const ClapPlugin, *const ClapIStream) -> bool>,
606}
607
608#[repr(C)]
609struct ClapAudioPortInfoRaw {
610 id: u32,
611 name: [c_char; 256],
612 flags: u32,
613 channel_count: u32,
614 port_type: *const c_char,
615 in_place_pair: u32,
616}
617
618#[repr(C)]
619struct ClapPluginAudioPorts {
620 count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
621 get: Option<
622 unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapAudioPortInfoRaw) -> bool,
623 >,
624}
625
626#[repr(C)]
627struct ClapNotePortInfoRaw {
628 id: u16,
629 supported_dialects: u32,
630 preferred_dialect: u32,
631 name: [c_char; 256],
632}
633
634#[repr(C)]
635struct ClapPluginNotePorts {
636 count: Option<unsafe extern "C" fn(*const ClapPlugin, bool) -> u32>,
637 get: Option<
638 unsafe extern "C" fn(*const ClapPlugin, u32, bool, *mut ClapNotePortInfoRaw) -> bool,
639 >,
640}
641
642#[repr(C)]
643struct ClapPluginGui {
644 is_api_supported: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
645 get_preferred_api:
646 Option<unsafe extern "C" fn(*const ClapPlugin, *mut *const c_char, *mut bool) -> bool>,
647 create: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char, bool) -> bool>,
648 destroy: Option<unsafe extern "C" fn(*const ClapPlugin)>,
649 set_scale: Option<unsafe extern "C" fn(*const ClapPlugin, f64) -> bool>,
650 get_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
651 can_resize: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
652 get_resize_hints: Option<unsafe extern "C" fn(*const ClapPlugin, *mut c_void) -> bool>,
653 adjust_size: Option<unsafe extern "C" fn(*const ClapPlugin, *mut u32, *mut u32) -> bool>,
654 set_size: Option<unsafe extern "C" fn(*const ClapPlugin, u32, u32) -> bool>,
655 set_parent: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_void) -> bool>,
656 set_transient: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_void) -> bool>,
657 suggest_title: Option<unsafe extern "C" fn(*const ClapPlugin, *const c_char)>,
658 show: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
659 hide: Option<unsafe extern "C" fn(*const ClapPlugin) -> bool>,
660}
661
662#[repr(C)]
663struct ClapHostThreadCheck {
664 is_main_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
665 is_audio_thread: Option<unsafe extern "C" fn(*const ClapHost) -> bool>,
666}
667
668#[repr(C)]
669struct ClapHostLatency {
670 changed: Option<unsafe extern "C" fn(*const ClapHost)>,
671}
672
673#[repr(C)]
674struct ClapHostTail {
675 changed: Option<unsafe extern "C" fn(*const ClapHost)>,
676}
677
678#[repr(C)]
679struct ClapHostTimerSupport {
680 register_timer: Option<unsafe extern "C" fn(*const ClapHost, u32, *mut u32) -> bool>,
681 unregister_timer: Option<unsafe extern "C" fn(*const ClapHost, u32) -> bool>,
682}
683
684#[repr(C)]
685struct ClapOStream {
686 ctx: *mut c_void,
687 write: Option<unsafe extern "C" fn(*const ClapOStream, *const c_void, u64) -> i64>,
688}
689
690#[repr(C)]
691struct ClapIStream {
692 ctx: *mut c_void,
693 read: Option<unsafe extern "C" fn(*const ClapIStream, *mut c_void, u64) -> i64>,
694}
695
696#[repr(C)]
697struct ClapAudioBuffer {
698 data32: *mut *mut f32,
699 data64: *mut *mut f64,
700 channel_count: u32,
701 latency: u32,
702 constant_mask: u64,
703}
704
705#[repr(C)]
706struct ClapProcess {
707 steady_time: i64,
708 frames_count: u32,
709 transport: *const c_void,
710 audio_inputs: *mut ClapAudioBuffer,
711 audio_outputs: *mut ClapAudioBuffer,
712 audio_inputs_count: u32,
713 audio_outputs_count: u32,
714 in_events: *const ClapInputEvents,
715 out_events: *mut ClapOutputEvents,
716}
717
718enum ClapInputEvent {
719 Midi(ClapEventMidi),
720 ParamValue(ClapEventParamValue),
721 ParamGesture(ClapEventParamGesture),
722 Transport(ClapEventTransport),
723}
724
725impl ClapInputEvent {
726 fn header_ptr(&self) -> *const ClapEventHeader {
727 match self {
728 Self::Midi(e) => &e.header as *const ClapEventHeader,
729 Self::ParamValue(e) => &e.header as *const ClapEventHeader,
730 Self::ParamGesture(e) => &e.header as *const ClapEventHeader,
731 Self::Transport(e) => &e.header as *const ClapEventHeader,
732 }
733 }
734}
735
736struct ClapInputEventsCtx {
737 events: Vec<ClapInputEvent>,
738}
739
740struct ClapOutputEventsCtx {
741 midi_events: Vec<ClapMidiOutputEvent>,
742 param_values: Vec<PendingParamValue>,
743}
744
745struct ClapIStreamCtx<'a> {
746 bytes: &'a [u8],
747 offset: usize,
748}
749
750struct HostRuntime {
751 callback_flags: Box<UnsafeMutex<HostCallbackFlags>>,
752 host: ClapHost,
753}
754
755#[derive(Default, Clone, Copy)]
756struct HostCallbackFlags {
757 restart: bool,
758 process: bool,
759 callback: bool,
760}
761
762impl HostRuntime {
763 fn new() -> Result<Self, String> {
764 let mut callback_flags = Box::new(UnsafeMutex::new(HostCallbackFlags::default()));
765 let host = ClapHost {
766 clap_version: CLAP_VERSION,
767 host_data: (&mut *callback_flags as *mut UnsafeMutex<HostCallbackFlags>)
768 .cast::<c_void>(),
769 name: c"Maolan".as_ptr(),
770 vendor: c"Maolan".as_ptr(),
771 url: c"https://example.invalid".as_ptr(),
772 version: c"0.0.1".as_ptr(),
773 get_extension: Some(host_get_extension),
774 request_restart: Some(host_request_restart),
775 request_process: Some(host_request_process),
776 request_callback: Some(host_request_callback),
777 };
778 Ok(Self {
779 callback_flags,
780 host,
781 })
782 }
783
784 fn take_callback_flags(&self) -> HostCallbackFlags {
785 let flags = self.callback_flags.lock();
786 let out = *flags;
787 *flags = HostCallbackFlags::default();
788 out
789 }
790}
791
792unsafe impl Send for HostRuntime {}
796unsafe impl Sync for HostRuntime {}
798
799struct PluginHandle {
800 _library: Library,
801 entry: *const ClapPluginEntry,
802 plugin: *const ClapPlugin,
803}
804
805unsafe impl Send for PluginHandle {}
808unsafe impl Sync for PluginHandle {}
810
811impl PluginHandle {
812 fn load(
813 plugin_path: &str,
814 plugin_id: Option<&str>,
815 host_runtime: Arc<HostRuntime>,
816 sample_rate: f64,
817 frames: u32,
818 ) -> Result<Self, String> {
819 let factory_id = c"clap.plugin-factory";
820
821 let library = unsafe { Library::new(plugin_path) }.map_err(|e| e.to_string())?;
823 let entry_ptr = unsafe {
825 let sym = library
826 .get::<*const ClapPluginEntry>(b"clap_entry\0")
827 .map_err(|e| e.to_string())?;
828 *sym
829 };
830 if entry_ptr.is_null() {
831 return Err("CLAP entry symbol is null".to_string());
832 }
833 let entry = unsafe { &*entry_ptr };
835 let init = entry
836 .init
837 .ok_or_else(|| "CLAP entry missing init()".to_string())?;
838 let host_ptr = &host_runtime.host as *const ClapHost;
839 if unsafe { !init(host_ptr) } {
841 return Err(format!("CLAP entry init failed for {plugin_path}"));
842 }
843 let get_factory = entry
844 .get_factory
845 .ok_or_else(|| "CLAP entry missing get_factory()".to_string())?;
846 let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
848 if factory.is_null() {
849 return Err("CLAP plugin factory not found".to_string());
850 }
851 let factory_ref = unsafe { &*factory };
853 let get_count = factory_ref
854 .get_plugin_count
855 .ok_or_else(|| "CLAP factory missing get_plugin_count()".to_string())?;
856 let get_desc = factory_ref
857 .get_plugin_descriptor
858 .ok_or_else(|| "CLAP factory missing get_plugin_descriptor()".to_string())?;
859 let create = factory_ref
860 .create_plugin
861 .ok_or_else(|| "CLAP factory missing create_plugin()".to_string())?;
862
863 let count = unsafe { get_count(factory) };
865 if count == 0 {
866 return Err("CLAP factory returned zero plugins".to_string());
867 }
868 let mut selected_id = None::<CString>;
869 for i in 0..count {
870 let desc = unsafe { get_desc(factory, i) };
872 if desc.is_null() {
873 continue;
874 }
875 let desc = unsafe { &*desc };
877 if desc.id.is_null() {
878 continue;
879 }
880 let id = unsafe { CStr::from_ptr(desc.id) };
882 let id_str = id.to_string_lossy();
883 if plugin_id.is_none() || plugin_id == Some(id_str.as_ref()) {
884 selected_id = Some(
885 CString::new(id_str.as_ref()).map_err(|e| format!("Invalid plugin id: {e}"))?,
886 );
887 break;
888 }
889 }
890 let selected_id = selected_id.ok_or_else(|| {
891 if let Some(id) = plugin_id {
892 format!("CLAP descriptor id not found in bundle: {id}")
893 } else {
894 "CLAP descriptor not found".to_string()
895 }
896 })?;
897 let plugin = unsafe { create(factory, &host_runtime.host, selected_id.as_ptr()) };
899 if plugin.is_null() {
900 return Err("CLAP factory create_plugin failed".to_string());
901 }
902 let plugin_ref = unsafe { &*plugin };
904 let plugin_init = plugin_ref
905 .init
906 .ok_or_else(|| "CLAP plugin missing init()".to_string())?;
907 if unsafe { !plugin_init(plugin) } {
909 return Err("CLAP plugin init() failed".to_string());
910 }
911 if let Some(activate) = plugin_ref.activate {
912 if unsafe { !activate(plugin, sample_rate, frames.max(1), frames.max(1)) } {
914 return Err("CLAP plugin activate() failed".to_string());
915 }
916 }
917 if let Some(start_processing) = plugin_ref.start_processing {
918 if unsafe { !start_processing(plugin) } {
920 return Err("CLAP plugin start_processing() failed".to_string());
921 }
922 }
923 Ok(Self {
924 _library: library,
925 entry: entry_ptr,
926 plugin,
927 })
928 }
929
930 fn process(&self, process: &mut ClapProcess) -> Result<bool, String> {
931 let plugin = unsafe { &*self.plugin };
933 let Some(process_fn) = plugin.process else {
934 return Ok(false);
935 };
936 let _status = unsafe { process_fn(self.plugin, process as *const _) };
938 Ok(true)
939 }
940
941 fn reset(&self) {
942 let plugin = unsafe { &*self.plugin };
944 if let Some(reset) = plugin.reset {
945 unsafe { reset(self.plugin) };
947 }
948 }
949
950 fn on_main_thread(&self) {
951 let plugin = unsafe { &*self.plugin };
953 if let Some(on_main_thread) = plugin.on_main_thread {
954 unsafe { on_main_thread(self.plugin) };
956 }
957 }
958
959 fn params_ext(&self) -> Option<&ClapPluginParams> {
960 let ext_id = c"clap.params";
961 let plugin = unsafe { &*self.plugin };
963 let get_extension = plugin.get_extension?;
964 let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
966 if ext_ptr.is_null() {
967 return None;
968 }
969 Some(unsafe { &*(ext_ptr as *const ClapPluginParams) })
971 }
972
973 fn state_ext(&self) -> Option<&ClapPluginStateExt> {
974 let ext_id = c"clap.state";
975 let plugin = unsafe { &*self.plugin };
977 let get_extension = plugin.get_extension?;
978 let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
980 if ext_ptr.is_null() {
981 return None;
982 }
983 Some(unsafe { &*(ext_ptr as *const ClapPluginStateExt) })
985 }
986
987 fn audio_ports_ext(&self) -> Option<&ClapPluginAudioPorts> {
988 let ext_id = c"clap.audio-ports";
989 let plugin = unsafe { &*self.plugin };
991 let get_extension = plugin.get_extension?;
992 let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
994 if ext_ptr.is_null() {
995 return None;
996 }
997 Some(unsafe { &*(ext_ptr as *const ClapPluginAudioPorts) })
999 }
1000
1001 fn note_ports_ext(&self) -> Option<&ClapPluginNotePorts> {
1002 let ext_id = c"clap.note-ports";
1003 let plugin = unsafe { &*self.plugin };
1005 let get_extension = plugin.get_extension?;
1006 let ext_ptr = unsafe { get_extension(self.plugin, ext_id.as_ptr()) };
1008 if ext_ptr.is_null() {
1009 return None;
1010 }
1011 Some(unsafe { &*(ext_ptr as *const ClapPluginNotePorts) })
1013 }
1014
1015 fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
1016 let Some(params) = self.params_ext() else {
1017 return Vec::new();
1018 };
1019 let Some(count_fn) = params.count else {
1020 return Vec::new();
1021 };
1022 let Some(get_info_fn) = params.get_info else {
1023 return Vec::new();
1024 };
1025 let count = unsafe { count_fn(self.plugin) };
1027 let mut out = Vec::with_capacity(count as usize);
1028 for idx in 0..count {
1029 let mut info = ClapParamInfoRaw {
1030 id: 0,
1031 flags: 0,
1032 cookie: std::ptr::null_mut(),
1033 name: [0; 256],
1034 module: [0; 1024],
1035 min_value: 0.0,
1036 max_value: 1.0,
1037 default_value: 0.0,
1038 };
1039 if unsafe { !get_info_fn(self.plugin, idx, &mut info as *mut _) } {
1041 continue;
1042 }
1043 out.push(ClapParameterInfo {
1044 id: info.id,
1045 name: c_char_buf_to_string(&info.name),
1046 module: c_char_buf_to_string(&info.module),
1047 min_value: info.min_value,
1048 max_value: info.max_value,
1049 default_value: info.default_value,
1050 });
1051 }
1052 out
1053 }
1054
1055 fn parameter_values(&self, infos: &[ClapParameterInfo]) -> HashMap<u32, f64> {
1056 let mut out = HashMap::new();
1057 let Some(params) = self.params_ext() else {
1058 for info in infos {
1059 out.insert(info.id, info.default_value);
1060 }
1061 return out;
1062 };
1063 let Some(get_value_fn) = params.get_value else {
1064 for info in infos {
1065 out.insert(info.id, info.default_value);
1066 }
1067 return out;
1068 };
1069 for info in infos {
1070 let mut value = info.default_value;
1071 if unsafe { !get_value_fn(self.plugin, info.id, &mut value as *mut _) } {
1073 value = info.default_value;
1074 }
1075 out.insert(info.id, value);
1076 }
1077 out
1078 }
1079
1080 fn snapshot_state(&self) -> Result<ClapPluginState, String> {
1081 let Some(state_ext) = self.state_ext() else {
1082 return Ok(ClapPluginState { bytes: Vec::new() });
1083 };
1084 let Some(save_fn) = state_ext.save else {
1085 return Ok(ClapPluginState { bytes: Vec::new() });
1086 };
1087 let mut bytes = Vec::<u8>::new();
1088 let mut stream = ClapOStream {
1089 ctx: (&mut bytes as *mut Vec<u8>).cast::<c_void>(),
1090 write: Some(clap_ostream_write),
1091 };
1092 if unsafe {
1094 !save_fn(
1095 self.plugin,
1096 &mut stream as *mut ClapOStream as *const ClapOStream,
1097 )
1098 } {
1099 return Err("CLAP state save failed".to_string());
1100 }
1101 Ok(ClapPluginState { bytes })
1102 }
1103
1104 fn restore_state(&self, state: &ClapPluginState) -> Result<(), String> {
1105 let Some(state_ext) = self.state_ext() else {
1106 return Ok(());
1107 };
1108 let Some(load_fn) = state_ext.load else {
1109 return Ok(());
1110 };
1111 let mut ctx = ClapIStreamCtx {
1112 bytes: &state.bytes,
1113 offset: 0,
1114 };
1115 let mut stream = ClapIStream {
1116 ctx: (&mut ctx as *mut ClapIStreamCtx).cast::<c_void>(),
1117 read: Some(clap_istream_read),
1118 };
1119 if unsafe {
1121 !load_fn(
1122 self.plugin,
1123 &mut stream as *mut ClapIStream as *const ClapIStream,
1124 )
1125 } {
1126 return Err("CLAP state load failed".to_string());
1127 }
1128 Ok(())
1129 }
1130
1131 fn audio_port_layout(&self) -> (Option<usize>, Option<usize>) {
1132 let Some(ext) = self.audio_ports_ext() else {
1133 return (None, None);
1134 };
1135 let Some(count_fn) = ext.count else {
1136 return (None, None);
1137 };
1138 let in_count = unsafe { count_fn(self.plugin, true) } as usize;
1140 let out_count = unsafe { count_fn(self.plugin, false) } as usize;
1142 (Some(in_count.max(1)), Some(out_count.max(1)))
1143 }
1144
1145 fn note_port_layout(&self) -> (Option<usize>, Option<usize>) {
1146 let Some(ext) = self.note_ports_ext() else {
1147 return (None, None);
1148 };
1149 let Some(count_fn) = ext.count else {
1150 return (None, None);
1151 };
1152 let in_count = unsafe { count_fn(self.plugin, true) } as usize;
1154 let out_count = unsafe { count_fn(self.plugin, false) } as usize;
1156 (Some(in_count.max(1)), Some(out_count.max(1)))
1157 }
1158}
1159
1160impl Drop for PluginHandle {
1161 fn drop(&mut self) {
1162 unsafe {
1164 if !self.plugin.is_null() {
1165 let plugin = &*self.plugin;
1166 if let Some(stop_processing) = plugin.stop_processing {
1167 stop_processing(self.plugin);
1168 }
1169 if let Some(deactivate) = plugin.deactivate {
1170 deactivate(self.plugin);
1171 }
1172 if let Some(destroy) = plugin.destroy {
1173 destroy(self.plugin);
1174 }
1175 }
1176 if !self.entry.is_null() {
1177 let entry = &*self.entry;
1178 if let Some(deinit) = entry.deinit {
1179 deinit();
1180 }
1181 }
1182 }
1183 }
1184}
1185
1186static HOST_THREAD_CHECK_EXT: ClapHostThreadCheck = ClapHostThreadCheck {
1187 is_main_thread: Some(host_is_main_thread),
1188 is_audio_thread: Some(host_is_audio_thread),
1189};
1190static HOST_LATENCY_EXT: ClapHostLatency = ClapHostLatency {
1191 changed: Some(host_latency_changed),
1192};
1193static HOST_TAIL_EXT: ClapHostTail = ClapHostTail {
1194 changed: Some(host_tail_changed),
1195};
1196static HOST_TIMER_EXT: ClapHostTimerSupport = ClapHostTimerSupport {
1197 register_timer: Some(host_timer_register),
1198 unregister_timer: Some(host_timer_unregister),
1199};
1200static NEXT_TIMER_ID: AtomicU32 = AtomicU32::new(1);
1201
1202unsafe extern "C" fn host_get_extension(
1203 _host: *const ClapHost,
1204 _extension_id: *const c_char,
1205) -> *const c_void {
1206 if _extension_id.is_null() {
1207 return std::ptr::null();
1208 }
1209 let id = unsafe { CStr::from_ptr(_extension_id) }.to_string_lossy();
1211 match id.as_ref() {
1212 "clap.host.thread-check" => {
1213 (&HOST_THREAD_CHECK_EXT as *const ClapHostThreadCheck).cast::<c_void>()
1214 }
1215 "clap.host.latency" => (&HOST_LATENCY_EXT as *const ClapHostLatency).cast::<c_void>(),
1216 "clap.host.tail" => (&HOST_TAIL_EXT as *const ClapHostTail).cast::<c_void>(),
1217 "clap.host.timer-support" => {
1218 (&HOST_TIMER_EXT as *const ClapHostTimerSupport).cast::<c_void>()
1219 }
1220 _ => std::ptr::null(),
1221 }
1222}
1223
1224unsafe extern "C" fn host_request_process(_host: *const ClapHost) {
1225 if _host.is_null() {
1226 return;
1227 }
1228 let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1230 if flags_ptr.is_null() {
1231 return;
1232 }
1233 unsafe {
1235 (*flags_ptr).lock().process = true;
1236 }
1237}
1238
1239unsafe extern "C" fn host_request_callback(_host: *const ClapHost) {
1240 if _host.is_null() {
1241 return;
1242 }
1243 let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1245 if flags_ptr.is_null() {
1246 return;
1247 }
1248 unsafe {
1250 (*flags_ptr).lock().callback = true;
1251 }
1252}
1253
1254unsafe extern "C" fn host_request_restart(_host: *const ClapHost) {
1255 if _host.is_null() {
1256 return;
1257 }
1258 let flags_ptr = unsafe { (*_host).host_data as *mut UnsafeMutex<HostCallbackFlags> };
1260 if flags_ptr.is_null() {
1261 return;
1262 }
1263 unsafe {
1265 (*flags_ptr).lock().restart = true;
1266 }
1267}
1268
1269unsafe extern "C" fn host_is_main_thread(_host: *const ClapHost) -> bool {
1270 true
1271}
1272
1273unsafe extern "C" fn host_is_audio_thread(_host: *const ClapHost) -> bool {
1274 true
1275}
1276
1277unsafe extern "C" fn host_latency_changed(_host: *const ClapHost) {}
1278
1279unsafe extern "C" fn host_tail_changed(_host: *const ClapHost) {}
1280
1281unsafe extern "C" fn host_timer_register(
1282 _host: *const ClapHost,
1283 _period_ms: u32,
1284 timer_id: *mut u32,
1285) -> bool {
1286 if timer_id.is_null() {
1287 return false;
1288 }
1289 let id = NEXT_TIMER_ID.fetch_add(1, Ordering::Relaxed);
1290 unsafe {
1292 *timer_id = id;
1293 }
1294 true
1295}
1296
1297unsafe extern "C" fn host_timer_unregister(_host: *const ClapHost, _timer_id: u32) -> bool {
1298 true
1299}
1300
1301unsafe extern "C" fn input_events_size(_list: *const ClapInputEvents) -> u32 {
1302 if _list.is_null() {
1303 return 0;
1304 }
1305 let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
1307 if ctx.is_null() {
1308 return 0;
1309 }
1310 unsafe { (*ctx).events.len() as u32 }
1312}
1313
1314unsafe extern "C" fn input_events_get(
1315 _list: *const ClapInputEvents,
1316 _index: u32,
1317) -> *const ClapEventHeader {
1318 if _list.is_null() {
1319 return std::ptr::null();
1320 }
1321 let ctx = unsafe { (*_list).ctx as *const ClapInputEventsCtx };
1323 if ctx.is_null() {
1324 return std::ptr::null();
1325 }
1326 let events = unsafe { &(*ctx).events };
1328 let Some(event) = events.get(_index as usize) else {
1329 return std::ptr::null();
1330 };
1331 event.header_ptr()
1332}
1333
1334unsafe extern "C" fn output_events_try_push(
1335 _list: *const ClapOutputEvents,
1336 _event: *const ClapEventHeader,
1337) -> bool {
1338 if _list.is_null() || _event.is_null() {
1339 return false;
1340 }
1341 let ctx = unsafe { (*_list).ctx as *mut ClapOutputEventsCtx };
1343 if ctx.is_null() {
1344 return false;
1345 }
1346 let header = unsafe { &*_event };
1348 if header.space_id != CLAP_CORE_EVENT_SPACE_ID {
1349 return false;
1350 }
1351 match header.type_ {
1352 CLAP_EVENT_MIDI => {
1353 if (header.size as usize) < std::mem::size_of::<ClapEventMidi>() {
1354 return false;
1355 }
1356 let midi = unsafe { &*(_event as *const ClapEventMidi) };
1358 unsafe {
1360 (*ctx).midi_events.push(ClapMidiOutputEvent {
1361 port: midi.port_index as usize,
1362 event: MidiEvent::new(header.time, midi.data.to_vec()),
1363 });
1364 }
1365 true
1366 }
1367 CLAP_EVENT_PARAM_VALUE => {
1368 if (header.size as usize) < std::mem::size_of::<ClapEventParamValue>() {
1369 return false;
1370 }
1371 let param = unsafe { &*(_event as *const ClapEventParamValue) };
1373 unsafe {
1375 (*ctx).param_values.push(PendingParamValue {
1376 param_id: param.param_id,
1377 value: param.value,
1378 });
1379 }
1380 true
1381 }
1382 _ => false,
1383 }
1384}
1385
1386fn input_events_from(
1387 midi_events: &[MidiEvent],
1388 param_events: &[PendingParamEvent],
1389 sample_rate: f64,
1390 transport: ClapTransportInfo,
1391) -> (ClapInputEvents, Box<ClapInputEventsCtx>) {
1392 let mut events = Vec::with_capacity(midi_events.len() + param_events.len() + 1);
1393 let bpm = transport.bpm.max(1.0);
1394 let sample_rate = sample_rate.max(1.0);
1395 let seconds = transport.transport_sample as f64 / sample_rate;
1396 let song_pos_seconds = (seconds * CLAP_SECTIME_FACTOR as f64) as i64;
1397 let beats = seconds * (bpm / 60.0);
1398 let song_pos_beats = (beats * CLAP_BEATTIME_FACTOR as f64) as i64;
1399 let mut flags = CLAP_TRANSPORT_HAS_TEMPO
1400 | CLAP_TRANSPORT_HAS_BEATS_TIMELINE
1401 | CLAP_TRANSPORT_HAS_SECONDS_TIMELINE
1402 | CLAP_TRANSPORT_HAS_TIME_SIGNATURE;
1403 if transport.playing {
1404 flags |= CLAP_TRANSPORT_IS_PLAYING;
1405 }
1406 let (loop_start_seconds, loop_end_seconds, loop_start_beats, loop_end_beats) =
1407 if transport.loop_enabled {
1408 if let Some((loop_start, loop_end)) = transport.loop_range_samples {
1409 flags |= CLAP_TRANSPORT_IS_LOOP_ACTIVE;
1410 let ls_sec = loop_start as f64 / sample_rate;
1411 let le_sec = loop_end as f64 / sample_rate;
1412 let ls_beats = ls_sec * (bpm / 60.0);
1413 let le_beats = le_sec * (bpm / 60.0);
1414 (
1415 (ls_sec * CLAP_SECTIME_FACTOR as f64) as i64,
1416 (le_sec * CLAP_SECTIME_FACTOR as f64) as i64,
1417 (ls_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
1418 (le_beats * CLAP_BEATTIME_FACTOR as f64) as i64,
1419 )
1420 } else {
1421 (0, 0, 0, 0)
1422 }
1423 } else {
1424 (0, 0, 0, 0)
1425 };
1426 let ts_num = transport.tsig_num.max(1);
1427 let ts_denom = transport.tsig_denom.max(1);
1428 let beats_per_bar = ts_num as f64 * (4.0 / ts_denom as f64);
1429 let bar_number = if beats_per_bar > 0.0 {
1430 (beats / beats_per_bar).floor().max(0.0) as i32
1431 } else {
1432 0
1433 };
1434 let bar_start_beats = (bar_number as f64 * beats_per_bar * CLAP_BEATTIME_FACTOR as f64) as i64;
1435 events.push(ClapInputEvent::Transport(ClapEventTransport {
1436 header: ClapEventHeader {
1437 size: std::mem::size_of::<ClapEventTransport>() as u32,
1438 time: 0,
1439 space_id: CLAP_CORE_EVENT_SPACE_ID,
1440 type_: CLAP_EVENT_TRANSPORT,
1441 flags: 0,
1442 },
1443 flags,
1444 song_pos_beats,
1445 song_pos_seconds,
1446 tempo: bpm,
1447 tempo_inc: 0.0,
1448 loop_start_beats,
1449 loop_end_beats,
1450 loop_start_seconds,
1451 loop_end_seconds,
1452 bar_start: bar_start_beats,
1453 bar_number,
1454 tsig_num: ts_num,
1455 tsig_denom: ts_denom,
1456 }));
1457 for event in midi_events {
1458 if event.data.is_empty() {
1459 continue;
1460 }
1461 let mut data = [0_u8; 3];
1462 let bytes = event.data.len().min(3);
1463 data[..bytes].copy_from_slice(&event.data[..bytes]);
1464 events.push(ClapInputEvent::Midi(ClapEventMidi {
1465 header: ClapEventHeader {
1466 size: std::mem::size_of::<ClapEventMidi>() as u32,
1467 time: event.frame,
1468 space_id: CLAP_CORE_EVENT_SPACE_ID,
1469 type_: CLAP_EVENT_MIDI,
1470 flags: 0,
1471 },
1472 port_index: 0,
1473 data,
1474 }));
1475 }
1476 for param in param_events {
1477 match *param {
1478 PendingParamEvent::Value {
1479 param_id,
1480 value,
1481 frame,
1482 } => events.push(ClapInputEvent::ParamValue(ClapEventParamValue {
1483 header: ClapEventHeader {
1484 size: std::mem::size_of::<ClapEventParamValue>() as u32,
1485 time: frame,
1486 space_id: CLAP_CORE_EVENT_SPACE_ID,
1487 type_: CLAP_EVENT_PARAM_VALUE,
1488 flags: 0,
1489 },
1490 param_id,
1491 cookie: std::ptr::null_mut(),
1492 note_id: -1,
1493 port_index: -1,
1494 channel: -1,
1495 key: -1,
1496 value,
1497 })),
1498 PendingParamEvent::GestureBegin { param_id, frame } => {
1499 events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
1500 header: ClapEventHeader {
1501 size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1502 time: frame,
1503 space_id: CLAP_CORE_EVENT_SPACE_ID,
1504 type_: CLAP_EVENT_PARAM_GESTURE_BEGIN,
1505 flags: 0,
1506 },
1507 param_id,
1508 }))
1509 }
1510 PendingParamEvent::GestureEnd { param_id, frame } => {
1511 events.push(ClapInputEvent::ParamGesture(ClapEventParamGesture {
1512 header: ClapEventHeader {
1513 size: std::mem::size_of::<ClapEventParamGesture>() as u32,
1514 time: frame,
1515 space_id: CLAP_CORE_EVENT_SPACE_ID,
1516 type_: CLAP_EVENT_PARAM_GESTURE_END,
1517 flags: 0,
1518 },
1519 param_id,
1520 }))
1521 }
1522 }
1523 }
1524 events.sort_by_key(|event| match event {
1525 ClapInputEvent::Midi(e) => e.header.time,
1526 ClapInputEvent::ParamValue(e) => e.header.time,
1527 ClapInputEvent::ParamGesture(e) => e.header.time,
1528 ClapInputEvent::Transport(e) => e.header.time,
1529 });
1530 let mut ctx = Box::new(ClapInputEventsCtx { events });
1531 let list = ClapInputEvents {
1532 ctx: (&mut *ctx as *mut ClapInputEventsCtx).cast::<c_void>(),
1533 size: Some(input_events_size),
1534 get: Some(input_events_get),
1535 };
1536 (list, ctx)
1537}
1538
1539fn output_events_ctx(capacity: usize) -> (ClapOutputEvents, Box<ClapOutputEventsCtx>) {
1540 let mut ctx = Box::new(ClapOutputEventsCtx {
1541 midi_events: Vec::with_capacity(capacity),
1542 param_values: Vec::with_capacity(capacity / 2),
1543 });
1544 let list = ClapOutputEvents {
1545 ctx: (&mut *ctx as *mut ClapOutputEventsCtx).cast::<c_void>(),
1546 try_push: Some(output_events_try_push),
1547 };
1548 (list, ctx)
1549}
1550
1551fn c_char_buf_to_string<const N: usize>(buf: &[c_char; N]) -> String {
1552 let bytes = buf
1553 .iter()
1554 .take_while(|&&b| b != 0)
1555 .map(|&b| b as u8)
1556 .collect::<Vec<u8>>();
1557 String::from_utf8_lossy(&bytes).to_string()
1558}
1559
1560fn split_plugin_spec(spec: &str) -> (&str, Option<&str>) {
1561 if let Some((path, id)) = spec.split_once("::")
1562 && !id.trim().is_empty()
1563 {
1564 return (path, Some(id.trim()));
1565 }
1566 (spec, None)
1567}
1568
1569unsafe extern "C" fn clap_ostream_write(
1570 stream: *const ClapOStream,
1571 buffer: *const c_void,
1572 size: u64,
1573) -> i64 {
1574 if stream.is_null() || buffer.is_null() {
1575 return -1;
1576 }
1577 let ctx = unsafe { (*stream).ctx as *mut Vec<u8> };
1579 if ctx.is_null() {
1580 return -1;
1581 }
1582 let n = (size as usize).min(isize::MAX as usize);
1583 let src = unsafe { std::slice::from_raw_parts(buffer.cast::<u8>(), n) };
1585 unsafe {
1587 (*ctx).extend_from_slice(src);
1588 }
1589 n as i64
1590}
1591
1592unsafe extern "C" fn clap_istream_read(
1593 stream: *const ClapIStream,
1594 buffer: *mut c_void,
1595 size: u64,
1596) -> i64 {
1597 if stream.is_null() || buffer.is_null() {
1598 return -1;
1599 }
1600 let ctx = unsafe { (*stream).ctx as *mut ClapIStreamCtx<'_> };
1602 if ctx.is_null() {
1603 return -1;
1604 }
1605 let ctx = unsafe { &mut *ctx };
1607 let remaining = ctx.bytes.len().saturating_sub(ctx.offset);
1608 if remaining == 0 {
1609 return 0;
1610 }
1611 let n = remaining.min(size as usize);
1612 let dst = unsafe { std::slice::from_raw_parts_mut(buffer.cast::<u8>(), n) };
1614 dst.copy_from_slice(&ctx.bytes[ctx.offset..ctx.offset + n]);
1615 ctx.offset += n;
1616 n as i64
1617}
1618
1619pub fn list_plugins() -> Vec<ClapPluginInfo> {
1620 list_plugins_with_capabilities(false)
1621}
1622
1623pub fn list_plugins_with_capabilities(scan_capabilities: bool) -> Vec<ClapPluginInfo> {
1624 let mut roots = default_clap_search_roots();
1625
1626 if let Ok(extra) = std::env::var("CLAP_PATH") {
1627 for p in std::env::split_paths(&extra) {
1628 if !p.as_os_str().is_empty() {
1629 roots.push(p);
1630 }
1631 }
1632 }
1633
1634 let mut out = Vec::new();
1635 for root in roots {
1636 collect_clap_plugins(&root, &mut out, scan_capabilities);
1637 }
1638
1639 out.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
1640 out.dedup_by(|a, b| a.path.eq_ignore_ascii_case(&b.path));
1641 out
1642}
1643
1644fn collect_clap_plugins(root: &Path, out: &mut Vec<ClapPluginInfo>, scan_capabilities: bool) {
1645 let Ok(entries) = std::fs::read_dir(root) else {
1646 return;
1647 };
1648 for entry in entries.flatten() {
1649 let path = entry.path();
1650 let Ok(ft) = entry.file_type() else {
1651 continue;
1652 };
1653 if ft.is_symlink() {
1654 continue;
1655 }
1656
1657 if ft.is_dir() {
1658 collect_clap_plugins(&path, out, scan_capabilities);
1659 continue;
1660 }
1661
1662 if path
1663 .extension()
1664 .is_some_and(|ext| ext.eq_ignore_ascii_case("clap"))
1665 {
1666 let infos = scan_bundle_descriptors(&path, scan_capabilities);
1667 if infos.is_empty() {
1668 let name = path
1669 .file_stem()
1670 .map(|s| s.to_string_lossy().to_string())
1671 .unwrap_or_else(|| path.to_string_lossy().to_string());
1672 out.push(ClapPluginInfo {
1673 name,
1674 path: path.to_string_lossy().to_string(),
1675 capabilities: None,
1676 });
1677 } else {
1678 out.extend(infos);
1679 }
1680 }
1681 }
1682}
1683
1684fn scan_bundle_descriptors(path: &Path, scan_capabilities: bool) -> Vec<ClapPluginInfo> {
1685 let path_str = path.to_string_lossy().to_string();
1686 let factory_id = c"clap.plugin-factory";
1687 let host_runtime = match HostRuntime::new() {
1688 Ok(runtime) => runtime,
1689 Err(_) => return Vec::new(),
1690 };
1691 let library = match unsafe { Library::new(path) } {
1693 Ok(lib) => lib,
1694 Err(_) => return Vec::new(),
1695 };
1696 let entry_ptr = unsafe {
1698 match library.get::<*const ClapPluginEntry>(b"clap_entry\0") {
1699 Ok(sym) => *sym,
1700 Err(_) => return Vec::new(),
1701 }
1702 };
1703 if entry_ptr.is_null() {
1704 return Vec::new();
1705 }
1706 let entry = unsafe { &*entry_ptr };
1708 let Some(init) = entry.init else {
1709 return Vec::new();
1710 };
1711 let host_ptr = &host_runtime.host;
1712 if unsafe { !init(host_ptr) } {
1714 return Vec::new();
1715 }
1716 let mut out = Vec::new();
1717 if let Some(get_factory) = entry.get_factory {
1718 let factory = unsafe { get_factory(factory_id.as_ptr()) } as *const ClapPluginFactory;
1720 if !factory.is_null() {
1721 let factory_ref = unsafe { &*factory };
1723 if let (Some(get_count), Some(get_desc)) = (
1724 factory_ref.get_plugin_count,
1725 factory_ref.get_plugin_descriptor,
1726 ) {
1727 let count = unsafe { get_count(factory) };
1729 for i in 0..count {
1730 let desc = unsafe { get_desc(factory, i) };
1732 if desc.is_null() {
1733 continue;
1734 }
1735 let desc = unsafe { &*desc };
1737 if desc.id.is_null() || desc.name.is_null() {
1738 continue;
1739 }
1740 let id = unsafe { CStr::from_ptr(desc.id) }
1742 .to_string_lossy()
1743 .to_string();
1744 let name = unsafe { CStr::from_ptr(desc.name) }
1746 .to_string_lossy()
1747 .to_string();
1748
1749 let capabilities = if scan_capabilities {
1750 scan_plugin_capabilities(factory_ref, factory, &host_runtime.host, &id)
1751 } else {
1752 None
1753 };
1754
1755 out.push(ClapPluginInfo {
1756 name,
1757 path: format!("{path_str}::{id}"),
1758 capabilities,
1759 });
1760 }
1761 }
1762 }
1763 }
1764 if let Some(deinit) = entry.deinit {
1766 unsafe { deinit() };
1767 }
1768 out
1769}
1770
1771fn scan_plugin_capabilities(
1772 factory: &ClapPluginFactory,
1773 factory_ptr: *const ClapPluginFactory,
1774 host: &ClapHost,
1775 plugin_id: &str,
1776) -> Option<ClapPluginCapabilities> {
1777 let create = factory.create_plugin?;
1778
1779 let id_cstring = CString::new(plugin_id).ok()?;
1780 let plugin = unsafe { create(factory_ptr, host, id_cstring.as_ptr()) };
1782 if plugin.is_null() {
1783 return None;
1784 }
1785
1786 let plugin_ref = unsafe { &*plugin };
1788 let plugin_init = plugin_ref.init?;
1789
1790 if unsafe { !plugin_init(plugin) } {
1792 return None;
1793 }
1794
1795 let mut capabilities = ClapPluginCapabilities {
1796 has_gui: false,
1797 gui_apis: Vec::new(),
1798 supports_embedded: false,
1799 supports_floating: false,
1800 has_params: false,
1801 has_state: false,
1802 has_audio_ports: false,
1803 has_note_ports: false,
1804 };
1805
1806 if let Some(get_extension) = plugin_ref.get_extension {
1808 let gui_ext_id = c"clap.gui";
1810 let gui_ptr = unsafe { get_extension(plugin, gui_ext_id.as_ptr()) };
1812 if !gui_ptr.is_null() {
1813 capabilities.has_gui = true;
1814 let gui = unsafe { &*(gui_ptr as *const ClapPluginGui) };
1816
1817 if let Some(is_api_supported) = gui.is_api_supported {
1819 for api in ["x11", "cocoa"] {
1820 if let Ok(api_cstr) = CString::new(api) {
1821 if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), false) } {
1824 capabilities.gui_apis.push(format!("{} (embedded)", api));
1825 capabilities.supports_embedded = true;
1826 }
1827 if unsafe { is_api_supported(plugin, api_cstr.as_ptr(), true) } {
1830 if !capabilities.supports_embedded {
1831 capabilities.gui_apis.push(format!("{} (floating)", api));
1832 }
1833 capabilities.supports_floating = true;
1834 }
1835 }
1836 }
1837 }
1838 }
1839
1840 let params_ext_id = c"clap.params";
1842 let params_ptr = unsafe { get_extension(plugin, params_ext_id.as_ptr()) };
1844 capabilities.has_params = !params_ptr.is_null();
1845
1846 let state_ext_id = c"clap.state";
1848 let state_ptr = unsafe { get_extension(plugin, state_ext_id.as_ptr()) };
1850 capabilities.has_state = !state_ptr.is_null();
1851
1852 let audio_ports_ext_id = c"clap.audio-ports";
1854 let audio_ports_ptr = unsafe { get_extension(plugin, audio_ports_ext_id.as_ptr()) };
1856 capabilities.has_audio_ports = !audio_ports_ptr.is_null();
1857
1858 let note_ports_ext_id = c"clap.note-ports";
1860 let note_ports_ptr = unsafe { get_extension(plugin, note_ports_ext_id.as_ptr()) };
1862 capabilities.has_note_ports = !note_ports_ptr.is_null();
1863 }
1864
1865 if let Some(destroy) = plugin_ref.destroy {
1867 unsafe { destroy(plugin) };
1869 }
1870
1871 Some(capabilities)
1872}
1873
1874fn default_clap_search_roots() -> Vec<PathBuf> {
1875 let mut roots = Vec::new();
1876
1877 #[cfg(target_os = "macos")]
1878 {
1879 paths::push_macos_audio_plugin_roots(&mut roots, "CLAP");
1880 }
1881
1882 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
1883 {
1884 paths::push_unix_plugin_roots(&mut roots, "clap");
1885 }
1886
1887 roots
1888}