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