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 self.bypassed.store(bypassed, Ordering::Relaxed);
241 }
242
243 pub fn is_bypassed(&self) -> bool {
244 self.bypassed.load(Ordering::Relaxed)
245 }
246
247 pub fn latency_samples(&self) -> usize {
248 let latency = self
249 .mapping
250 .as_ref()
251 .map(|mapping| unsafe {
252 latency_samples_atomic(mapping.as_ptr()).load(Ordering::Acquire) as usize
253 })
254 .unwrap_or(0);
255 let previous = self.last_latency_samples.swap(latency, Ordering::AcqRel);
256 if previous != latency {
257 self.latency_changed.store(true, Ordering::Release);
258 }
259 latency
260 }
261
262 pub fn take_latency_changed(&self) -> bool {
263 self.latency_changed.swap(false, Ordering::AcqRel)
264 }
265
266 pub fn parameter_values(&self) -> HashMap<u32, f64> {
267 self.param_values
268 .iter()
269 .map(|(&id, value)| (id, f64::from_bits(value.load(Ordering::Relaxed))))
270 .collect()
271 }
272
273 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
274 self.set_parameter_at(param_id, value, 0)
275 }
276
277 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
278 if let Some(slot) = self.param_values.get(¶m_id) {
279 slot.store(value.to_bits(), Ordering::Relaxed);
280 } else {
281 tracing::warn!("LV2 set_parameter_at: unknown parameter id {param_id}");
282 }
283
284 if let Some(ref mapping) = self.mapping {
285 let ring = unsafe {
286 let buf = param_ring_ptr(mapping.as_ptr());
287 let (w, r) = param_indices(mapping.as_ptr());
288 RingBuffer::new(buf, w, r, RING_CAPACITY)
289 };
290 let ev = ParameterEvent {
291 param_index: param_id,
292 value: value as f32,
293 sample_offset: 0,
294 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
295 };
296 if !ring.push(ev) {}
297 }
298 Ok(())
299 }
300
301 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
302 Ok(())
303 }
304
305 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
306 Ok(())
307 }
308
309 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
310 false
311 }
312
313 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
314 let (mapping, events) = match (&self.mapping, &self.events) {
315 (Some(m), Some(e)) => (m, e),
316 _ => return Err("LV2 processor not initialized".to_string()),
317 };
318 let ptr = mapping.as_ptr();
319 let header = unsafe { header_mut(ptr) };
320
321 tracing::info!("LV2 snapshot_state: sending request to host");
322 header.request_type.store(1, Ordering::Release);
323 header.request_status.store(0, Ordering::Release);
324 if let Err(e) = events.signal_host() {
325 header.request_type.store(0, Ordering::Release);
326 return Err(format!("Failed to signal host for state save: {}", e));
327 }
328
329 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
330 header.request_type.store(0, Ordering::Release);
331 return Err(format!("Host did not respond to state save: {}", e));
332 }
333
334 let status = header.request_status.load(Ordering::Acquire);
335 let size = header.scratch_size.load(Ordering::Acquire) as usize;
336 tracing::info!(status, size, "LV2 snapshot_state: host responded");
337 if status != 1 {
338 header.request_type.store(0, Ordering::Release);
339 return Err("State save failed in host".to_string());
340 }
341
342 let scratch = unsafe { scratch_ptr(ptr) };
343 let mut bytes = vec![0u8; size];
344 unsafe {
345 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
346 }
347 header.request_type.store(0, Ordering::Release);
348 Ok(bytes)
349 }
350
351 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
352 let (mapping, events) = match (&self.mapping, &self.events) {
353 (Some(m), Some(e)) => (m, e),
354 _ => return Err("LV2 processor not initialized".to_string()),
355 };
356 let ptr = mapping.as_ptr();
357 let header = unsafe { header_mut(ptr) };
358
359 let scratch = unsafe { scratch_ptr(ptr) };
360 let size = state.len().min(SCRATCH_SIZE);
361 unsafe {
362 std::ptr::copy_nonoverlapping(state.as_ptr(), scratch, size);
363 }
364 header.scratch_size.store(size as u32, Ordering::Release);
365
366 header.request_type.store(2, Ordering::Release);
367 header.request_status.store(0, Ordering::Release);
368 if let Err(e) = events.signal_host() {
369 header.request_type.store(0, Ordering::Release);
370 return Err(format!("Failed to signal host for state restore: {}", e));
371 }
372
373 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
374 header.request_type.store(0, Ordering::Release);
375 return Err(format!("Host did not respond to state restore: {}", e));
376 }
377
378 let status = header.request_status.load(Ordering::Acquire);
379 header.request_type.store(0, Ordering::Release);
380 if status != 1 {
381 return Err("State restore failed in host".to_string());
382 }
383 Ok(())
384 }
385
386 fn deserialize_lv2_control_ports(
387 scratch: *const u8,
388 size: usize,
389 ) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
390 if size < 4 {
391 return Err("scratch too small for LV2 control ports".to_string());
392 }
393 let mut offset = 0usize;
394
395 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
396 offset += 4;
397
398 let mut ports = Vec::with_capacity(count);
399 for _ in 0..count {
400 if offset + 4 > size {
401 return Err("scratch underflow".to_string());
402 }
403 let index = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) };
404 offset += 4;
405
406 if offset + 4 > size {
407 return Err("scratch underflow".to_string());
408 }
409 let name_len =
410 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
411 offset += 4;
412 if offset + name_len > size {
413 return Err("scratch underflow".to_string());
414 }
415 let mut name_bytes = vec![0u8; name_len];
416 unsafe {
417 std::ptr::copy_nonoverlapping(
418 scratch.add(offset),
419 name_bytes.as_mut_ptr(),
420 name_len,
421 );
422 }
423 offset += name_len;
424 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
425
426 if offset + 12 > size {
427 return Err("scratch underflow".to_string());
428 }
429 let min = f32::from_bits(unsafe {
430 std::ptr::read_unaligned(scratch.add(offset) as *const u32)
431 });
432 let max = f32::from_bits(unsafe {
433 std::ptr::read_unaligned(scratch.add(offset + 4) as *const u32)
434 });
435 let value = f32::from_bits(unsafe {
436 std::ptr::read_unaligned(scratch.add(offset + 8) as *const u32)
437 });
438 offset += 12;
439
440 ports.push(crate::message::Lv2ControlPortInfo {
441 index,
442 name,
443 min,
444 max,
445 value,
446 });
447 }
448
449 Ok(ports)
450 }
451
452 fn deserialize_lv2_note_names(
453 scratch: *const u8,
454 size: usize,
455 ) -> Result<HashMap<u8, String>, String> {
456 if size < 4 {
457 return Err("scratch too small for LV2 note names".to_string());
458 }
459 let mut offset = 0usize;
460 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
461 offset += 4;
462
463 let mut note_names = HashMap::with_capacity(count);
464 for _ in 0..count {
465 if offset + 4 > size {
466 return Err("scratch underflow".to_string());
467 }
468 let note = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as u8;
469 offset += 4;
470
471 if offset + 4 > size {
472 return Err("scratch underflow".to_string());
473 }
474 let name_len =
475 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
476 offset += 4;
477 if offset + name_len > size {
478 return Err("scratch underflow".to_string());
479 }
480 let mut name_bytes = vec![0u8; name_len];
481 unsafe {
482 std::ptr::copy_nonoverlapping(
483 scratch.add(offset),
484 name_bytes.as_mut_ptr(),
485 name_len,
486 );
487 }
488 offset += name_len;
489 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
490 note_names.insert(note, name);
491 }
492
493 Ok(note_names)
494 }
495
496 pub fn control_ports(&self) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
497 let (mapping, events) = match (&self.mapping, &self.events) {
498 (Some(m), Some(e)) => (m, e),
499 _ => return Err("LV2 processor not initialized".to_string()),
500 };
501 let ptr = mapping.as_ptr();
502 let header = unsafe { header_mut(ptr) };
503
504 tracing::info!("LV2 control_ports: sending request to host");
505 header.request_type.store(
506 maolan_plugin_protocol::protocol::REQUEST_LV2_CONTROL_PORTS,
507 Ordering::Release,
508 );
509 header.request_status.store(0, Ordering::Release);
510 if let Err(e) = events.signal_host() {
511 header.request_type.store(0, Ordering::Release);
512 return Err(format!("Failed to signal host for LV2 control ports: {e}"));
513 }
514
515 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
516 header.request_type.store(0, Ordering::Release);
517 return Err(format!("Host did not respond to LV2 control ports: {e}"));
518 }
519
520 let status = header.request_status.load(Ordering::Acquire);
521 let size = header.scratch_size.load(Ordering::Acquire) as usize;
522 tracing::info!(status, size, "LV2 control_ports: host responded");
523 if status != 1 {
524 header.request_type.store(0, Ordering::Release);
525 return Err("LV2 control port enumeration failed in host".to_string());
526 }
527
528 let scratch = unsafe { scratch_ptr(ptr) };
529 let result = Self::deserialize_lv2_control_ports(scratch, size);
530 match &result {
531 Ok(ports) => tracing::info!(count = ports.len(), "LV2 control_ports: deserialized"),
532 Err(e) => tracing::error!("LV2 control_ports: deserialize failed: {e}"),
533 }
534 header.request_type.store(0, Ordering::Release);
535 result
536 }
537
538 pub fn note_names(&self) -> Result<HashMap<u8, String>, String> {
539 let (mapping, events) = match (&self.mapping, &self.events) {
540 (Some(m), Some(e)) => (m, e),
541 _ => return Err("LV2 processor not initialized".to_string()),
542 };
543 let ptr = mapping.as_ptr();
544 let header = unsafe { header_mut(ptr) };
545
546 header.request_type.store(
547 maolan_plugin_protocol::protocol::REQUEST_LV2_MIDNAM,
548 Ordering::Release,
549 );
550 header.request_status.store(0, Ordering::Release);
551 if let Err(e) = events.signal_host() {
552 header.request_type.store(0, Ordering::Release);
553 return Err(format!("Failed to signal host for LV2 midnam: {e}"));
554 }
555
556 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
557 header.request_type.store(0, Ordering::Release);
558 return Err(format!("Host did not respond to LV2 midnam: {e}"));
559 }
560
561 let status = header.request_status.load(Ordering::Acquire);
562 let size = header.scratch_size.load(Ordering::Acquire) as usize;
563 if status != 1 {
564 header.request_type.store(0, Ordering::Release);
565 return Err("LV2 midnam enumeration failed in host".to_string());
566 }
567
568 let scratch = unsafe { scratch_ptr(ptr) };
569 let result = Self::deserialize_lv2_note_names(scratch, size);
570 header.request_type.store(0, Ordering::Release);
571 result
572 }
573
574 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
575 let (mapping, events) = match (&self.mapping, &self.events) {
576 (Some(m), Some(e)) => (m, e),
577 _ => return Err("LV2 processor not initialized".to_string()),
578 };
579 let ptr = mapping.as_ptr();
580 let header = unsafe { header_mut(ptr) };
581 let path_str = dir.to_string_lossy().to_string();
582 unsafe {
583 write_resource_directory_to_scratch(ptr, &path_str)
584 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
585 }
586 std::sync::atomic::fence(Ordering::SeqCst);
587
588 header.request_type.store(5, Ordering::Release);
589 header.request_status.store(0, Ordering::Release);
590 if let Err(e) = events.signal_host() {
591 header.request_type.store(0, Ordering::Release);
592 return Err(format!("Failed to signal host for resource directory: {e}"));
593 }
594
595 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
596 header.request_type.store(0, Ordering::Release);
597 return Err(format!("Host did not respond to resource directory: {e}"));
598 }
599
600 let status = header.request_status.load(Ordering::Acquire);
601 header.request_type.store(0, Ordering::Release);
602 if status != 1 {
603 return Err("Resource directory update failed in host".to_string());
604 }
605 Ok(())
606 }
607
608 pub fn process_with_audio_buffers(
609 &self,
610 frames: usize,
611 audio_inputs: &[&[f32]],
612 audio_outputs: &mut [&mut [f32]],
613 ) -> Vec<MidiEvent> {
614 if self.bypassed.load(Ordering::Relaxed) {
615 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
616 return Vec::new();
617 }
618
619 let crashed = unsafe {
622 self.with_child(|child| {
623 if let Some(c) = child.as_mut()
624 && let Ok(Some(status)) = c.try_wait()
625 && !status.success()
626 {
627 self.crash_count.fetch_add(1, Ordering::Relaxed);
628 return true;
629 }
630 false
631 })
632 };
633 if crashed {
634 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
635 return Vec::new();
636 }
637
638 let (mapping, events) = match (&self.mapping, &self.events) {
639 (Some(m), Some(e)) => (m, e),
640 _ => {
641 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
642 return Vec::new();
643 }
644 };
645
646 let ptr = mapping.as_ptr();
647 let num_in = audio_inputs.len();
648 let num_out = audio_outputs.len();
649 let midi_in_count = self.midi_input_ports.len();
650 let midi_out_count = self.midi_output_ports.len();
651 unsafe {
652 ipc::configure_shm_header(ptr, frames, num_in, num_out, midi_in_count, midi_out_count);
653
654 let t = transport_mut(ptr);
655 t.playhead_sample = 0;
656 t.tempo = 120.0;
657 t.numerator = 4;
658 t.denominator = 4;
659 t.flags = 1;
660
661 ipc::copy_input_slices_to_shm(audio_inputs, ptr, frames);
662
663 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
664 let buf = midi_in_ring_ptr(ptr, port_idx);
665 let (w, r) = midi_in_indices(ptr, port_idx);
666 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
667 let port_buffer = port.buffer();
672 for ev in port_buffer {
673 let data = {
674 let mut d = [0u8; 3];
675 for (i, b) in ev.data.iter().enumerate().take(3) {
676 d[i] = *b;
677 }
678 d
679 };
680 let _ = ring.push(maolan_plugin_protocol::MidiEvent {
681 sample_offset: ev.frame,
682 data,
683 channel: ev.data.first().copied().unwrap_or(0) & 0x0F,
684 flags: 0,
685 _pad: 0,
686 });
687 }
688 port.mark_finished();
689 }
690 }
691
692 if events.signal_host().is_err() {
693 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
694 return Vec::new();
695 }
696
697 let timeout = Duration::from_millis(100);
698 match events.wait_host(timeout) {
699 Ok(()) => {}
700 Err(_) => {
701 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
702 return Vec::new();
703 }
704 }
705
706 unsafe {
707 ipc::copy_outputs_from_shm_to_slices(audio_outputs, ptr, frames);
708
709 let mut output_events = Vec::new();
710 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
711 let buf = midi_out_ring_ptr(ptr, port_idx);
712 let (w, r) = midi_out_indices(ptr, port_idx);
713 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
714 let mut port_buffer = port.buffer_mut();
717 port_buffer.clear();
718 while let Some(ev) = ring.pop() {
719 let event = MidiEvent {
720 frame: ev.sample_offset,
721 data: ev.data.to_vec(),
722 };
723 port_buffer.push(event.clone());
724 output_events.push(event);
725 }
726 port.mark_finished();
727 }
728 output_events
729 }
730 }
731
732 pub fn uri(&self) -> &str {
733 &self.uri
734 }
735
736 pub fn name(&self) -> &str {
737 &self.name
738 }
739
740 pub fn take_stderr(&self) -> Option<ChildStderr> {
741 self.stderr.swap(None).and_then(|s| Arc::try_unwrap(s).ok())
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 gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
761 if let Some(ref mapping) = self.mapping {
762 let header = unsafe { header_mut(mapping.as_ptr()) };
763 header.set_parent_window(window);
764 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::X11);
765 return Ok(());
766 }
767 Err("No active host to set parent window".to_string())
768 }
769
770 pub fn gui_set_parent_wayland(&self, window: usize) -> Result<(), String> {
771 if let Some(ref mapping) = self.mapping {
772 let header = unsafe { header_mut(mapping.as_ptr()) };
773 header.set_parent_window(window);
774 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::Wayland);
775 return Ok(());
776 }
777 Err("No active host to set parent window".to_string())
778 }
779
780 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
781 if let Some(ref mapping) = self.mapping {
782 let header = unsafe { header_mut(mapping.as_ptr()) };
783 header.set_gui_mode(if floating {
784 GuiMode::Floating
785 } else {
786 GuiMode::Embedded
787 });
788 if floating {
789 header.set_parent_window(0);
790 header.set_gui_parent_api(maolan_plugin_protocol::protocol::GuiParentApi::None);
791 }
792 return Ok(());
793 }
794 Err("No active host to set GUI mode".to_string())
795 }
796
797 pub fn gui_show(&self) -> Result<(), String> {
798 if let Some(ref mapping) = self.mapping
799 && let Some(ref events) = self.events
800 {
801 let header = unsafe { header_mut(mapping.as_ptr()) };
802 header.request_type.store(3, Ordering::Release);
803 let _ = events.signal_host();
804 return Ok(());
805 }
806 Err("No active host to show GUI".to_string())
807 }
808
809 pub fn gui_hide(&self) {
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(4, Ordering::Release);
815 let _ = events.signal_host();
816 }
817 }
818
819 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
820 let mut result = Vec::new();
821 if let Some(ref mapping) = self.mapping {
822 let ring = unsafe {
823 let buf = echo_ring_ptr(mapping.as_ptr());
824 let (w, r) = echo_indices(mapping.as_ptr());
825 RingBuffer::new(buf, w, r, RING_CAPACITY)
826 };
827 while let Some(ev) = ring.pop() {
828 result.push(ev);
829 }
830 }
831 result
832 }
833
834 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
835 let mut result = Vec::new();
836 if let Some(ref mapping) = self.mapping {
837 let ring = unsafe {
838 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
839 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
840 RingBuffer::new(buf, w, r, RING_CAPACITY)
841 };
842 while let Some(ev) = ring.pop() {
843 result.push(crate::midi::io::MidiEvent {
844 frame: ev.sample_offset,
845 data: ev.data.to_vec(),
846 });
847 }
848 }
849 result
850 }
851}
852
853impl Drop for Lv2Processor {
854 fn drop(&mut self) {
855 let mapping = self.mapping.take();
856 let events = self.events.take();
857 let child = self.child.get_mut().take();
858 let shm_name = std::mem::take(&mut self.shm_name);
859 ipc::drop_host(mapping, events, child, shm_name);
860 }
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
866
867 fn find_host_binary() -> PathBuf {
868 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
869 }
870
871 #[cfg_attr(
872 all(miri, target_os = "freebsd"),
873 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
874 )]
875 #[test]
876 fn find_host_binary_locates_binary() {
877 let host_bin = find_host_binary();
878 assert!(
879 host_bin.exists(),
880 "plugin-host binary should exist at {}",
881 host_bin.display()
882 );
883 }
884
885 #[cfg_attr(
886 all(miri, target_os = "freebsd"),
887 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
888 )]
889 #[test]
890 fn lv2_processor_crash_bypass() {
891 let host_bin = find_host_binary();
892
893 let processor = Lv2Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
894 .expect("should create LV2 processor for crash test");
895
896 processor.setup_audio_ports();
897
898 let input_buffers = [vec![1.0; 256]];
899 let mut output_buffers = [vec![0.0; 256]];
900 let inputs = input_buffers.iter().map(Vec::as_slice).collect::<Vec<_>>();
901 let mut outputs = output_buffers
902 .iter_mut()
903 .map(Vec::as_mut_slice)
904 .collect::<Vec<_>>();
905 processor.process_with_audio_buffers(256, &inputs, &mut outputs);
906
907 let out_buf = &output_buffers[0];
908 let first_few: Vec<f32> = out_buf.iter().take(10).copied().collect();
909 assert!(
910 out_buf.iter().all(|&s| s == 1.0),
911 "after crash, output should be bypass copy of input, got: {:?}",
912 first_few
913 );
914 }
915}