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