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