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 = Self::fetch_parameter_infos(&mapping, &events).unwrap_or_else(|e| {
129 tracing::warn!("Failed to fetch CLAP parameter infos: {e}");
130 Vec::new()
131 });
132
133 Ok(Self {
134 path: plugin_spec.to_string(),
135 plugin_id: plugin_id.to_string(),
136 name,
137 audio_inputs,
138 audio_outputs,
139 main_audio_inputs: actual_audio_in as usize,
140 main_audio_outputs: actual_audio_out as usize,
141 midi_input_count: actual_midi_in as usize,
142 midi_output_count: actual_midi_out as usize,
143 midi_input_ports,
144 midi_output_ports,
145 param_infos,
146 param_values: UnsafeMutex::new(HashMap::new()),
147 bypassed: Arc::new(AtomicBool::new(false)),
148 child: UnsafeMutex::new(Some(child)),
149 stderr: UnsafeMutex::new(stderr),
150 mapping: Some(mapping),
151 events: Some(events),
152 shm_name,
153 crash_count: AtomicU32::new(0),
154 last_process_time: UnsafeMutex::new(Instant::now()),
155 })
156 }
157
158 pub fn setup_audio_ports(&self) {
159 for port in &self.audio_inputs {
160 port.setup();
161 }
162 for port in &self.audio_outputs {
163 port.setup();
164 }
165 }
166
167 pub fn setup_midi_ports(&self) {
168 for port in &self.midi_input_ports {
169 port.lock().setup();
170 }
171 for port in &self.midi_output_ports {
172 port.lock().setup();
173 }
174 }
175
176 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
177 &self.audio_inputs
178 }
179
180 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
181 &self.audio_outputs
182 }
183
184 pub fn main_audio_input_count(&self) -> usize {
185 self.main_audio_inputs
186 }
187
188 pub fn main_audio_output_count(&self) -> usize {
189 self.main_audio_outputs
190 }
191
192 pub fn midi_input_count(&self) -> usize {
193 self.midi_input_count
194 }
195
196 pub fn midi_output_count(&self) -> usize {
197 self.midi_output_count
198 }
199
200 pub fn midi_input_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
201 &self.midi_input_ports
202 }
203
204 pub fn midi_output_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
205 &self.midi_output_ports
206 }
207
208 pub fn set_bypassed(&self, bypassed: bool) {
209 self.bypassed.store(bypassed, Ordering::Relaxed);
210 }
211
212 pub fn is_bypassed(&self) -> bool {
213 self.bypassed.load(Ordering::Relaxed)
214 }
215
216 pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
217 self.param_infos.clone()
218 }
219
220 fn fetch_parameter_infos(
221 mapping: &ShmMapping,
222 events: &EventPair,
223 ) -> Result<Vec<ClapParameterInfo>, String> {
224 let ptr = mapping.as_ptr();
225 let header = unsafe { header_mut(ptr) };
226
227 tracing::info!("CLAP fetch_parameter_infos: sending request to host");
228 header
229 .request_type
230 .store(REQUEST_CLAP_PARAMETERS, Ordering::Release);
231 header.request_status.store(0, Ordering::Release);
232 if let Err(e) = events.signal_host() {
233 header.request_type.store(0, Ordering::Release);
234 return Err(format!("Failed to signal host for CLAP parameters: {e}"));
235 }
236
237 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
238 header.request_type.store(0, Ordering::Release);
239 return Err(format!("Host did not respond to CLAP parameters: {e}"));
240 }
241
242 let status = header.request_status.load(Ordering::Acquire);
243 let size = header.scratch_size.load(Ordering::Acquire) as usize;
244 tracing::info!(status, size, "CLAP fetch_parameter_infos: host responded");
245 if status != 1 {
246 header.request_type.store(0, Ordering::Release);
247 return Err("CLAP parameter enumeration failed in host".to_string());
248 }
249
250 let scratch = unsafe { scratch_ptr(ptr) };
251 let result = Self::deserialize_clap_parameters(scratch, size);
252 match &result {
253 Ok(params) => tracing::info!(
254 count = params.len(),
255 "CLAP fetch_parameter_infos: deserialized"
256 ),
257 Err(e) => tracing::error!("CLAP fetch_parameter_infos: deserialize failed: {e}"),
258 }
259 header.request_type.store(0, Ordering::Release);
260 result
261 }
262
263 fn deserialize_clap_parameters(
264 scratch: *const u8,
265 size: usize,
266 ) -> Result<Vec<ClapParameterInfo>, String> {
267 if size < 4 {
268 return Err("scratch too small for CLAP parameters".to_string());
269 }
270 let mut offset = 0usize;
271
272 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
273 offset += 4;
274
275 let mut params = Vec::with_capacity(count);
276 for _ in 0..count {
277 if offset + 4 > size {
278 return Err("scratch underflow".to_string());
279 }
280 let id = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) };
281 offset += 4;
282
283 if offset + 4 > size {
284 return Err("scratch underflow".to_string());
285 }
286 let name_len =
287 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
288 offset += 4;
289 if offset + name_len > size {
290 return Err("scratch underflow".to_string());
291 }
292 let mut name_bytes = vec![0u8; name_len];
293 unsafe {
294 std::ptr::copy_nonoverlapping(
295 scratch.add(offset),
296 name_bytes.as_mut_ptr(),
297 name_len,
298 );
299 }
300 offset += name_len;
301 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
302
303 if offset + 4 > size {
304 return Err("scratch underflow".to_string());
305 }
306 let module_len =
307 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
308 offset += 4;
309 if offset + module_len > size {
310 return Err("scratch underflow".to_string());
311 }
312 let mut module_bytes = vec![0u8; module_len];
313 unsafe {
314 std::ptr::copy_nonoverlapping(
315 scratch.add(offset),
316 module_bytes.as_mut_ptr(),
317 module_len,
318 );
319 }
320 offset += module_len;
321 let module = String::from_utf8(module_bytes).map_err(|e| e.to_string())?;
322
323 if offset + 24 > size {
324 return Err("scratch underflow".to_string());
325 }
326 let min_value = f64::from_bits(unsafe {
327 std::ptr::read_unaligned(scratch.add(offset) as *const u64)
328 });
329 let max_value = f64::from_bits(unsafe {
330 std::ptr::read_unaligned(scratch.add(offset + 8) as *const u64)
331 });
332 let default_value = f64::from_bits(unsafe {
333 std::ptr::read_unaligned(scratch.add(offset + 16) as *const u64)
334 });
335 offset += 24;
336
337 params.push(ClapParameterInfo {
338 id,
339 name,
340 module,
341 min_value,
342 max_value,
343 default_value,
344 });
345 }
346
347 Ok(params)
348 }
349
350 pub fn parameter_values(&self) -> HashMap<u32, f64> {
351 self.param_values.lock().clone()
352 }
353
354 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
355 self.set_parameter_at(param_id, value, 0)
356 }
357
358 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
359 self.param_values.lock().insert(param_id, value);
360
361 if let Some(ref mapping) = self.mapping {
362 let ring = unsafe {
363 let buf = param_ring_ptr(mapping.as_ptr());
364 let (w, r) = param_indices(mapping.as_ptr());
365 RingBuffer::new(buf, w, r, RING_CAPACITY)
366 };
367 let ev = ParameterEvent {
368 param_index: param_id,
369 value: value as f32,
370 sample_offset: 0,
371 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
372 };
373 if !ring.push(ev) {}
374 }
375 Ok(())
376 }
377
378 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
379 Ok(())
380 }
381
382 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
383 Ok(())
384 }
385
386 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
387 false
388 }
389
390 pub fn take_state_dirty(&self) -> bool {
391 let header = match self.mapping.as_ref() {
392 Some(m) => unsafe { header_mut(m.as_ptr()) },
393 None => return false,
394 };
395 header.state_dirty.swap(0, Ordering::Acquire) != 0
396 }
397
398 pub fn snapshot_state(&self) -> Result<crate::plugins::types::ClapPluginState, String> {
399 let (mapping, events) = match (&self.mapping, &self.events) {
400 (Some(m), Some(e)) => (m, e),
401 _ => return Err("CLAP processor not initialized".to_string()),
402 };
403 let ptr = mapping.as_ptr();
404 let header = unsafe { header_mut(ptr) };
405
406 header.request_type.store(1, Ordering::Release);
407 header.request_status.store(0, Ordering::Release);
408 if let Err(e) = events.signal_host() {
409 header.request_type.store(0, Ordering::Release);
410 return Err(format!("Failed to signal host for state save: {e}"));
411 }
412
413 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
414 header.request_type.store(0, Ordering::Release);
415 return Err(format!("Host did not respond to state save: {e}"));
416 }
417
418 let status = header.request_status.load(Ordering::Acquire);
419 let size = header.scratch_size.load(Ordering::Acquire) as usize;
420 if status != 1 {
421 header.request_type.store(0, Ordering::Release);
422 return Err("State save failed in host".to_string());
423 }
424 if size > SCRATCH_SIZE {
425 header.request_type.store(0, Ordering::Release);
426 return Err(format!("Host returned invalid CLAP state size: {size}"));
427 }
428
429 let scratch = unsafe { scratch_ptr(ptr) };
430 let mut bytes = vec![0u8; size];
431 unsafe {
432 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
433 }
434 header.request_type.store(0, Ordering::Release);
435 Ok(crate::plugins::types::ClapPluginState { bytes })
436 }
437
438 pub fn restore_state(
439 &self,
440 state: &crate::plugins::types::ClapPluginState,
441 ) -> Result<(), String> {
442 let (mapping, events) = match (&self.mapping, &self.events) {
443 (Some(m), Some(e)) => (m, e),
444 _ => return Err("CLAP processor not initialized".to_string()),
445 };
446 if state.bytes.len() > SCRATCH_SIZE {
447 return Err(format!(
448 "CLAP state is too large for scratch buffer: {} bytes",
449 state.bytes.len()
450 ));
451 }
452
453 let ptr = mapping.as_ptr();
454 let header = unsafe { header_mut(ptr) };
455 let scratch = unsafe { scratch_ptr(ptr) };
456 unsafe {
457 std::ptr::copy_nonoverlapping(state.bytes.as_ptr(), scratch, state.bytes.len());
458 }
459 header
460 .scratch_size
461 .store(state.bytes.len() as u32, Ordering::Release);
462
463 header.request_type.store(2, Ordering::Release);
464 header.request_status.store(0, Ordering::Release);
465 if let Err(e) = events.signal_host() {
466 header.request_type.store(0, Ordering::Release);
467 return Err(format!("Failed to signal host for state restore: {e}"));
468 }
469
470 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
471 header.request_type.store(0, Ordering::Release);
472 return Err(format!("Host did not respond to state restore: {e}"));
473 }
474
475 let status = header.request_status.load(Ordering::Acquire);
476 header.request_type.store(0, Ordering::Release);
477 if status != 1 {
478 return Err("State restore failed in host".to_string());
479 }
480 Ok(())
481 }
482
483 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
484 let (mapping, events) = match (&self.mapping, &self.events) {
485 (Some(m), Some(e)) => (m, e),
486 _ => return Err("CLAP processor not initialized".to_string()),
487 };
488 let ptr = mapping.as_ptr();
489 let header = unsafe { header_mut(ptr) };
490 let path_str = dir.to_string_lossy().to_string();
491 unsafe {
492 write_resource_directory_to_scratch(ptr, &path_str)
493 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
494 }
495 std::sync::atomic::fence(Ordering::SeqCst);
496
497 header.request_type.store(5, Ordering::Release);
498 header.request_status.store(0, Ordering::Release);
499 if let Err(e) = events.signal_host() {
500 header.request_type.store(0, Ordering::Release);
501 return Err(format!("Failed to signal host for resource directory: {e}"));
502 }
503
504 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
505 header.request_type.store(0, Ordering::Release);
506 return Err(format!("Host did not respond to resource directory: {e}"));
507 }
508
509 let status = header.request_status.load(Ordering::Acquire);
510 header.request_type.store(0, Ordering::Release);
511 if status != 1 {
512 return Err("Resource directory update failed in host".to_string());
513 }
514 Ok(())
515 }
516
517 pub fn file_references(
518 &self,
519 ) -> Result<Vec<maolan_plugin_protocol::protocol::FileReference>, String> {
520 let (mapping, events) = match (&self.mapping, &self.events) {
521 (Some(m), Some(e)) => (m, e),
522 _ => return Err("CLAP processor not initialized".to_string()),
523 };
524 let ptr = mapping.as_ptr();
525 let header = unsafe { header_mut(ptr) };
526
527 header.request_type.store(6, Ordering::Release);
528 header.request_status.store(0, Ordering::Release);
529 if let Err(e) = events.signal_host() {
530 header.request_type.store(0, Ordering::Release);
531 return Err(format!("Failed to signal host for file references: {e}"));
532 }
533
534 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
535 header.request_type.store(0, Ordering::Release);
536 return Err(format!("Host did not respond to file references: {e}"));
537 }
538
539 let status = header.request_status.load(Ordering::Acquire);
540 if status != 1 {
541 header.request_type.store(0, Ordering::Release);
542 return Err("File references enumeration failed in host".to_string());
543 }
544
545 let paths = unsafe { read_file_references_from_scratch(ptr) }
546 .ok_or("Failed to read file references from scratch")?;
547 header.request_type.store(0, Ordering::Release);
548 Ok(paths)
549 }
550
551 pub fn update_file_reference(&self, index: u32, path: &str) -> Result<(), String> {
552 let (mapping, events) = match (&self.mapping, &self.events) {
553 (Some(m), Some(e)) => (m, e),
554 _ => return Err("CLAP processor not initialized".to_string()),
555 };
556 let ptr = mapping.as_ptr();
557 let header = unsafe { header_mut(ptr) };
558 unsafe {
559 write_file_reference_update_to_scratch(ptr, index, path)
560 .map_err(|e| format!("Failed to write file-reference update: {e}"))?;
561 }
562
563 header.request_type.store(7, Ordering::Release);
564 header.request_status.store(0, Ordering::Release);
565 if let Err(e) = events.signal_host() {
566 header.request_type.store(0, Ordering::Release);
567 return Err(format!(
568 "Failed to signal host for file-reference update: {e}"
569 ));
570 }
571
572 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
573 header.request_type.store(0, Ordering::Release);
574 return Err(format!(
575 "Host did not respond to file-reference update: {e}"
576 ));
577 }
578
579 let status = header.request_status.load(Ordering::Acquire);
580 header.request_type.store(0, Ordering::Release);
581 if status != 1 {
582 return Err("File-reference update failed in host".to_string());
583 }
584 Ok(())
585 }
586
587 pub fn process_with_audio_io(&self, frames: usize) {
588 let _ = self.process_with_midi(frames, &[], ClapTransportInfo::default());
589 }
590
591 pub fn process_with_midi(
592 &self,
593 frames: usize,
594 midi_in: &[MidiEvent],
595 transport: ClapTransportInfo,
596 ) -> Vec<ClapMidiOutputEvent> {
597 if self.bypassed.load(Ordering::Relaxed) {
598 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
599 return Vec::new();
600 }
601
602 self.setup_midi_ports();
603
604 {
605 let child = self.child.lock();
606 if let Some(ref mut c) = child.as_mut() {
607 match c.try_wait() {
608 Ok(Some(status)) if !status.success() => {
609 self.crash_count.fetch_add(1, Ordering::Relaxed);
610 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
611 return Vec::new();
612 }
613 _ => {}
614 }
615 }
616 }
617
618 let (mapping, events) = match (&self.mapping, &self.events) {
619 (Some(m), Some(e)) => (m, e),
620 _ => {
621 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
622 return Vec::new();
623 }
624 };
625
626 let ptr = mapping.as_ptr();
627 unsafe {
628 ipc::configure_shm_header(
629 ptr,
630 frames,
631 self.audio_inputs.len(),
632 self.audio_outputs.len(),
633 self.midi_input_ports.len(),
634 self.midi_output_ports.len(),
635 );
636 ipc::copy_inputs_to_shm(&self.audio_inputs, ptr, frames);
637
638 let t = transport_mut(ptr);
639 t.playhead_sample = transport.transport_sample as u64;
640 t.tempo = transport.bpm;
641 t.numerator = transport.tsig_num as u32;
642 t.denominator = transport.tsig_denom as u32;
643 t.flags = if transport.playing { 1 } else { 0 };
644
645 if let Some(port0) = self.midi_input_ports.first() {
649 let port0_lock = port0.lock();
650 port0_lock.buffer.extend_from_slice(midi_in);
651 port0_lock.mark_finished();
652 }
653
654 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
655 let port_lock = port.lock();
656 let midi_buf = midi_in_ring_ptr(ptr, port_idx);
657 let (midi_w, midi_r) = midi_in_indices(ptr, port_idx);
658 let midi_ring = RingBuffer::new(midi_buf, midi_w, midi_r, RING_CAPACITY);
659 for ev in &port_lock.buffer {
660 let midi_event = maolan_plugin_protocol::protocol::MidiEvent {
661 sample_offset: ev.frame,
662 data: [
663 ev.data.first().copied().unwrap_or(0),
664 ev.data.get(1).copied().unwrap_or(0),
665 ev.data.get(2).copied().unwrap_or(0),
666 ],
667 channel: ev.data.first().map(|b| b & 0x0F).unwrap_or(0),
668 flags: 0,
669 _pad: 0,
670 };
671 if !midi_ring.push(midi_event) {
672 break;
673 }
674 }
675 }
676 }
677
678 if events.signal_host().is_err() {
679 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
680 return Vec::new();
681 }
682
683 let timeout = Duration::from_millis(100);
684 if events.wait_host(timeout).is_err() {
685 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
686 return Vec::new();
687 }
688
689 {
690 let child = self.child.lock();
691 if let Some(ref mut c) = child.as_mut()
692 && let Ok(Some(status)) = c.try_wait()
693 && !status.success()
694 {
695 self.crash_count.fetch_add(1, Ordering::Relaxed);
696 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
697 return Vec::new();
698 }
699 }
700
701 unsafe {
702 ipc::copy_outputs_from_shm(&self.audio_outputs, ptr, frames);
703 }
704
705 let mut midi_out = Vec::new();
706 unsafe {
707 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
708 let port_lock = port.lock();
709 port_lock.buffer.clear();
710 let midi_out_buf = midi_out_ring_ptr(ptr, port_idx);
711 let (midi_out_w, midi_out_r) = midi_out_indices(ptr, port_idx);
712 let midi_out_ring =
713 RingBuffer::new(midi_out_buf, midi_out_w, midi_out_r, RING_CAPACITY);
714 while let Some(ev) = midi_out_ring.pop() {
715 let event = crate::midi::io::MidiEvent::new(ev.sample_offset, ev.data.to_vec());
716 port_lock.buffer.push(event.clone());
717 midi_out.push(ClapMidiOutputEvent {
718 port: port_idx,
719 event,
720 });
721 }
722 port_lock.mark_finished();
723 }
724 }
725
726 *self.last_process_time.lock() = Instant::now();
727 midi_out
728 }
729
730 pub fn path(&self) -> &str {
731 &self.path
732 }
733
734 pub fn plugin_id(&self) -> &str {
735 &self.plugin_id
736 }
737
738 pub fn name(&self) -> &str {
739 &self.name
740 }
741
742 pub fn take_stderr(&self) -> Option<ChildStderr> {
743 self.stderr.lock().take()
744 }
745
746 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
747 Ok(())
748 }
749
750 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
751 Ok(())
752 }
753
754 pub fn run_host_callbacks_main_thread(&self) {}
755
756 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
757 Ok(false)
758 }
759
760 pub fn ui_begin_session(&self) {}
761 pub fn ui_end_session(&self) {}
762 pub fn ui_should_close(&self) -> bool {
763 false
764 }
765 pub fn ui_take_due_timers(&self) -> Vec<u32> {
766 Vec::new()
767 }
768 pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
769 Vec::new()
770 }
771 pub fn ui_take_state_update(&self) -> Option<crate::plugins::types::ClapPluginState> {
772 None
773 }
774
775 pub fn gui_info(&self) -> Result<crate::plugins::types::ClapGuiInfo, String> {
776 Err("GUI not yet supported for CLAP plugins".to_string())
777 }
778
779 pub fn gui_create(&self, _api: &str, _is_floating: bool) -> Result<(), String> {
780 Err("GUI not yet supported for CLAP plugins".to_string())
781 }
782
783 pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
784 Err("GUI not yet supported for CLAP plugins".to_string())
785 }
786
787 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
788 if let Some(ref mapping) = self.mapping {
789 let header = unsafe { header_mut(mapping.as_ptr()) };
790 header.set_parent_window(window);
791 return Ok(());
792 }
793 Err("No active host to set parent window".to_string())
794 }
795
796 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
797 if let Some(ref mapping) = self.mapping {
798 let header = unsafe { header_mut(mapping.as_ptr()) };
799 header.set_gui_mode(if floating {
800 GuiMode::Floating
801 } else {
802 GuiMode::Embedded
803 });
804 return Ok(());
805 }
806 Err("No active host to set GUI mode".to_string())
807 }
808
809 pub fn gui_show(&self) -> Result<(), String> {
810 if let Some(ref mapping) = self.mapping
811 && let Some(ref events) = self.events
812 {
813 let header = unsafe { header_mut(mapping.as_ptr()) };
814 header.request_type.store(3, Ordering::Release);
815 let _ = events.signal_host();
816 return Ok(());
817 }
818 Err("No active host to show GUI".to_string())
819 }
820
821 pub fn gui_hide(&self) {
822 if let Some(ref mapping) = self.mapping
823 && let Some(ref events) = self.events
824 {
825 let header = unsafe { header_mut(mapping.as_ptr()) };
826 header.request_type.store(4, Ordering::Release);
827 let _ = events.signal_host();
828 }
829 }
830
831 pub fn gui_destroy(&self) {}
832
833 pub fn gui_on_main_thread(&self) {}
834
835 pub fn gui_on_timer(&self, _timer_id: u32) {}
836
837 pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
838 std::collections::HashMap::new()
839 }
840
841 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
842 let mut result = Vec::new();
843 if let Some(ref mapping) = self.mapping {
844 let ring = unsafe {
845 let buf = echo_ring_ptr(mapping.as_ptr());
846 let (w, r) = echo_indices(mapping.as_ptr());
847 RingBuffer::new(buf, w, r, RING_CAPACITY)
848 };
849 while let Some(ev) = ring.pop() {
850 result.push(ev);
851 }
852 }
853 result
854 }
855
856 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
857 let mut result = Vec::new();
858 if let Some(ref mapping) = self.mapping {
859 let ring = unsafe {
860 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
861 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
862 RingBuffer::new(buf, w, r, RING_CAPACITY)
863 };
864 while let Some(ev) = ring.pop() {
865 result.push(crate::midi::io::MidiEvent {
866 frame: ev.sample_offset,
867 data: ev.data.to_vec(),
868 });
869 }
870 }
871 result
872 }
873}
874
875impl Drop for ClapProcessor {
876 fn drop(&mut self) {
877 let mapping = self.mapping.take();
878 let events = self.events.take();
879 let child = self.child.lock().take();
880 let shm_name = std::mem::take(&mut self.shm_name);
881 ipc::drop_host(mapping, events, child, shm_name);
882 }
883}
884
885crate::impl_ipc_processor_wrapper!(ClapProcessor);
886
887impl UnsafeMutex<ClapProcessor> {
888 pub fn process_with_midi(
889 &self,
890 frames: usize,
891 midi_events: &[MidiEvent],
892 transport: ClapTransportInfo,
893 ) -> Vec<ClapMidiOutputEvent> {
894 self.lock()
895 .process_with_midi(frames, midi_events, transport)
896 }
897
898 pub fn is_bypassed(&self) -> bool {
899 self.lock().is_bypassed()
900 }
901
902 pub fn parameter_infos(&self) -> Vec<ClapParameterInfo> {
903 self.lock().parameter_infos()
904 }
905
906 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
907 self.lock().set_parameter(param_id, value)
908 }
909
910 pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
911 self.lock().set_parameter_at(param_id, value, frame)
912 }
913
914 pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
915 self.lock().begin_parameter_edit_at(param_id, frame)
916 }
917
918 pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
919 self.lock().end_parameter_edit_at(param_id, frame)
920 }
921
922 pub fn snapshot_state(&self) -> Result<crate::plugins::types::ClapPluginState, String> {
923 self.lock().snapshot_state()
924 }
925
926 pub fn restore_state(
927 &self,
928 state: &crate::plugins::types::ClapPluginState,
929 ) -> Result<(), String> {
930 self.lock().restore_state(state)
931 }
932
933 pub fn take_state_dirty(&self) -> bool {
934 self.lock().take_state_dirty()
935 }
936
937 pub fn path(&self) -> String {
938 self.lock().path().to_string()
939 }
940
941 pub fn plugin_id(&self) -> String {
942 self.lock().plugin_id().to_string()
943 }
944
945 pub fn ui_begin_session(&self) {
946 self.lock().ui_begin_session();
947 }
948
949 pub fn ui_end_session(&self) {
950 self.lock().ui_end_session();
951 }
952
953 pub fn ui_should_close(&self) -> bool {
954 self.lock().ui_should_close()
955 }
956
957 pub fn ui_take_due_timers(&self) -> Vec<u32> {
958 self.lock().ui_take_due_timers()
959 }
960
961 pub fn ui_take_param_updates(&self) -> Vec<ClapParamUpdate> {
962 self.lock().ui_take_param_updates()
963 }
964
965 pub fn ui_take_state_update(&self) -> Option<crate::plugins::types::ClapPluginState> {
966 self.lock().ui_take_state_update()
967 }
968
969 pub fn gui_info(&self) -> Result<crate::plugins::types::ClapGuiInfo, String> {
970 self.lock().gui_info()
971 }
972
973 pub fn gui_create(&self, api: &str, is_floating: bool) -> Result<(), String> {
974 self.lock().gui_create(api, is_floating)
975 }
976
977 pub fn gui_get_size(&self) -> Result<(u32, u32), String> {
978 self.lock().gui_get_size()
979 }
980
981 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
982 self.lock().gui_set_parent_x11(window)
983 }
984
985 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
986 self.lock().gui_set_floating_mode(floating)
987 }
988
989 pub fn gui_show(&self) -> Result<(), String> {
990 self.lock().gui_show()
991 }
992
993 pub fn gui_hide(&self) {
994 self.lock().gui_hide();
995 }
996
997 pub fn gui_destroy(&self) {
998 self.lock().gui_destroy();
999 }
1000
1001 pub fn gui_on_main_thread(&self) {
1002 self.lock().gui_on_main_thread();
1003 }
1004
1005 pub fn gui_on_timer(&self, timer_id: u32) {
1006 self.lock().gui_on_timer(timer_id);
1007 }
1008
1009 pub fn note_names(&self) -> std::collections::HashMap<u8, String> {
1010 self.lock().note_names()
1011 }
1012}
1013
1014fn split_plugin_spec(spec: &str) -> (&str, &str) {
1015 if let Some(pos) = spec.rfind("::") {
1016 (&spec[..pos], &spec[pos + 2..])
1017 } else if let Some(pos) = spec.rfind('#') {
1018 (&spec[..pos], &spec[pos + 1..])
1019 } else {
1020 (spec, "")
1021 }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 fn find_host_binary() -> PathBuf {
1029 let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
1030 let workspace_root = std::path::Path::new(&manifest)
1031 .parent()
1032 .unwrap()
1033 .join("daw");
1034 workspace_root
1035 .join("target")
1036 .join("debug")
1037 .join("maolan-plugin-host")
1038 }
1039
1040 #[test]
1041 fn clap_processor_processes_audio() {
1042 let host_bin = find_host_binary();
1043 if !host_bin.exists() {
1044 return;
1045 }
1046
1047 let plugin_path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
1048 .parent()
1049 .unwrap()
1050 .join("daw")
1051 .join("plugin-host")
1052 .join("tests")
1053 .join("test_passthrough.clap");
1054
1055 if !plugin_path.exists() {
1056 return;
1057 }
1058
1059 let processor = ClapProcessor::new(
1060 48000.0,
1061 256,
1062 &format!("{}#com.maolan.test.passthrough", plugin_path.display()),
1063 2,
1064 2,
1065 host_bin,
1066 )
1067 .expect("should create processor");
1068
1069 processor.setup_audio_ports();
1070
1071 for (i, input) in processor.audio_inputs().iter().enumerate() {
1072 let buf = input.buffer.lock();
1073 for (j, sample) in buf.iter_mut().enumerate() {
1074 *sample = (i * 1000 + j) as f32;
1075 }
1076 *input.finished.lock() = true;
1077 }
1078
1079 processor.process_with_audio_io(256);
1080
1081 for output in processor.audio_outputs().iter() {
1082 let buf = output.buffer.lock();
1083 assert!(
1084 buf.iter().any(|&s| s != 0.0),
1085 "output buffer should contain non-zero samples"
1086 );
1087 }
1088 }
1089
1090 #[test]
1091 fn clap_processor_crash_bypass() {
1092 let host_bin = find_host_binary();
1093 if !host_bin.exists() {
1094 return;
1095 }
1096
1097 let processor = ClapProcessor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
1098 .expect("should create processor for crash test");
1099
1100 processor.setup_audio_ports();
1101
1102 {
1103 let buf = processor.audio_inputs()[0].buffer.lock();
1104 buf.fill(1.0);
1105 *processor.audio_inputs()[0].finished.lock() = true;
1106 }
1107
1108 std::thread::sleep(std::time::Duration::from_millis(50));
1110
1111 processor.process_with_audio_io(256);
1112
1113 let out_buf = processor.audio_outputs()[0].buffer.lock();
1114 assert!(
1115 out_buf.iter().all(|&s| s == 1.0),
1116 "after crash, output should be bypass copy of input"
1117 );
1118 }
1119
1120 #[test]
1121 fn clap_track_integration() {
1122 use crate::track::Track;
1123
1124 let host_bin = find_host_binary();
1125 if !host_bin.exists() {
1126 return;
1127 }
1128
1129 let plugin_path = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
1130 .parent()
1131 .unwrap()
1132 .join("daw")
1133 .join("plugin-host")
1134 .join("tests")
1135 .join("test_passthrough.clap");
1136
1137 if !plugin_path.exists() {
1138 return;
1139 }
1140
1141 let mut track = Track::new("test-track".to_string(), 2, 2, 0, 0, 256, 48000.0);
1142
1143 track
1144 .load_clap_plugin(
1145 &format!("{}::com.maolan.test.passthrough", plugin_path.display()),
1146 None,
1147 )
1148 .expect("should load CLAP plugin on track");
1149
1150 assert_eq!(track.clap_plugins.len(), 1);
1151
1152 let processor = track.clap_plugins[0].processor.lock();
1153 processor.setup_audio_ports();
1154
1155 for (i, input) in processor.audio_inputs().iter().enumerate() {
1156 let buf = input.buffer.lock();
1157 for (j, sample) in buf.iter_mut().enumerate() {
1158 *sample = (i * 1000 + j) as f32;
1159 }
1160 *input.finished.lock() = true;
1161 }
1162
1163 processor.process_with_audio_io(256);
1164
1165 for (ch, output) in processor.audio_outputs().iter().enumerate() {
1166 let buf = output.buffer.lock();
1167 assert!(
1168 buf.iter().any(|&s| s != 0.0),
1169 "plugin output ch={ch} should contain non-zero samples after CLAP processing"
1170 );
1171 }
1172 }
1173}