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