1use crate::audio::io::AudioIO;
2use crate::midi::io::{MIDIIO, MidiEvent};
3use crate::plugins::ipc;
4use arc_swap::ArcSwapOption;
5use maolan_plugin_protocol::events::EventPair;
6use maolan_plugin_protocol::protocol::*;
7use maolan_plugin_protocol::ringbuf::RingBuffer;
8use maolan_plugin_protocol::shm::ShmMapping;
9use std::cell::UnsafeCell;
10use std::collections::HashMap;
11use std::path::PathBuf;
12use std::process::{Child, ChildStderr};
13use std::sync::Arc;
14use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
15use std::time::{Duration, Instant};
16
17const SHM_LATENCY_SAMPLES_OFFSET: usize = 84;
18
19unsafe fn latency_samples_atomic(ptr: *mut u8) -> &'static AtomicU32 {
20 unsafe { &*(ptr.add(SHM_LATENCY_SAMPLES_OFFSET) as *const AtomicU32) }
21}
22
23fn wait_for_host_request_complete(
24 header: &ShmHeader,
25 events: &EventPair,
26 timeout: Duration,
27) -> Result<(), String> {
28 let start = Instant::now();
29 loop {
30 if header.request_status.load(Ordering::Acquire) != 0 {
31 return Ok(());
32 }
33 let elapsed = start.elapsed();
34 if elapsed >= timeout {
35 return Err("Host did not respond to request".to_string());
36 }
37 match events.wait_host(timeout - elapsed) {
38 Ok(()) => {
39 std::sync::atomic::fence(Ordering::Acquire);
40 continue;
41 }
42 Err(e) if e.kind() == std::io::ErrorKind::TimedOut => continue,
43 Err(e) => return Err(format!("Host did not respond to request: {e}")),
44 }
45 }
46}
47
48pub struct Lv2Processor {
49 uri: String,
50 name: String,
51 audio_inputs: Vec<Arc<AudioIO>>,
52 audio_outputs: Vec<Arc<AudioIO>>,
53 main_audio_inputs: usize,
54 main_audio_outputs: usize,
55 midi_input_ports: Vec<Arc<MIDIIO>>,
56 midi_output_ports: Vec<Arc<MIDIIO>>,
57 param_values: HashMap<u32, AtomicU64>,
60 bypassed: Arc<AtomicBool>,
61
62 child: UnsafeCell<Option<Child>>,
70 stderr: ArcSwapOption<ChildStderr>,
73 mapping: Option<ShmMapping>,
74 events: Option<EventPair>,
75 shm_name: String,
76
77 crash_count: AtomicU32,
78 last_latency_samples: AtomicUsize,
79 latency_changed: AtomicBool,
80}
81
82unsafe impl Sync for Lv2Processor {}
86
87pub type SharedLv2Processor = Arc<Lv2Processor>;
88
89impl Lv2Processor {
90 pub fn new(
91 sample_rate: f64,
92 buffer_size: usize,
93 plugin_uri: &str,
94 input_count: usize,
95 output_count: usize,
96 host_binary: PathBuf,
97 ) -> Result<Self, String> {
98 let audio_inputs = (0..input_count.max(1))
99 .map(|_| Arc::new(AudioIO::new(buffer_size)))
100 .collect::<Vec<_>>();
101 let audio_outputs = (0..output_count.max(1))
102 .map(|_| Arc::new(AudioIO::new(buffer_size)))
103 .collect::<Vec<_>>();
104
105 let instance_id = ipc::unique_instance_id("lv2");
106 let sample_rate_str = sample_rate.to_string();
107 let buffer_size_str = buffer_size.to_string();
108 let num_inputs_str = input_count.max(1).to_string();
109 let num_outputs_str = output_count.max(1).to_string();
110 let (mut child, mapping, events, shm_name, stderr) = ipc::spawn_host(ipc::HostSpawnArgs {
111 host_binary: &host_binary,
112 format: "lv2",
113 plugin_spec: plugin_uri,
114 instance_id: &instance_id,
115 extra_args: &[
116 &sample_rate_str,
117 &buffer_size_str,
118 &num_inputs_str,
119 &num_outputs_str,
120 ],
121 })?;
122
123 let header = unsafe { header_ref(mapping.as_ptr()) };
124 if !ipc::wait_for_ready(header, Duration::from_secs(10)) {
125 let _ = child.kill();
126 return Err("LV2 host did not signal ready".to_string());
127 }
128
129 let name = unsafe {
130 maolan_plugin_protocol::protocol::read_plugin_name_from_scratch(mapping.as_ptr())
131 .unwrap_or_else(|| {
132 plugin_uri
133 .rsplit_once('/')
134 .map(|(_, name)| name)
135 .unwrap_or(plugin_uri)
136 .to_string()
137 })
138 };
139
140 let header = unsafe { header_ref(mapping.as_ptr()) };
141 let midi_in_count = header.midi_in_port_count.load(Ordering::Acquire) as usize;
142 let midi_out_count = header.midi_out_port_count.load(Ordering::Acquire) as usize;
143 let midi_input_ports: Vec<_> = (0..midi_in_count)
144 .map(|_| Arc::new(MIDIIO::new()))
145 .collect();
146 let midi_output_ports: Vec<_> = (0..midi_out_count)
147 .map(|_| Arc::new(MIDIIO::new()))
148 .collect();
149
150 let param_values: HashMap<u32, AtomicU64> = HashMap::new();
151
152 Ok(Self {
153 uri: plugin_uri.to_string(),
154 name,
155 audio_inputs,
156 audio_outputs,
157 main_audio_inputs: input_count.max(1),
158 main_audio_outputs: output_count.max(1),
159 midi_input_ports,
160 midi_output_ports,
161 param_values,
162 bypassed: Arc::new(AtomicBool::new(false)),
163 child: UnsafeCell::new(Some(child)),
164 stderr: ArcSwapOption::from_pointee(stderr),
165 mapping: Some(mapping),
166 events: Some(events),
167 shm_name,
168 crash_count: AtomicU32::new(0),
169 last_latency_samples: AtomicUsize::new(0),
170 latency_changed: AtomicBool::new(false),
171 })
172 }
173
174 unsafe fn with_child<R>(&self, f: impl FnOnce(&mut Option<Child>) -> R) -> R {
182 f(unsafe { &mut *self.child.get() })
183 }
184
185 pub fn setup_audio_ports(&self) {
186 for port in &self.audio_inputs {
187 port.setup();
188 }
189 for port in &self.audio_outputs {
190 port.setup();
191 }
192 }
193
194 pub fn setup_midi_ports(&self) {
195 for port in &self.midi_input_ports {
196 unsafe { port.setup() };
200 }
201 for port in &self.midi_output_ports {
202 unsafe { port.setup() };
204 }
205 }
206
207 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
208 &self.audio_inputs
209 }
210
211 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
212 &self.audio_outputs
213 }
214
215 pub fn main_audio_input_count(&self) -> usize {
216 self.main_audio_inputs
217 }
218
219 pub fn main_audio_output_count(&self) -> usize {
220 self.main_audio_outputs
221 }
222
223 pub fn midi_input_count(&self) -> usize {
224 self.midi_input_ports.len()
225 }
226
227 pub fn midi_output_count(&self) -> usize {
228 self.midi_output_ports.len()
229 }
230
231 pub fn midi_input_ports(&self) -> &[Arc<MIDIIO>] {
232 &self.midi_input_ports
233 }
234
235 pub fn midi_output_ports(&self) -> &[Arc<MIDIIO>] {
236 &self.midi_output_ports
237 }
238
239 pub fn set_bypassed(&self, bypassed: bool) {
240 let previous = self.bypassed.swap(bypassed, Ordering::Relaxed);
241 if previous != bypassed {
242 self.latency_changed.store(true, Ordering::Release);
243 }
244 }
245
246 pub fn is_bypassed(&self) -> bool {
247 self.bypassed.load(Ordering::Relaxed)
248 }
249
250 pub fn latency_samples(&self) -> usize {
251 if self.bypassed.load(Ordering::Relaxed) {
252 let previous = self.last_latency_samples.swap(0, Ordering::AcqRel);
253 if previous != 0 {
254 self.latency_changed.store(true, Ordering::Release);
255 }
256 return 0;
257 }
258 let latency = self
259 .mapping
260 .as_ref()
261 .map(|mapping| unsafe {
262 latency_samples_atomic(mapping.as_ptr()).load(Ordering::Acquire) as usize
263 })
264 .unwrap_or(0);
265 let previous = self.last_latency_samples.swap(latency, Ordering::AcqRel);
266 if previous != latency {
267 self.latency_changed.store(true, Ordering::Release);
268 }
269 latency
270 }
271
272 pub fn take_latency_changed(&self) -> bool {
273 self.latency_changed.swap(false, Ordering::AcqRel)
274 }
275
276 pub fn parameter_values(&self) -> HashMap<u32, f64> {
277 self.param_values
278 .iter()
279 .map(|(&id, value)| (id, f64::from_bits(value.load(Ordering::Relaxed))))
280 .collect()
281 }
282
283 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
284 self.set_parameter_at(param_id, value, 0)
285 }
286
287 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
288 if let Some(slot) = self.param_values.get(¶m_id) {
289 slot.store(value.to_bits(), Ordering::Relaxed);
290 } else {
291 tracing::warn!("LV2 set_parameter_at: unknown parameter id {param_id}");
292 }
293
294 if let Some(ref mapping) = self.mapping {
295 let ring = unsafe {
296 let buf = param_ring_ptr(mapping.as_ptr());
297 let (w, r) = param_indices(mapping.as_ptr());
298 RingBuffer::new(buf, w, r, RING_CAPACITY)
299 };
300 let ev = ParameterEvent {
301 param_index: param_id,
302 value: value as f32,
303 sample_offset: 0,
304 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
305 };
306 if !ring.push(ev) {}
307 }
308 Ok(())
309 }
310
311 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
312 Ok(())
313 }
314
315 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
316 Ok(())
317 }
318
319 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
320 false
321 }
322
323 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
324 let (mapping, events) = match (&self.mapping, &self.events) {
325 (Some(m), Some(e)) => (m, e),
326 _ => return Err("LV2 processor not initialized".to_string()),
327 };
328 let ptr = mapping.as_ptr();
329 let header = unsafe { header_mut(ptr) };
330
331 tracing::info!("LV2 snapshot_state: sending request to host");
332 header.request_type.store(1, Ordering::Release);
333 header.request_status.store(0, Ordering::Release);
334 if let Err(e) = events.signal_host() {
335 header.request_type.store(0, Ordering::Release);
336 return Err(format!("Failed to signal host for state save: {}", e));
337 }
338
339 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
340 header.request_type.store(0, Ordering::Release);
341 return Err(format!("Host did not respond to state save: {}", e));
342 }
343
344 let status = header.request_status.load(Ordering::Acquire);
345 let size = header.scratch_size.load(Ordering::Acquire) as usize;
346 tracing::info!(status, size, "LV2 snapshot_state: host responded");
347 if status != 1 {
348 header.request_type.store(0, Ordering::Release);
349 return Err("State save failed in host".to_string());
350 }
351
352 let scratch = unsafe { scratch_ptr(ptr) };
353 let mut bytes = vec![0u8; size];
354 unsafe {
355 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
356 }
357 header.request_type.store(0, Ordering::Release);
358 Ok(bytes)
359 }
360
361 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
362 let (mapping, events) = match (&self.mapping, &self.events) {
363 (Some(m), Some(e)) => (m, e),
364 _ => return Err("LV2 processor not initialized".to_string()),
365 };
366 let ptr = mapping.as_ptr();
367 let header = unsafe { header_mut(ptr) };
368
369 let scratch = unsafe { scratch_ptr(ptr) };
370 let size = state.len().min(SCRATCH_SIZE);
371 unsafe {
372 std::ptr::copy_nonoverlapping(state.as_ptr(), scratch, size);
373 }
374 header.scratch_size.store(size as u32, Ordering::Release);
375
376 header.request_type.store(2, Ordering::Release);
377 header.request_status.store(0, Ordering::Release);
378 if let Err(e) = events.signal_host() {
379 header.request_type.store(0, Ordering::Release);
380 return Err(format!("Failed to signal host for state restore: {}", e));
381 }
382
383 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
384 header.request_type.store(0, Ordering::Release);
385 return Err(format!("Host did not respond to state restore: {}", e));
386 }
387
388 let status = header.request_status.load(Ordering::Acquire);
389 header.request_type.store(0, Ordering::Release);
390 if status != 1 {
391 return Err("State restore failed in host".to_string());
392 }
393 Ok(())
394 }
395
396 fn deserialize_lv2_control_ports(
397 scratch: *const u8,
398 size: usize,
399 ) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
400 if size < 4 {
401 return Err("scratch too small for LV2 control ports".to_string());
402 }
403 let mut offset = 0usize;
404
405 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
406 offset += 4;
407
408 let mut ports = Vec::with_capacity(count);
409 for _ in 0..count {
410 if offset + 4 > size {
411 return Err("scratch underflow".to_string());
412 }
413 let index = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) };
414 offset += 4;
415
416 if offset + 4 > size {
417 return Err("scratch underflow".to_string());
418 }
419 let name_len =
420 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
421 offset += 4;
422 if offset + name_len > size {
423 return Err("scratch underflow".to_string());
424 }
425 let mut name_bytes = vec![0u8; name_len];
426 unsafe {
427 std::ptr::copy_nonoverlapping(
428 scratch.add(offset),
429 name_bytes.as_mut_ptr(),
430 name_len,
431 );
432 }
433 offset += name_len;
434 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
435
436 if offset + 12 > size {
437 return Err("scratch underflow".to_string());
438 }
439 let min = f32::from_bits(unsafe {
440 std::ptr::read_unaligned(scratch.add(offset) as *const u32)
441 });
442 let max = f32::from_bits(unsafe {
443 std::ptr::read_unaligned(scratch.add(offset + 4) as *const u32)
444 });
445 let value = f32::from_bits(unsafe {
446 std::ptr::read_unaligned(scratch.add(offset + 8) as *const u32)
447 });
448 offset += 12;
449
450 ports.push(crate::message::Lv2ControlPortInfo {
451 index,
452 name,
453 min,
454 max,
455 value,
456 });
457 }
458
459 Ok(ports)
460 }
461
462 fn deserialize_lv2_note_names(
463 scratch: *const u8,
464 size: usize,
465 ) -> Result<HashMap<u8, String>, String> {
466 if size < 4 {
467 return Err("scratch too small for LV2 note names".to_string());
468 }
469 let mut offset = 0usize;
470 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
471 offset += 4;
472
473 let mut note_names = HashMap::with_capacity(count);
474 for _ in 0..count {
475 if offset + 4 > size {
476 return Err("scratch underflow".to_string());
477 }
478 let note = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as u8;
479 offset += 4;
480
481 if offset + 4 > size {
482 return Err("scratch underflow".to_string());
483 }
484 let name_len =
485 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
486 offset += 4;
487 if offset + name_len > size {
488 return Err("scratch underflow".to_string());
489 }
490 let mut name_bytes = vec![0u8; name_len];
491 unsafe {
492 std::ptr::copy_nonoverlapping(
493 scratch.add(offset),
494 name_bytes.as_mut_ptr(),
495 name_len,
496 );
497 }
498 offset += name_len;
499 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
500 note_names.insert(note, name);
501 }
502
503 Ok(note_names)
504 }
505
506 pub fn control_ports(&self) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
507 let (mapping, events) = match (&self.mapping, &self.events) {
508 (Some(m), Some(e)) => (m, e),
509 _ => return Err("LV2 processor not initialized".to_string()),
510 };
511 let ptr = mapping.as_ptr();
512 let header = unsafe { header_mut(ptr) };
513
514 tracing::info!("LV2 control_ports: sending request to host");
515 header.request_type.store(
516 maolan_plugin_protocol::protocol::REQUEST_LV2_CONTROL_PORTS,
517 Ordering::Release,
518 );
519 header.request_status.store(0, Ordering::Release);
520 if let Err(e) = events.signal_host() {
521 header.request_type.store(0, Ordering::Release);
522 return Err(format!("Failed to signal host for LV2 control ports: {e}"));
523 }
524
525 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
526 header.request_type.store(0, Ordering::Release);
527 return Err(format!("Host did not respond to LV2 control ports: {e}"));
528 }
529
530 let status = header.request_status.load(Ordering::Acquire);
531 let size = header.scratch_size.load(Ordering::Acquire) as usize;
532 tracing::info!(status, size, "LV2 control_ports: host responded");
533 if status != 1 {
534 header.request_type.store(0, Ordering::Release);
535 return Err("LV2 control port enumeration failed in host".to_string());
536 }
537
538 let scratch = unsafe { scratch_ptr(ptr) };
539 let result = Self::deserialize_lv2_control_ports(scratch, size);
540 match &result {
541 Ok(ports) => tracing::info!(count = ports.len(), "LV2 control_ports: deserialized"),
542 Err(e) => tracing::error!("LV2 control_ports: deserialize failed: {e}"),
543 }
544 header.request_type.store(0, Ordering::Release);
545 result
546 }
547
548 pub fn note_names(&self) -> Result<HashMap<u8, String>, String> {
549 let (mapping, events) = match (&self.mapping, &self.events) {
550 (Some(m), Some(e)) => (m, e),
551 _ => return Err("LV2 processor not initialized".to_string()),
552 };
553 let ptr = mapping.as_ptr();
554 let header = unsafe { header_mut(ptr) };
555
556 header.request_type.store(
557 maolan_plugin_protocol::protocol::REQUEST_LV2_MIDNAM,
558 Ordering::Release,
559 );
560 header.request_status.store(0, Ordering::Release);
561 if let Err(e) = events.signal_host() {
562 header.request_type.store(0, Ordering::Release);
563 return Err(format!("Failed to signal host for LV2 midnam: {e}"));
564 }
565
566 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
567 header.request_type.store(0, Ordering::Release);
568 return Err(format!("Host did not respond to LV2 midnam: {e}"));
569 }
570
571 let status = header.request_status.load(Ordering::Acquire);
572 let size = header.scratch_size.load(Ordering::Acquire) as usize;
573 if status != 1 {
574 header.request_type.store(0, Ordering::Release);
575 return Err("LV2 midnam enumeration failed in host".to_string());
576 }
577
578 let scratch = unsafe { scratch_ptr(ptr) };
579 let result = Self::deserialize_lv2_note_names(scratch, size);
580 header.request_type.store(0, Ordering::Release);
581 result
582 }
583
584 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
585 let (mapping, events) = match (&self.mapping, &self.events) {
586 (Some(m), Some(e)) => (m, e),
587 _ => return Err("LV2 processor not initialized".to_string()),
588 };
589 let ptr = mapping.as_ptr();
590 let header = unsafe { header_mut(ptr) };
591 let path_str = dir.to_string_lossy().to_string();
592 unsafe {
593 write_resource_directory_to_scratch(ptr, &path_str)
594 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
595 }
596 std::sync::atomic::fence(Ordering::SeqCst);
597
598 header.request_type.store(5, Ordering::Release);
599 header.request_status.store(0, Ordering::Release);
600 if let Err(e) = events.signal_host() {
601 header.request_type.store(0, Ordering::Release);
602 return Err(format!("Failed to signal host for resource directory: {e}"));
603 }
604
605 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
606 header.request_type.store(0, Ordering::Release);
607 return Err(format!("Host did not respond to resource directory: {e}"));
608 }
609
610 let status = header.request_status.load(Ordering::Acquire);
611 header.request_type.store(0, Ordering::Release);
612 if status != 1 {
613 return Err("Resource directory update failed in host".to_string());
614 }
615 Ok(())
616 }
617
618 pub fn process_with_audio_buffers(
619 &self,
620 frames: usize,
621 transport: crate::plugins::types::Lv2TransportInfo,
622 audio_inputs: &[&[f32]],
623 audio_outputs: &mut [&mut [f32]],
624 ) -> Vec<MidiEvent> {
625 if self.bypassed.load(Ordering::Relaxed) {
626 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
627 return Vec::new();
628 }
629
630 let crashed = unsafe {
633 self.with_child(|child| {
634 if let Some(c) = child.as_mut()
635 && let Ok(Some(status)) = c.try_wait()
636 && !status.success()
637 {
638 self.crash_count.fetch_add(1, Ordering::Relaxed);
639 return true;
640 }
641 false
642 })
643 };
644 if crashed {
645 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
646 return Vec::new();
647 }
648
649 let (mapping, events) = match (&self.mapping, &self.events) {
650 (Some(m), Some(e)) => (m, e),
651 _ => {
652 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
653 return Vec::new();
654 }
655 };
656
657 let ptr = mapping.as_ptr();
658 let num_in = audio_inputs.len();
659 let num_out = audio_outputs.len();
660 let midi_in_count = self.midi_input_ports.len();
661 let midi_out_count = self.midi_output_ports.len();
662 unsafe {
663 ipc::configure_shm_header(ptr, frames, num_in, num_out, midi_in_count, midi_out_count);
664
665 let t = transport_mut(ptr);
666 t.playhead_sample = transport.transport_sample as u64;
667 t.tempo = transport.bpm;
668 t.numerator = u32::from(transport.tsig_num);
669 t.denominator = u32::from(transport.tsig_denom);
670 t.flags = u32::from(transport.playing);
671
672 ipc::copy_input_slices_to_shm(audio_inputs, ptr, frames);
673
674 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
675 let buf = midi_in_ring_ptr(ptr, port_idx);
676 let (w, r) = midi_in_indices(ptr, port_idx);
677 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
678 let port_buffer = port.buffer();
683 for ev in port_buffer {
684 let data = {
685 let mut d = [0u8; 3];
686 for (i, b) in ev.data.iter().enumerate().take(3) {
687 d[i] = *b;
688 }
689 d
690 };
691 let _ = ring.push(maolan_plugin_protocol::MidiEvent {
692 sample_offset: ev.frame,
693 data,
694 channel: ev.data.first().copied().unwrap_or(0) & 0x0F,
695 flags: 0,
696 _pad: 0,
697 });
698 }
699 port.mark_finished();
700 }
701 }
702
703 if events.signal_host().is_err() {
704 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
705 return Vec::new();
706 }
707
708 let timeout = Duration::from_millis(100);
709 match events.wait_host(timeout) {
710 Ok(()) => {}
711 Err(_) => {
712 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
713 return Vec::new();
714 }
715 }
716
717 unsafe {
718 ipc::copy_outputs_from_shm_to_slices(audio_outputs, ptr, frames);
719
720 let mut output_events = Vec::new();
721 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
722 let buf = midi_out_ring_ptr(ptr, port_idx);
723 let (w, r) = midi_out_indices(ptr, port_idx);
724 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
725 let mut port_buffer = port.buffer_mut();
728 port_buffer.clear();
729 while let Some(ev) = ring.pop() {
730 let event = MidiEvent {
731 frame: ev.sample_offset,
732 data: ev.data.to_vec(),
733 };
734 port_buffer.push(event.clone());
735 output_events.push(event);
736 }
737 port.mark_finished();
738 }
739 output_events
740 }
741 }
742
743 pub fn uri(&self) -> &str {
744 &self.uri
745 }
746
747 pub fn name(&self) -> &str {
748 &self.name
749 }
750
751 pub fn take_stderr(&self) -> Option<ChildStderr> {
752 self.stderr.swap(None).and_then(|s| Arc::try_unwrap(s).ok())
755 }
756
757 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
758 Ok(())
759 }
760
761 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
762 Ok(())
763 }
764
765 pub fn run_host_callbacks_main_thread(&self) {}
766
767 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
768 Ok(false)
769 }
770
771 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
772 if let Some(ref mapping) = self.mapping {
773 let header = unsafe { header_mut(mapping.as_ptr()) };
774 header.set_parent_window(window);
775 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::X11);
776 return Ok(());
777 }
778 Err("No active host to set parent window".to_string())
779 }
780
781 pub fn gui_set_parent_wayland(&self, window: usize) -> Result<(), String> {
782 if let Some(ref mapping) = self.mapping {
783 let header = unsafe { header_mut(mapping.as_ptr()) };
784 header.set_parent_window(window);
785 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::Wayland);
786 return Ok(());
787 }
788 Err("No active host to set parent window".to_string())
789 }
790
791 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
792 if let Some(ref mapping) = self.mapping {
793 let header = unsafe { header_mut(mapping.as_ptr()) };
794 header.set_gui_mode(if floating {
795 GuiMode::Floating
796 } else {
797 GuiMode::Embedded
798 });
799 if floating {
800 header.set_parent_window(0);
801 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::None);
802 }
803 return Ok(());
804 }
805 Err("No active host to set GUI mode".to_string())
806 }
807
808 pub fn gui_show(&self) -> Result<(), String> {
809 if let Some(ref mapping) = self.mapping
810 && let Some(ref events) = self.events
811 {
812 let header = unsafe { header_mut(mapping.as_ptr()) };
813 header.request_type.store(3, Ordering::Release);
814 let _ = events.signal_host();
815 return Ok(());
816 }
817 Err("No active host to show GUI".to_string())
818 }
819
820 pub fn gui_hide(&self) {
821 if let Some(ref mapping) = self.mapping
822 && let Some(ref events) = self.events
823 {
824 let header = unsafe { header_mut(mapping.as_ptr()) };
825 header.request_type.store(4, Ordering::Release);
826 let _ = events.signal_host();
827 }
828 }
829
830 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
831 let mut result = Vec::new();
832 if let Some(ref mapping) = self.mapping {
833 let ring = unsafe {
834 let buf = echo_ring_ptr(mapping.as_ptr());
835 let (w, r) = echo_indices(mapping.as_ptr());
836 RingBuffer::new(buf, w, r, RING_CAPACITY)
837 };
838 while let Some(ev) = ring.pop() {
839 result.push(ev);
840 }
841 }
842 result
843 }
844
845 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
846 let mut result = Vec::new();
847 if let Some(ref mapping) = self.mapping {
848 let ring = unsafe {
849 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
850 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
851 RingBuffer::new(buf, w, r, RING_CAPACITY)
852 };
853 while let Some(ev) = ring.pop() {
854 result.push(crate::midi::io::MidiEvent {
855 frame: ev.sample_offset,
856 data: ev.data.to_vec(),
857 });
858 }
859 }
860 result
861 }
862}
863
864impl Drop for Lv2Processor {
865 fn drop(&mut self) {
866 let mapping = self.mapping.take();
867 let events = self.events.take();
868 let child = self.child.get_mut().take();
869 let shm_name = std::mem::take(&mut self.shm_name);
870 ipc::drop_host(mapping, events, child, shm_name);
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877
878 fn find_host_binary() -> PathBuf {
879 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
880 }
881
882 #[cfg_attr(
883 all(miri, target_os = "freebsd"),
884 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
885 )]
886 #[test]
887 fn find_host_binary_locates_binary() {
888 let host_bin = find_host_binary();
889 assert!(
890 host_bin.exists(),
891 "plugin-host binary should exist at {}",
892 host_bin.display()
893 );
894 }
895
896 #[cfg_attr(
897 all(miri, target_os = "freebsd"),
898 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
899 )]
900 #[test]
901 fn lv2_processor_crash_bypass() {
902 let host_bin = find_host_binary();
903
904 let processor = Lv2Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
905 .expect("should create LV2 processor for crash test");
906
907 processor.setup_audio_ports();
908
909 let input_buffers = [vec![1.0; 256]];
910 let mut output_buffers = [vec![0.0; 256]];
911 let inputs = input_buffers.iter().map(Vec::as_slice).collect::<Vec<_>>();
912 let mut outputs = output_buffers
913 .iter_mut()
914 .map(Vec::as_mut_slice)
915 .collect::<Vec<_>>();
916 processor.process_with_audio_buffers(
917 256,
918 crate::plugins::types::Lv2TransportInfo::default(),
919 &inputs,
920 &mut outputs,
921 );
922
923 let out_buf = &output_buffers[0];
924 let first_few: Vec<f32> = out_buf.iter().take(10).copied().collect();
925 assert!(
926 out_buf.iter().all(|&s| s == 1.0),
927 "after crash, output should be bypass copy of input, got: {:?}",
928 first_few
929 );
930 }
931
932 #[cfg_attr(
933 all(miri, target_os = "freebsd"),
934 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
935 )]
936 #[test]
937 fn lv2_bypass_reports_zero_latency() {
938 let processor = Lv2Processor::new(48000.0, 256, "__test__", 1, 1, find_host_binary())
939 .expect("should create LV2 processor");
940 let mapping = processor.mapping.as_ref().expect("mapping exists");
941 unsafe {
942 latency_samples_atomic(mapping.as_ptr()).store(128, Ordering::Release);
943 }
944
945 assert_eq!(processor.latency_samples(), 128);
946 processor.set_bypassed(true);
947 assert_eq!(processor.latency_samples(), 0);
948 assert!(processor.take_latency_changed());
949 }
950}