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