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