1use crate::audio::io::AudioIO;
2use crate::midi::io::{MIDIIO, MidiEvent};
3use crate::mutex::UnsafeMutex;
4use crate::plugins::ipc;
5use crate::plugins::types::{
6 ClapMidiOutputEvent, ClapParamUpdate, ClapParameterInfo, ClapTransportInfo,
7};
8use maolan_plugin_protocol::events::EventPair;
9use maolan_plugin_protocol::protocol::*;
10use maolan_plugin_protocol::ringbuf::RingBuffer;
11use maolan_plugin_protocol::shm::ShmMapping;
12use std::collections::HashMap;
13use std::path::PathBuf;
14use std::process::{Child, ChildStderr};
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::{Arc, atomic::AtomicU32};
17use std::time::{Duration, Instant};
18
19fn wait_for_host_request_complete(
20 header: &ShmHeader,
21 events: &EventPair,
22 timeout: Duration,
23) -> Result<(), String> {
24 let start = Instant::now();
25 loop {
26 if header.request_type.load(Ordering::Acquire) == 0
30 || header.request_status.load(Ordering::Acquire) != 0
31 {
32 return Ok(());
33 }
34 let elapsed = start.elapsed();
35 if elapsed >= timeout {
36 return Err("Host did not respond to request".to_string());
37 }
38 if let Err(e) = events.wait_host(timeout - elapsed) {
39 return Err(format!("Host did not respond to request: {e}"));
40 }
41 }
42}
43
44pub struct ClapProcessor {
45 path: String,
46 plugin_id: String,
47 name: String,
48 audio_inputs: Vec<Arc<AudioIO>>,
49 audio_outputs: Vec<Arc<AudioIO>>,
50 main_audio_inputs: usize,
51 main_audio_outputs: usize,
52 midi_input_count: usize,
53 midi_output_count: usize,
54 midi_input_ports: Vec<Arc<UnsafeMutex<Box<MIDIIO>>>>,
55 midi_output_ports: Vec<Arc<UnsafeMutex<Box<MIDIIO>>>>,
56 param_infos: Vec<ClapParameterInfo>,
57 param_values: UnsafeMutex<HashMap<u32, f64>>,
58 bypassed: Arc<AtomicBool>,
59
60 child: UnsafeMutex<Option<Child>>,
61 stderr: UnsafeMutex<Option<ChildStderr>>,
62 mapping: Option<ShmMapping>,
63 events: Option<EventPair>,
64 shm_name: String,
65
66 crash_count: AtomicU32,
67 last_process_time: UnsafeMutex<Instant>,
68}
69
70pub type SharedClapProcessor = Arc<UnsafeMutex<ClapProcessor>>;
71
72impl ClapProcessor {
73 pub fn new(
74 _sample_rate: f64,
75 buffer_size: usize,
76 plugin_spec: &str,
77 input_count: usize,
78 output_count: usize,
79 host_binary: PathBuf,
80 ) -> Result<Self, String> {
81 let (plugin_path, plugin_id) = split_plugin_spec(plugin_spec);
82
83 let instance_id = ipc::unique_instance_id("clap");
84 let plugin_spec = if plugin_id.is_empty() {
85 plugin_path.to_string()
86 } else {
87 format!("{plugin_path}::{plugin_id}")
88 };
89 let (mut child, mapping, events, shm_name, stderr) = ipc::spawn_host(ipc::HostSpawnArgs {
90 host_binary: &host_binary,
91 format: "clap",
92 plugin_spec: &plugin_spec,
93 instance_id: &instance_id,
94 extra_args: &[],
95 })?;
96
97 let header = unsafe { header_ref(mapping.as_ptr()) };
98 if !ipc::wait_for_ready(header, Duration::from_secs(10)) {
99 let _ = child.kill();
100 return Err("host did not signal ready".to_string());
101 }
102
103 let name = unsafe {
104 maolan_plugin_protocol::protocol::read_plugin_name_from_scratch(mapping.as_ptr())
105 .unwrap_or_else(|| plugin_id.to_string())
106 };
107
108 let (actual_audio_in, actual_audio_out, actual_midi_in, actual_midi_out) = unsafe {
109 let counts =
110 maolan_plugin_protocol::protocol::read_port_counts_from_scratch(mapping.as_ptr());
111
112 counts.unwrap_or((input_count as u32, output_count as u32, 0, 0))
113 };
114
115 let audio_inputs = (0..actual_audio_in as usize)
116 .map(|_| Arc::new(AudioIO::new(buffer_size)))
117 .collect::<Vec<_>>();
118 let audio_outputs = (0..actual_audio_out as usize)
119 .map(|_| Arc::new(AudioIO::new(buffer_size)))
120 .collect::<Vec<_>>();
121 let midi_input_ports = (0..actual_midi_in as usize)
122 .map(|_| Arc::new(UnsafeMutex::new(Box::new(MIDIIO::new()))))
123 .collect::<Vec<_>>();
124 let midi_output_ports = (0..actual_midi_out as usize)
125 .map(|_| Arc::new(UnsafeMutex::new(Box::new(MIDIIO::new()))))
126 .collect::<Vec<_>>();
127
128 let param_infos = Vec::new();
129
130 Ok(Self {
131 path: plugin_spec.to_string(),
132 plugin_id: plugin_id.to_string(),
133 name,
134 audio_inputs,
135 audio_outputs,
136 main_audio_inputs: actual_audio_in as usize,
137 main_audio_outputs: actual_audio_out as usize,
138 midi_input_count: actual_midi_in as usize,
139 midi_output_count: actual_midi_out as usize,
140 midi_input_ports,
141 midi_output_ports,
142 param_infos,
143 param_values: UnsafeMutex::new(HashMap::new()),
144 bypassed: Arc::new(AtomicBool::new(false)),
145 child: UnsafeMutex::new(Some(child)),
146 stderr: UnsafeMutex::new(stderr),
147 mapping: Some(mapping),
148 events: Some(events),
149 shm_name,
150 crash_count: AtomicU32::new(0),
151 last_process_time: UnsafeMutex::new(Instant::now()),
152 })
153 }
154
155 pub fn setup_audio_ports(&self) {
156 for port in &self.audio_inputs {
157 port.setup();
158 }
159 for port in &self.audio_outputs {
160 port.setup();
161 }
162 }
163
164 pub fn setup_midi_ports(&self) {
165 for port in &self.midi_input_ports {
166 port.lock().setup();
167 }
168 for port in &self.midi_output_ports {
169 port.lock().setup();
170 }
171 }
172
173 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
174 &self.audio_inputs
175 }
176
177 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
178 &self.audio_outputs
179 }
180
181 pub fn main_audio_input_count(&self) -> usize {
182 self.main_audio_inputs
183 }
184
185 pub fn main_audio_output_count(&self) -> usize {
186 self.main_audio_outputs
187 }
188
189 pub fn midi_input_count(&self) -> usize {
190 self.midi_input_count
191 }
192
193 pub fn midi_output_count(&self) -> usize {
194 self.midi_output_count
195 }
196
197 pub fn midi_input_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
198 &self.midi_input_ports
199 }
200
201 pub fn midi_output_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
202 &self.midi_output_ports
203 }
204
205 pub fn set_bypassed(&self, bypassed: bool) {
206 self.bypassed.store(bypassed, Ordering::Relaxed);
207 }
208
209 pub fn is_bypassed(&self) -> bool {
210 self.bypassed.load(Ordering::Relaxed)
211 }
212
213 pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
214 self.param_infos.clone()
215 }
216
217 pub fn parameter_values(&self) -> HashMap<u32, f64> {
218 self.param_values.lock().clone()
219 }
220
221 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
222 self.set_parameter_at(param_id, value, 0)
223 }
224
225 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
226 self.param_values.lock().insert(param_id, value);
227
228 if let Some(ref mapping) = self.mapping {
229 let ring = unsafe {
230 let buf = param_ring_ptr(mapping.as_ptr());
231 let (w, r) = param_indices(mapping.as_ptr());
232 RingBuffer::new(buf, w, r, RING_CAPACITY)
233 };
234 let ev = ParameterEvent {
235 param_index: param_id,
236 value: value as f32,
237 sample_offset: 0,
238 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
239 };
240 if !ring.push(ev) {}
241 }
242 Ok(())
243 }
244
245 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
246 Ok(())
247 }
248
249 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
250 Ok(())
251 }
252
253 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
254 false
255 }
256
257 pub fn take_state_dirty(&self) -> bool {
258 let header = match self.mapping.as_ref() {
259 Some(m) => unsafe { header_mut(m.as_ptr()) },
260 None => return false,
261 };
262 header.state_dirty.swap(0, Ordering::Acquire) != 0
263 }
264
265 pub fn snapshot_state(&self) -> Result<crate::plugins::types::ClapPluginState, String> {
266 let (mapping, events) = match (&self.mapping, &self.events) {
267 (Some(m), Some(e)) => (m, e),
268 _ => return Err("CLAP processor not initialized".to_string()),
269 };
270 let ptr = mapping.as_ptr();
271 let header = unsafe { header_mut(ptr) };
272
273 header.request_type.store(1, Ordering::Release);
274 header.request_status.store(0, Ordering::Release);
275 if let Err(e) = events.signal_host() {
276 header.request_type.store(0, Ordering::Release);
277 return Err(format!("Failed to signal host for state save: {e}"));
278 }
279
280 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
281 header.request_type.store(0, Ordering::Release);
282 return Err(format!("Host did not respond to state save: {e}"));
283 }
284
285 let status = header.request_status.load(Ordering::Acquire);
286 let size = header.scratch_size.load(Ordering::Acquire) as usize;
287 if status != 1 {
288 header.request_type.store(0, Ordering::Release);
289 return Err("State save failed in host".to_string());
290 }
291 if size > SCRATCH_SIZE {
292 header.request_type.store(0, Ordering::Release);
293 return Err(format!("Host returned invalid CLAP state size: {size}"));
294 }
295
296 let scratch = unsafe { scratch_ptr(ptr) };
297 let mut bytes = vec![0u8; size];
298 unsafe {
299 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
300 }
301 header.request_type.store(0, Ordering::Release);
302 Ok(crate::plugins::types::ClapPluginState { bytes })
303 }
304
305 pub fn restore_state(
306 &self,
307 state: &crate::plugins::types::ClapPluginState,
308 ) -> Result<(), String> {
309 let (mapping, events) = match (&self.mapping, &self.events) {
310 (Some(m), Some(e)) => (m, e),
311 _ => return Err("CLAP processor not initialized".to_string()),
312 };
313 if state.bytes.len() > SCRATCH_SIZE {
314 return Err(format!(
315 "CLAP state is too large for scratch buffer: {} bytes",
316 state.bytes.len()
317 ));
318 }
319
320 let ptr = mapping.as_ptr();
321 let header = unsafe { header_mut(ptr) };
322 let scratch = unsafe { scratch_ptr(ptr) };
323 unsafe {
324 std::ptr::copy_nonoverlapping(state.bytes.as_ptr(), scratch, state.bytes.len());
325 }
326 header
327 .scratch_size
328 .store(state.bytes.len() as u32, Ordering::Release);
329
330 header.request_type.store(2, Ordering::Release);
331 header.request_status.store(0, Ordering::Release);
332 if let Err(e) = events.signal_host() {
333 header.request_type.store(0, Ordering::Release);
334 return Err(format!("Failed to signal host for state restore: {e}"));
335 }
336
337 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
338 header.request_type.store(0, Ordering::Release);
339 return Err(format!("Host did not respond to state restore: {e}"));
340 }
341
342 let status = header.request_status.load(Ordering::Acquire);
343 header.request_type.store(0, Ordering::Release);
344 if status != 1 {
345 return Err("State restore failed in host".to_string());
346 }
347 Ok(())
348 }
349
350 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
351 let (mapping, events) = match (&self.mapping, &self.events) {
352 (Some(m), Some(e)) => (m, e),
353 _ => return Err("CLAP processor not initialized".to_string()),
354 };
355 let ptr = mapping.as_ptr();
356 let header = unsafe { header_mut(ptr) };
357 let path_str = dir.to_string_lossy().to_string();
358 unsafe {
359 write_resource_directory_to_scratch(ptr, &path_str)
360 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
361 }
362 std::sync::atomic::fence(Ordering::SeqCst);
363
364 header.request_type.store(5, Ordering::Release);
365 header.request_status.store(0, Ordering::Release);
366 if let Err(e) = events.signal_host() {
367 header.request_type.store(0, Ordering::Release);
368 return Err(format!("Failed to signal host for resource directory: {e}"));
369 }
370
371 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
372 header.request_type.store(0, Ordering::Release);
373 return Err(format!("Host did not respond to resource directory: {e}"));
374 }
375
376 let status = header.request_status.load(Ordering::Acquire);
377 header.request_type.store(0, Ordering::Release);
378 if status != 1 {
379 return Err("Resource directory update failed in host".to_string());
380 }
381 Ok(())
382 }
383
384 pub fn file_references(
385 &self,
386 ) -> Result<Vec<maolan_plugin_protocol::protocol::FileReference>, String> {
387 let (mapping, events) = match (&self.mapping, &self.events) {
388 (Some(m), Some(e)) => (m, e),
389 _ => return Err("CLAP processor not initialized".to_string()),
390 };
391 let ptr = mapping.as_ptr();
392 let header = unsafe { header_mut(ptr) };
393
394 header.request_type.store(6, Ordering::Release);
395 header.request_status.store(0, Ordering::Release);
396 if let Err(e) = events.signal_host() {
397 header.request_type.store(0, Ordering::Release);
398 return Err(format!("Failed to signal host for file references: {e}"));
399 }
400
401 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
402 header.request_type.store(0, Ordering::Release);
403 return Err(format!("Host did not respond to file references: {e}"));
404 }
405
406 let status = header.request_status.load(Ordering::Acquire);
407 if status != 1 {
408 header.request_type.store(0, Ordering::Release);
409 return Err("File references enumeration failed in host".to_string());
410 }
411
412 let paths = unsafe { read_file_references_from_scratch(ptr) }
413 .ok_or("Failed to read file references from scratch")?;
414 header.request_type.store(0, Ordering::Release);
415 Ok(paths)
416 }
417
418 pub fn update_file_reference(&self, index: u32, path: &str) -> Result<(), String> {
419 let (mapping, events) = match (&self.mapping, &self.events) {
420 (Some(m), Some(e)) => (m, e),
421 _ => return Err("CLAP processor not initialized".to_string()),
422 };
423 let ptr = mapping.as_ptr();
424 let header = unsafe { header_mut(ptr) };
425 unsafe {
426 write_file_reference_update_to_scratch(ptr, index, path)
427 .map_err(|e| format!("Failed to write file-reference update: {e}"))?;
428 }
429
430 header.request_type.store(7, Ordering::Release);
431 header.request_status.store(0, Ordering::Release);
432 if let Err(e) = events.signal_host() {
433 header.request_type.store(0, Ordering::Release);
434 return Err(format!(
435 "Failed to signal host for file-reference update: {e}"
436 ));
437 }
438
439 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
440 header.request_type.store(0, Ordering::Release);
441 return Err(format!(
442 "Host did not respond to file-reference update: {e}"
443 ));
444 }
445
446 let status = header.request_status.load(Ordering::Acquire);
447 header.request_type.store(0, Ordering::Release);
448 if status != 1 {
449 return Err("File-reference update failed in host".to_string());
450 }
451 Ok(())
452 }
453
454 pub fn process_with_audio_io(&self, frames: usize) {
455 let _ = self.process_with_midi(frames, &[], ClapTransportInfo::default());
456 }
457
458 pub fn process_with_midi(
459 &self,
460 frames: usize,
461 midi_in: &[MidiEvent],
462 transport: ClapTransportInfo,
463 ) -> Vec<ClapMidiOutputEvent> {
464 if self.bypassed.load(Ordering::Relaxed) {
465 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
466 return Vec::new();
467 }
468
469 self.setup_midi_ports();
470
471 {
472 let child = self.child.lock();
473 if let Some(ref mut c) = child.as_mut() {
474 match c.try_wait() {
475 Ok(Some(status)) if !status.success() => {
476 self.crash_count.fetch_add(1, Ordering::Relaxed);
477 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
478 return Vec::new();
479 }
480 _ => {}
481 }
482 }
483 }
484
485 let (mapping, events) = match (&self.mapping, &self.events) {
486 (Some(m), Some(e)) => (m, e),
487 _ => {
488 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
489 return Vec::new();
490 }
491 };
492
493 let ptr = mapping.as_ptr();
494 unsafe {
495 ipc::configure_shm_header(
496 ptr,
497 frames,
498 self.audio_inputs.len(),
499 self.audio_outputs.len(),
500 self.midi_input_ports.len(),
501 self.midi_output_ports.len(),
502 );
503 ipc::copy_inputs_to_shm(&self.audio_inputs, ptr, frames);
504
505 let t = transport_mut(ptr);
506 t.playhead_sample = transport.transport_sample as u64;
507 t.tempo = transport.bpm;
508 t.numerator = transport.tsig_num as u32;
509 t.denominator = transport.tsig_denom as u32;
510 t.flags = if transport.playing { 1 } else { 0 };
511
512 if let Some(port0) = self.midi_input_ports.first() {
516 let port0_lock = port0.lock();
517 port0_lock.buffer.extend_from_slice(midi_in);
518 port0_lock.mark_finished();
519 }
520
521 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
522 let port_lock = port.lock();
523 let midi_buf = midi_in_ring_ptr(ptr, port_idx);
524 let (midi_w, midi_r) = midi_in_indices(ptr, port_idx);
525 let midi_ring = RingBuffer::new(midi_buf, midi_w, midi_r, RING_CAPACITY);
526 for ev in &port_lock.buffer {
527 let midi_event = maolan_plugin_protocol::protocol::MidiEvent {
528 sample_offset: ev.frame,
529 data: [
530 ev.data.first().copied().unwrap_or(0),
531 ev.data.get(1).copied().unwrap_or(0),
532 ev.data.get(2).copied().unwrap_or(0),
533 ],
534 channel: ev.data.first().map(|b| b & 0x0F).unwrap_or(0),
535 flags: 0,
536 _pad: 0,
537 };
538 if !midi_ring.push(midi_event) {
539 break;
540 }
541 }
542 }
543 }
544
545 if events.signal_host().is_err() {
546 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
547 return Vec::new();
548 }
549
550 let timeout = Duration::from_millis(100);
551 if events.wait_host(timeout).is_err() {
552 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
553 return Vec::new();
554 }
555
556 {
557 let child = self.child.lock();
558 if let Some(ref mut c) = child.as_mut()
559 && let Ok(Some(status)) = c.try_wait()
560 && !status.success()
561 {
562 self.crash_count.fetch_add(1, Ordering::Relaxed);
563 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
564 return Vec::new();
565 }
566 }
567
568 unsafe {
569 ipc::copy_outputs_from_shm(&self.audio_outputs, ptr, frames);
570 }
571
572 let mut midi_out = Vec::new();
573 unsafe {
574 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
575 let port_lock = port.lock();
576 port_lock.buffer.clear();
577 let midi_out_buf = midi_out_ring_ptr(ptr, port_idx);
578 let (midi_out_w, midi_out_r) = midi_out_indices(ptr, port_idx);
579 let midi_out_ring =
580 RingBuffer::new(midi_out_buf, midi_out_w, midi_out_r, RING_CAPACITY);
581 while let Some(ev) = midi_out_ring.pop() {
582 let event = crate::midi::io::MidiEvent::new(ev.sample_offset, ev.data.to_vec());
583 port_lock.buffer.push(event.clone());
584 midi_out.push(ClapMidiOutputEvent {
585 port: port_idx,
586 event,
587 });
588 }
589 port_lock.mark_finished();
590 }
591 }
592
593 *self.last_process_time.lock() = Instant::now();
594 midi_out
595 }
596
597 pub fn path(&self) -> &str {
598 &self.path
599 }
600
601 pub fn plugin_id(&self) -> &str {
602 &self.plugin_id
603 }
604
605 pub fn name(&self) -> &str {
606 &self.name
607 }
608
609 pub fn take_stderr(&self) -> Option<ChildStderr> {
610 self.stderr.lock().take()
611 }
612
613 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
614 Ok(())
615 }
616
617 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
618 Ok(())
619 }
620
621 pub fn run_host_callbacks_main_thread(&self) {}
622
623 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
624 Ok(false)
625 }
626
627 pub fn ui_begin_session(&self) {}
628 pub fn ui_end_session(&self) {}
629 pub fn ui_should_close(&self) -> bool {
630 false
631 }
632 pub fn ui_take_due_timers(&self) -> Vec<u32> {
633 Vec::new()
634 }
635 pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
636 Vec::new()
637 }
638 pub fn ui_take_state_update(&self) -> Option<crate::plugins::types::ClapPluginState> {
639 None
640 }
641
642 pub fn gui_info(&self) -> Result<crate::plugins::types::ClapGuiInfo, String> {
643 Err("GUI not yet supported for CLAP plugins".to_string())
644 }
645
646 pub fn gui_create(&self, _api: &str, _is_floating: bool) -> Result<(), String> {
647 Err("GUI not yet supported for CLAP plugins".to_string())
648 }
649
650 pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
651 Err("GUI not yet supported for CLAP plugins".to_string())
652 }
653
654 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
655 if let Some(ref mapping) = self.mapping {
656 let header = unsafe { header_mut(mapping.as_ptr()) };
657 header.set_parent_window(window);
658 return Ok(());
659 }
660 Err("No active host to set parent window".to_string())
661 }
662
663 pub fn gui_show(&self) -> Result<(), String> {
664 if let Some(ref mapping) = self.mapping
665 && let Some(ref events) = self.events
666 {
667 let header = unsafe { header_mut(mapping.as_ptr()) };
668 header.request_type.store(3, Ordering::Release);
669 let _ = events.signal_host();
670 return Ok(());
671 }
672 Err("No active host to show GUI".to_string())
673 }
674
675 pub fn gui_hide(&self) {
676 if let Some(ref mapping) = self.mapping
677 && let Some(ref events) = self.events
678 {
679 let header = unsafe { header_mut(mapping.as_ptr()) };
680 header.request_type.store(4, Ordering::Release);
681 let _ = events.signal_host();
682 }
683 }
684
685 pub fn gui_destroy(&self) {}
686
687 pub fn gui_on_main_thread(&self) {}
688
689 pub fn gui_on_timer(&self, _timer_id: u32) {}
690
691 pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
692 std::collections::HashMap::new()
693 }
694
695 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
696 let mut result = Vec::new();
697 if let Some(ref mapping) = self.mapping {
698 let ring = unsafe {
699 let buf = echo_ring_ptr(mapping.as_ptr());
700 let (w, r) = echo_indices(mapping.as_ptr());
701 RingBuffer::new(buf, w, r, RING_CAPACITY)
702 };
703 while let Some(ev) = ring.pop() {
704 result.push(ev);
705 }
706 }
707 result
708 }
709
710 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
711 let mut result = Vec::new();
712 if let Some(ref mapping) = self.mapping {
713 let ring = unsafe {
714 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
715 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
716 RingBuffer::new(buf, w, r, RING_CAPACITY)
717 };
718 while let Some(ev) = ring.pop() {
719 result.push(crate::midi::io::MidiEvent {
720 frame: ev.sample_offset,
721 data: ev.data.to_vec(),
722 });
723 }
724 }
725 result
726 }
727}
728
729impl Drop for ClapProcessor {
730 fn drop(&mut self) {
731 ipc::drop_host(&self.mapping, &self.events, &self.child, &self.shm_name);
732 }
733}
734
735crate::impl_ipc_processor_wrapper!(ClapProcessor);
736
737impl UnsafeMutex<ClapProcessor> {
738 pub fn process_with_midi(
739 &self,
740 frames: usize,
741 midi_events: &[MidiEvent],
742 transport: ClapTransportInfo,
743 ) -> Vec<ClapMidiOutputEvent> {
744 self.lock()
745 .process_with_midi(frames, midi_events, transport)
746 }
747
748 pub fn is_bypassed(&self) -> bool {
749 self.lock().is_bypassed()
750 }
751
752 pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
753 self.lock().parameter_infos()
754 }
755
756 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
757 self.lock().set_parameter(param_id, value)
758 }
759
760 pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
761 self.lock().set_parameter_at(param_id, value, frame)
762 }
763
764 pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
765 self.lock().begin_parameter_edit_at(param_id, frame)
766 }
767
768 pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
769 self.lock().end_parameter_edit_at(param_id, frame)
770 }
771
772 pub fn snapshot_state(&self) -> Result<crate::plugins::types::ClapPluginState, String> {
773 self.lock().snapshot_state()
774 }
775
776 pub fn restore_state(
777 &self,
778 state: &crate::plugins::types::ClapPluginState,
779 ) -> Result<(), String> {
780 self.lock().restore_state(state)
781 }
782
783 pub fn take_state_dirty(&self) -> bool {
784 self.lock().take_state_dirty()
785 }
786
787 pub fn path(&self) -> String {
788 self.lock().path().to_string()
789 }
790
791 pub fn plugin_id(&self) -> String {
792 self.lock().plugin_id().to_string()
793 }
794
795 pub fn ui_begin_session(&self) {
796 self.lock().ui_begin_session();
797 }
798
799 pub fn ui_end_session(&self) {
800 self.lock().ui_end_session();
801 }
802
803 pub fn ui_should_close(&self) -> bool {
804 self.lock().ui_should_close()
805 }
806
807 pub fn ui_take_due_timers(&self) -> Vec<u32> {
808 self.lock().ui_take_due_timers()
809 }
810
811 pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
812 self.lock().ui_take_param_updates()
813 }
814
815 pub fn ui_take_state_update(&self) -> Option<crate::plugins::types::ClapPluginState> {
816 self.lock().ui_take_state_update()
817 }
818
819 pub fn gui_info(&self) -> Result<crate::plugins::types::ClapGuiInfo, String> {
820 self.lock().gui_info()
821 }
822
823 pub fn gui_create(&self, api: &str, is_floating: bool) -> Result<(), String> {
824 self.lock().gui_create(api, is_floating)
825 }
826
827 pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
828 self.lock().gui_get_size()
829 }
830
831 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
832 self.lock().gui_set_parent_x11(window)
833 }
834
835 pub fn gui_show(&self) -> Result<(), String> {
836 self.lock().gui_show()
837 }
838
839 pub fn gui_hide(&self) {
840 self.lock().gui_hide();
841 }
842
843 pub fn gui_destroy(&self) {
844 self.lock().gui_destroy();
845 }
846
847 pub fn gui_on_main_thread(&self) {
848 self.lock().gui_on_main_thread();
849 }
850
851 pub fn gui_on_timer(&self, timer_id: u32) {
852 self.lock().gui_on_timer(timer_id);
853 }
854
855 pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
856 self.lock().note_names()
857 }
858}
859
860fn split_plugin_spec(spec: &str) -> (&str, &str) {
861 if let Some(pos) = spec.rfind("::") {
862 (&spec[..pos], &spec[pos + 2..])
863 } else if let Some(pos) = spec.rfind('#') {
864 (&spec[..pos], &spec[pos + 1..])
865 } else {
866 (spec, "")
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use super::*;
873
874 fn find_host_binary() -> PathBuf {
875 let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
876 let workspace_root = std::path::Path::new(&manifest)
877 .parent()
878 .unwrap()
879 .join("daw");
880 workspace_root
881 .join("target")
882 .join("debug")
883 .join("maolan-plugin-host")
884 }
885
886 #[test]
887 fn clap_processor_processes_audio() {
888 let host_bin = find_host_binary();
889 if !host_bin.exists() {
890 return;
891 }
892
893 let plugin_path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
894 .parent()
895 .unwrap()
896 .join("daw")
897 .join("plugin-host")
898 .join("tests")
899 .join("test_passthrough.clap");
900
901 if !plugin_path.exists() {
902 return;
903 }
904
905 let processor = ClapProcessor::new(
906 48000.0,
907 256,
908 &format!("{}#com.maolan.test.passthrough", plugin_path.display()),
909 2,
910 2,
911 host_bin,
912 )
913 .expect("should create processor");
914
915 processor.setup_audio_ports();
916
917 for (i, input) in processor.audio_inputs().iter().enumerate() {
918 let buf = input.buffer.lock();
919 for (j, sample) in buf.iter_mut().enumerate() {
920 *sample = (i * 1000 + j) as f32;
921 }
922 *input.finished.lock() = true;
923 }
924
925 processor.process_with_audio_io(256);
926
927 for output in processor.audio_outputs().iter() {
928 let buf = output.buffer.lock();
929 assert!(
930 buf.iter().any(|&s| s != 0.0),
931 "output buffer should contain non-zero samples"
932 );
933 }
934 }
935
936 #[test]
937 fn clap_processor_crash_bypass() {
938 let host_bin = find_host_binary();
939 if !host_bin.exists() {
940 return;
941 }
942
943 let processor = ClapProcessor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
944 .expect("should create processor for crash test");
945
946 processor.setup_audio_ports();
947
948 {
949 let buf = processor.audio_inputs()[0].buffer.lock();
950 buf.fill(1.0);
951 *processor.audio_inputs()[0].finished.lock() = true;
952 }
953
954 std::thread::sleep(std::time::Duration::from_millis(50));
956
957 processor.process_with_audio_io(256);
958
959 let out_buf = processor.audio_outputs()[0].buffer.lock();
960 assert!(
961 out_buf.iter().all(|&s| s == 1.0),
962 "after crash, output should be bypass copy of input"
963 );
964 }
965
966 #[test]
967 fn clap_track_integration() {
968 use crate::track::Track;
969
970 let host_bin = find_host_binary();
971 if !host_bin.exists() {
972 return;
973 }
974
975 let plugin_path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
976 .parent()
977 .unwrap()
978 .join("daw")
979 .join("plugin-host")
980 .join("tests")
981 .join("test_passthrough.clap");
982
983 if !plugin_path.exists() {
984 return;
985 }
986
987 let mut track = Track::new("test-track".to_string(), 2, 2, 0, 0, 256, 48000.0);
988
989 track
990 .load_clap_plugin(
991 &format!("{}::com.maolan.test.passthrough", plugin_path.display()),
992 None,
993 )
994 .expect("should load CLAP plugin on track");
995
996 assert_eq!(track.clap_plugins.len(), 1);
997
998 let processor = track.clap_plugins[0].processor.lock();
999 processor.setup_audio_ports();
1000
1001 for (i, input) in processor.audio_inputs().iter().enumerate() {
1002 let buf = input.buffer.lock();
1003 for (j, sample) in buf.iter_mut().enumerate() {
1004 *sample = (i * 1000 + j) as f32;
1005 }
1006 *input.finished.lock() = true;
1007 }
1008
1009 processor.process_with_audio_io(256);
1010
1011 for (ch, output) in processor.audio_outputs().iter().enumerate() {
1012 let buf = output.buffer.lock();
1013 assert!(
1014 buf.iter().any(|&s| s != 0.0),
1015 "plugin output ch={ch} should contain non-zero samples after CLAP processing"
1016 );
1017 }
1018 }
1019}