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