1use crate::audio::io::AudioIO;
2use crate::midi::io::{MIDIIO, MidiEvent};
3use crate::mutex::UnsafeMutex;
4use crate::plugins::ipc;
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::collections::HashMap;
10use std::path::PathBuf;
11use std::process::{Child, ChildStderr};
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::{Arc, atomic::AtomicU32};
14use std::time::{Duration, Instant};
15
16fn wait_for_host_request_complete(
17 header: &ShmHeader,
18 events: &EventPair,
19 timeout: Duration,
20) -> Result<(), String> {
21 let start = Instant::now();
22 loop {
23 if header.request_status.load(Ordering::Acquire) != 0 {
24 return Ok(());
25 }
26 let elapsed = start.elapsed();
27 if elapsed >= timeout {
28 return Err("Host did not respond to request".to_string());
29 }
30 match events.wait_host(timeout - elapsed) {
31 Ok(()) => {
32 std::sync::atomic::fence(Ordering::Acquire);
33 continue;
34 }
35 Err(e) if e.kind() == std::io::ErrorKind::TimedOut => continue,
36 Err(e) => return Err(format!("Host did not respond to request: {e}")),
37 }
38 }
39}
40
41pub struct Lv2Processor {
42 uri: String,
43 name: String,
44 audio_inputs: Vec<Arc<AudioIO>>,
45 audio_outputs: Vec<Arc<AudioIO>>,
46 main_audio_inputs: usize,
47 main_audio_outputs: usize,
48 midi_input_ports: Vec<Arc<UnsafeMutex<Box<MIDIIO>>>>,
49 midi_output_ports: Vec<Arc<UnsafeMutex<Box<MIDIIO>>>>,
50 param_values: UnsafeMutex<HashMap<u32, f64>>,
51 bypassed: Arc<AtomicBool>,
52
53 child: UnsafeMutex<Option<Child>>,
54 stderr: UnsafeMutex<Option<ChildStderr>>,
55 mapping: Option<ShmMapping>,
56 events: Option<EventPair>,
57 shm_name: String,
58
59 crash_count: AtomicU32,
60 last_process_time: UnsafeMutex<Instant>,
61}
62
63pub type SharedLv2Processor = Arc<UnsafeMutex<Lv2Processor>>;
64
65impl Lv2Processor {
66 pub fn new(
67 sample_rate: f64,
68 buffer_size: usize,
69 plugin_uri: &str,
70 input_count: usize,
71 output_count: usize,
72 host_binary: PathBuf,
73 ) -> Result<Self, String> {
74 let audio_inputs = (0..input_count.max(1))
75 .map(|_| Arc::new(AudioIO::new(buffer_size)))
76 .collect::<Vec<_>>();
77 let audio_outputs = (0..output_count.max(1))
78 .map(|_| Arc::new(AudioIO::new(buffer_size)))
79 .collect::<Vec<_>>();
80
81 let instance_id = ipc::unique_instance_id("lv2");
82 let sample_rate_str = sample_rate.to_string();
83 let buffer_size_str = buffer_size.to_string();
84 let num_inputs_str = input_count.max(1).to_string();
85 let num_outputs_str = output_count.max(1).to_string();
86 let (mut child, mapping, events, shm_name, stderr) = ipc::spawn_host(ipc::HostSpawnArgs {
87 host_binary: &host_binary,
88 format: "lv2",
89 plugin_spec: plugin_uri,
90 instance_id: &instance_id,
91 extra_args: &[
92 &sample_rate_str,
93 &buffer_size_str,
94 &num_inputs_str,
95 &num_outputs_str,
96 ],
97 })?;
98
99 let header = unsafe { header_ref(mapping.as_ptr()) };
100 if !ipc::wait_for_ready(header, Duration::from_secs(10)) {
101 let _ = child.kill();
102 return Err("LV2 host did not signal ready".to_string());
103 }
104
105 let name = unsafe {
106 maolan_plugin_protocol::protocol::read_plugin_name_from_scratch(mapping.as_ptr())
107 .unwrap_or_else(|| {
108 plugin_uri
109 .rsplit_once('/')
110 .map(|(_, name)| name)
111 .unwrap_or(plugin_uri)
112 .to_string()
113 })
114 };
115
116 let header = unsafe { header_ref(mapping.as_ptr()) };
117 let midi_in_count = header.midi_in_port_count.load(Ordering::Acquire) as usize;
118 let midi_out_count = header.midi_out_port_count.load(Ordering::Acquire) as usize;
119 let midi_input_ports: Vec<_> = (0..midi_in_count)
120 .map(|_| Arc::new(UnsafeMutex::new(Box::new(MIDIIO::new()))))
121 .collect();
122 let midi_output_ports: Vec<_> = (0..midi_out_count)
123 .map(|_| Arc::new(UnsafeMutex::new(Box::new(MIDIIO::new()))))
124 .collect();
125
126 Ok(Self {
127 uri: plugin_uri.to_string(),
128 name,
129 audio_inputs,
130 audio_outputs,
131 main_audio_inputs: input_count.max(1),
132 main_audio_outputs: output_count.max(1),
133 midi_input_ports,
134 midi_output_ports,
135 param_values: UnsafeMutex::new(HashMap::new()),
136 bypassed: Arc::new(AtomicBool::new(false)),
137 child: UnsafeMutex::new(Some(child)),
138 stderr: UnsafeMutex::new(stderr),
139 mapping: Some(mapping),
140 events: Some(events),
141 shm_name,
142 crash_count: AtomicU32::new(0),
143 last_process_time: UnsafeMutex::new(Instant::now()),
144 })
145 }
146
147 pub fn setup_audio_ports(&self) {
148 for port in &self.audio_inputs {
149 port.setup();
150 }
151 for port in &self.audio_outputs {
152 port.setup();
153 }
154 }
155
156 pub fn setup_midi_ports(&self) {
157 for port in &self.midi_input_ports {
158 port.lock().setup();
159 }
160 for port in &self.midi_output_ports {
161 port.lock().setup();
162 }
163 }
164
165 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
166 &self.audio_inputs
167 }
168
169 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
170 &self.audio_outputs
171 }
172
173 pub fn main_audio_input_count(&self) -> usize {
174 self.main_audio_inputs
175 }
176
177 pub fn main_audio_output_count(&self) -> usize {
178 self.main_audio_outputs
179 }
180
181 pub fn midi_input_count(&self) -> usize {
182 self.midi_input_ports.len()
183 }
184
185 pub fn midi_output_count(&self) -> usize {
186 self.midi_output_ports.len()
187 }
188
189 pub fn midi_input_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
190 &self.midi_input_ports
191 }
192
193 pub fn midi_output_ports(&self) -> &[Arc<UnsafeMutex<Box<MIDIIO>>>] {
194 &self.midi_output_ports
195 }
196
197 pub fn set_bypassed(&self, bypassed: bool) {
198 self.bypassed.store(bypassed, Ordering::Relaxed);
199 }
200
201 pub fn is_bypassed(&self) -> bool {
202 self.bypassed.load(Ordering::Relaxed)
203 }
204
205 pub fn parameter_values(&self) -> HashMap<u32, f64> {
206 self.param_values.lock().clone()
207 }
208
209 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
210 self.set_parameter_at(param_id, value, 0)
211 }
212
213 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
214 self.param_values.lock().insert(param_id, value);
215 if let Some(ref mapping) = self.mapping {
216 let ring = unsafe {
217 let buf = param_ring_ptr(mapping.as_ptr());
218 let (w, r) = param_indices(mapping.as_ptr());
219 RingBuffer::new(buf, w, r, RING_CAPACITY)
220 };
221 let ev = ParameterEvent {
222 param_index: param_id,
223 value: value as f32,
224 sample_offset: 0,
225 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
226 };
227 if !ring.push(ev) {}
228 }
229 Ok(())
230 }
231
232 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
233 Ok(())
234 }
235
236 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
237 Ok(())
238 }
239
240 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
241 false
242 }
243
244 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
245 let (mapping, events) = match (&self.mapping, &self.events) {
246 (Some(m), Some(e)) => (m, e),
247 _ => return Err("LV2 processor not initialized".to_string()),
248 };
249 let ptr = mapping.as_ptr();
250 let header = unsafe { header_mut(ptr) };
251
252 tracing::info!("LV2 snapshot_state: sending request to host");
253 header.request_type.store(1, Ordering::Release);
254 header.request_status.store(0, Ordering::Release);
255 if let Err(e) = events.signal_host() {
256 header.request_type.store(0, Ordering::Release);
257 return Err(format!("Failed to signal host for state save: {}", e));
258 }
259
260 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
261 header.request_type.store(0, Ordering::Release);
262 return Err(format!("Host did not respond to state save: {}", e));
263 }
264
265 let status = header.request_status.load(Ordering::Acquire);
266 let size = header.scratch_size.load(Ordering::Acquire) as usize;
267 tracing::info!(status, size, "LV2 snapshot_state: host responded");
268 if status != 1 {
269 header.request_type.store(0, Ordering::Release);
270 return Err("State save failed in host".to_string());
271 }
272
273 let scratch = unsafe { scratch_ptr(ptr) };
274 let mut bytes = vec![0u8; size];
275 unsafe {
276 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
277 }
278 header.request_type.store(0, Ordering::Release);
279 Ok(bytes)
280 }
281
282 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
283 let (mapping, events) = match (&self.mapping, &self.events) {
284 (Some(m), Some(e)) => (m, e),
285 _ => return Err("LV2 processor not initialized".to_string()),
286 };
287 let ptr = mapping.as_ptr();
288 let header = unsafe { header_mut(ptr) };
289
290 let scratch = unsafe { scratch_ptr(ptr) };
291 let size = state.len().min(SCRATCH_SIZE);
292 unsafe {
293 std::ptr::copy_nonoverlapping(state.as_ptr(), scratch, size);
294 }
295 header.scratch_size.store(size as u32, Ordering::Release);
296
297 header.request_type.store(2, Ordering::Release);
298 header.request_status.store(0, Ordering::Release);
299 if let Err(e) = events.signal_host() {
300 header.request_type.store(0, Ordering::Release);
301 return Err(format!("Failed to signal host for state restore: {}", e));
302 }
303
304 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
305 header.request_type.store(0, Ordering::Release);
306 return Err(format!("Host did not respond to state restore: {}", e));
307 }
308
309 let status = header.request_status.load(Ordering::Acquire);
310 header.request_type.store(0, Ordering::Release);
311 if status != 1 {
312 return Err("State restore failed in host".to_string());
313 }
314 Ok(())
315 }
316
317 fn deserialize_lv2_control_ports(
318 scratch: *const u8,
319 size: usize,
320 ) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
321 if size < 4 {
322 return Err("scratch too small for LV2 control ports".to_string());
323 }
324 let mut offset = 0usize;
325
326 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
327 offset += 4;
328
329 let mut ports = Vec::with_capacity(count);
330 for _ in 0..count {
331 if offset + 4 > size {
332 return Err("scratch underflow".to_string());
333 }
334 let index = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) };
335 offset += 4;
336
337 if offset + 4 > size {
338 return Err("scratch underflow".to_string());
339 }
340 let name_len =
341 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
342 offset += 4;
343 if offset + name_len > size {
344 return Err("scratch underflow".to_string());
345 }
346 let mut name_bytes = vec![0u8; name_len];
347 unsafe {
348 std::ptr::copy_nonoverlapping(
349 scratch.add(offset),
350 name_bytes.as_mut_ptr(),
351 name_len,
352 );
353 }
354 offset += name_len;
355 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
356
357 if offset + 12 > size {
358 return Err("scratch underflow".to_string());
359 }
360 let min = f32::from_bits(unsafe {
361 std::ptr::read_unaligned(scratch.add(offset) as *const u32)
362 });
363 let max = f32::from_bits(unsafe {
364 std::ptr::read_unaligned(scratch.add(offset + 4) as *const u32)
365 });
366 let value = f32::from_bits(unsafe {
367 std::ptr::read_unaligned(scratch.add(offset + 8) as *const u32)
368 });
369 offset += 12;
370
371 ports.push(crate::message::Lv2ControlPortInfo {
372 index,
373 name,
374 min,
375 max,
376 value,
377 });
378 }
379
380 Ok(ports)
381 }
382
383 pub fn control_ports(&self) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
384 let (mapping, events) = match (&self.mapping, &self.events) {
385 (Some(m), Some(e)) => (m, e),
386 _ => return Err("LV2 processor not initialized".to_string()),
387 };
388 let ptr = mapping.as_ptr();
389 let header = unsafe { header_mut(ptr) };
390
391 tracing::info!("LV2 control_ports: sending request to host");
392 header.request_type.store(
393 maolan_plugin_protocol::protocol::REQUEST_LV2_CONTROL_PORTS,
394 Ordering::Release,
395 );
396 header.request_status.store(0, Ordering::Release);
397 if let Err(e) = events.signal_host() {
398 header.request_type.store(0, Ordering::Release);
399 return Err(format!("Failed to signal host for LV2 control ports: {e}"));
400 }
401
402 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
403 header.request_type.store(0, Ordering::Release);
404 return Err(format!("Host did not respond to LV2 control ports: {e}"));
405 }
406
407 let status = header.request_status.load(Ordering::Acquire);
408 let size = header.scratch_size.load(Ordering::Acquire) as usize;
409 tracing::info!(status, size, "LV2 control_ports: host responded");
410 if status != 1 {
411 header.request_type.store(0, Ordering::Release);
412 return Err("LV2 control port enumeration failed in host".to_string());
413 }
414
415 let scratch = unsafe { scratch_ptr(ptr) };
416 let result = Self::deserialize_lv2_control_ports(scratch, size);
417 match &result {
418 Ok(ports) => tracing::info!(count = ports.len(), "LV2 control_ports: deserialized"),
419 Err(e) => tracing::error!("LV2 control_ports: deserialize failed: {e}"),
420 }
421 header.request_type.store(0, Ordering::Release);
422 result
423 }
424
425 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
426 let (mapping, events) = match (&self.mapping, &self.events) {
427 (Some(m), Some(e)) => (m, e),
428 _ => return Err("LV2 processor not initialized".to_string()),
429 };
430 let ptr = mapping.as_ptr();
431 let header = unsafe { header_mut(ptr) };
432 let path_str = dir.to_string_lossy().to_string();
433 unsafe {
434 write_resource_directory_to_scratch(ptr, &path_str)
435 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
436 }
437 std::sync::atomic::fence(Ordering::SeqCst);
438
439 header.request_type.store(5, Ordering::Release);
440 header.request_status.store(0, Ordering::Release);
441 if let Err(e) = events.signal_host() {
442 header.request_type.store(0, Ordering::Release);
443 return Err(format!("Failed to signal host for resource directory: {e}"));
444 }
445
446 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
447 header.request_type.store(0, Ordering::Release);
448 return Err(format!("Host did not respond to resource directory: {e}"));
449 }
450
451 let status = header.request_status.load(Ordering::Acquire);
452 header.request_type.store(0, Ordering::Release);
453 if status != 1 {
454 return Err("Resource directory update failed in host".to_string());
455 }
456 Ok(())
457 }
458
459 pub fn process_with_audio_io(&self, frames: usize) {
460 let _ = self.process_with_midi(frames, &[]);
461 }
462
463 pub fn process_with_midi(&self, frames: usize, _midi_in: &[MidiEvent]) -> Vec<MidiEvent> {
464 if self.bypassed.load(Ordering::Relaxed) {
465 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
466 return Vec::new();
467 }
468
469 {
470 let child = self.child.lock();
471 if let Some(ref mut c) = child.as_mut() {
472 match c.try_wait() {
473 Ok(Some(status)) if !status.success() => {
474 self.crash_count.fetch_add(1, Ordering::Relaxed);
475 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
476 return Vec::new();
477 }
478 Ok(Some(_status)) => {}
479 Ok(None) => {}
480 Err(_) => {}
481 }
482 }
483 }
484
485 let (mapping, events) = match (&self.mapping, &self.events) {
486 (Some(m), Some(e)) => (m, e),
487 _ => {
488 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
489 return Vec::new();
490 }
491 };
492
493 let ptr = mapping.as_ptr();
494 let num_in = self.audio_inputs.len();
495 let num_out = self.audio_outputs.len();
496 let midi_in_count = self.midi_input_ports.len();
497 let midi_out_count = self.midi_output_ports.len();
498 unsafe {
499 ipc::configure_shm_header(ptr, frames, num_in, num_out, midi_in_count, midi_out_count);
500
501 let t = transport_mut(ptr);
502 t.playhead_sample = 0;
503 t.tempo = 120.0;
504 t.numerator = 4;
505 t.denominator = 4;
506 t.flags = 1;
507
508 ipc::copy_inputs_to_shm(&self.audio_inputs, ptr, frames);
509
510 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
511 let buf = midi_in_ring_ptr(ptr, port_idx);
512 let (w, r) = midi_in_indices(ptr, port_idx);
513 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
514 let lock = port.lock();
515 for ev in &lock.buffer {
516 let data = {
517 let mut d = [0u8; 3];
518 for (i, b) in ev.data.iter().enumerate().take(3) {
519 d[i] = *b;
520 }
521 d
522 };
523 let _ = ring.push(maolan_plugin_protocol::MidiEvent {
524 sample_offset: ev.frame,
525 data,
526 channel: ev.data.first().copied().unwrap_or(0) & 0x0F,
527 flags: 0,
528 _pad: 0,
529 });
530 }
531 lock.mark_finished();
532 }
533 }
534
535 if events.signal_host().is_err() {
536 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
537 return Vec::new();
538 }
539
540 let timeout = Duration::from_millis(100);
541 match events.wait_host(timeout) {
542 Ok(()) => {}
543 Err(_) => {
544 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
545 return Vec::new();
546 }
547 }
548
549 unsafe {
550 ipc::copy_outputs_from_shm(&self.audio_outputs, ptr, frames);
551
552 let mut output_events = Vec::new();
553 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
554 let buf = midi_out_ring_ptr(ptr, port_idx);
555 let (w, r) = midi_out_indices(ptr, port_idx);
556 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
557 let lock = port.lock();
558 lock.buffer.clear();
559 while let Some(ev) = ring.pop() {
560 let event = MidiEvent {
561 frame: ev.sample_offset,
562 data: ev.data.to_vec(),
563 };
564 lock.buffer.push(event.clone());
565 output_events.push(event);
566 }
567 lock.mark_finished();
568 }
569 *self.last_process_time.lock() = Instant::now();
570 output_events
571 }
572 }
573
574 pub fn uri(&self) -> &str {
575 &self.uri
576 }
577
578 pub fn name(&self) -> &str {
579 &self.name
580 }
581
582 pub fn take_stderr(&self) -> Option<ChildStderr> {
583 self.stderr.lock().take()
584 }
585
586 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
587 Ok(())
588 }
589
590 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
591 Ok(())
592 }
593
594 pub fn run_host_callbacks_main_thread(&self) {}
595
596 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
597 Ok(false)
598 }
599
600 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
601 if let Some(ref mapping) = self.mapping {
602 let header = unsafe { header_mut(mapping.as_ptr()) };
603 header.set_parent_window(window);
604 return Ok(());
605 }
606 Err("No active host to set parent window".to_string())
607 }
608
609 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
610 if let Some(ref mapping) = self.mapping {
611 let header = unsafe { header_mut(mapping.as_ptr()) };
612 header.set_gui_mode(if floating {
613 GuiMode::Floating
614 } else {
615 GuiMode::Embedded
616 });
617 return Ok(());
618 }
619 Err("No active host to set GUI mode".to_string())
620 }
621
622 pub fn gui_show(&self) -> Result<(), String> {
623 if let Some(ref mapping) = self.mapping
624 && let Some(ref events) = self.events
625 {
626 let header = unsafe { header_mut(mapping.as_ptr()) };
627 header.request_type.store(3, Ordering::Release);
628 let _ = events.signal_host();
629 return Ok(());
630 }
631 Err("No active host to show GUI".to_string())
632 }
633
634 pub fn gui_hide(&self) {
635 if let Some(ref mapping) = self.mapping
636 && let Some(ref events) = self.events
637 {
638 let header = unsafe { header_mut(mapping.as_ptr()) };
639 header.request_type.store(4, Ordering::Release);
640 let _ = events.signal_host();
641 }
642 }
643
644 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
645 let mut result = Vec::new();
646 if let Some(ref mapping) = self.mapping {
647 let ring = unsafe {
648 let buf = echo_ring_ptr(mapping.as_ptr());
649 let (w, r) = echo_indices(mapping.as_ptr());
650 RingBuffer::new(buf, w, r, RING_CAPACITY)
651 };
652 while let Some(ev) = ring.pop() {
653 result.push(ev);
654 }
655 }
656 result
657 }
658
659 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
660 let mut result = Vec::new();
661 if let Some(ref mapping) = self.mapping {
662 let ring = unsafe {
663 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
664 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
665 RingBuffer::new(buf, w, r, RING_CAPACITY)
666 };
667 while let Some(ev) = ring.pop() {
668 result.push(crate::midi::io::MidiEvent {
669 frame: ev.sample_offset,
670 data: ev.data.to_vec(),
671 });
672 }
673 }
674 result
675 }
676}
677
678impl Drop for Lv2Processor {
679 fn drop(&mut self) {
680 let mapping = self.mapping.take();
681 let events = self.events.take();
682 let child = self.child.lock().take();
683 let shm_name = std::mem::take(&mut self.shm_name);
684 ipc::drop_host(mapping, events, child, shm_name);
685 }
686}
687
688crate::impl_ipc_processor_wrapper!(Lv2Processor);
689
690impl UnsafeMutex<Lv2Processor> {
691 pub fn process_with_midi(&self, frames: usize, midi_events: &[MidiEvent]) -> Vec<MidiEvent> {
692 self.lock().process_with_midi(frames, midi_events)
693 }
694
695 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
696 self.lock().snapshot_state()
697 }
698
699 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
700 self.lock().restore_state(state)
701 }
702
703 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
704 self.lock().drain_echoed_parameters()
705 }
706
707 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
708 self.lock().drain_midi_outputs()
709 }
710
711 pub fn control_ports(&self) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
712 self.lock().control_ports()
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 fn find_host_binary() -> PathBuf {
721 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
722 }
723
724 #[test]
725 fn find_host_binary_locates_binary() {
726 let host_bin = find_host_binary();
727 assert!(
728 host_bin.exists(),
729 "plugin-host binary should exist at {}",
730 host_bin.display()
731 );
732 }
733
734 #[test]
735 fn lv2_processor_crash_bypass() {
736 let host_bin = find_host_binary();
737
738 let processor = Lv2Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
739 .expect("should create LV2 processor for crash test");
740
741 processor.setup_audio_ports();
742
743 {
744 let buf = processor.audio_inputs()[0].buffer.lock();
745 buf.fill(1.0);
746 *processor.audio_inputs()[0].finished.lock() = true;
747 }
748
749 processor.process_with_audio_io(256);
750
751 let out_buf = processor.audio_outputs()[0].buffer.lock();
752 let first_few: Vec<f32> = out_buf.iter().take(10).copied().collect();
753 assert!(
754 out_buf.iter().all(|&s| s == 1.0),
755 "after crash, output should be bypass copy of input, got: {:?}",
756 first_few
757 );
758 }
759}