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}