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