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