Skip to main content

maolan_engine/plugins/
lv2_proc.rs

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}