1use crate::audio::io::AudioIO;
4use crate::midi::io::MidiEvent;
5use crate::mutex::UnsafeMutex;
6use crate::plugins::ipc;
7use maolan_plugin_protocol::events::EventPair;
8use maolan_plugin_protocol::protocol::*;
9use maolan_plugin_protocol::ringbuf::RingBuffer;
10use maolan_plugin_protocol::shm::ShmMapping;
11use std::collections::HashMap;
12use std::path::PathBuf;
13use std::process::Child;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, atomic::AtomicU32};
16use std::time::{Duration, Instant};
17
18pub struct Lv2Processor {
20 uri: String,
21 name: String,
22 audio_inputs: Vec<Arc<AudioIO>>,
23 audio_outputs: Vec<Arc<AudioIO>>,
24 main_audio_inputs: usize,
25 main_audio_outputs: usize,
26 param_values: UnsafeMutex<HashMap<u32, f64>>,
27 bypassed: Arc<AtomicBool>,
28 child: UnsafeMutex<Option<Child>>,
30 mapping: Option<ShmMapping>,
31 events: Option<EventPair>,
32 shm_name: String,
33 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 = format!("lv2-{}", std::process::id());
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) = 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 = plugin_uri
81 .rsplit_once('/')
82 .map(|(_, name)| name)
83 .unwrap_or(plugin_uri)
84 .to_string();
85
86 Ok(Self {
87 uri: plugin_uri.to_string(),
88 name,
89 audio_inputs,
90 audio_outputs,
91 main_audio_inputs: input_count.max(1),
92 main_audio_outputs: output_count.max(1),
93 param_values: UnsafeMutex::new(HashMap::new()),
94 bypassed: Arc::new(AtomicBool::new(false)),
95 child: UnsafeMutex::new(Some(child)),
96 mapping: Some(mapping),
97 events: Some(events),
98 shm_name,
99 crash_count: AtomicU32::new(0),
100 last_process_time: UnsafeMutex::new(Instant::now()),
101 })
102 }
103
104 pub fn setup_audio_ports(&self) {
105 for port in &self.audio_inputs {
106 port.setup();
107 }
108 for port in &self.audio_outputs {
109 port.setup();
110 }
111 }
112
113 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
114 &self.audio_inputs
115 }
116
117 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
118 &self.audio_outputs
119 }
120
121 pub fn main_audio_input_count(&self) -> usize {
122 self.main_audio_inputs
123 }
124
125 pub fn main_audio_output_count(&self) -> usize {
126 self.main_audio_outputs
127 }
128
129 pub fn midi_input_count(&self) -> usize {
130 0
131 }
132
133 pub fn midi_output_count(&self) -> usize {
134 0
135 }
136
137 pub fn set_bypassed(&self, bypassed: bool) {
138 self.bypassed.store(bypassed, Ordering::Relaxed);
139 }
140
141 pub fn is_bypassed(&self) -> bool {
142 self.bypassed.load(Ordering::Relaxed)
143 }
144
145 pub fn parameter_values(&self) -> HashMap<u32, f64> {
146 self.param_values.lock().clone()
147 }
148
149 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
150 self.set_parameter_at(param_id, value, 0)
151 }
152
153 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
154 self.param_values.lock().insert(param_id, value);
155 if let Some(ref mapping) = self.mapping {
156 let ring = unsafe {
157 let buf = param_ring_ptr(mapping.as_ptr());
158 let (w, r) = param_indices(mapping.as_ptr());
159 RingBuffer::new(buf, w, r, RING_CAPACITY)
160 };
161 let ev = ParameterEvent {
162 param_index: param_id,
163 value: value as f32,
164 sample_offset: 0,
165 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
166 };
167 if !ring.push(ev) {
168 tracing::warn!("LV2 param ring full, dropping parameter event");
169 }
170 }
171 Ok(())
172 }
173
174 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
175 Ok(())
176 }
177
178 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
179 Ok(())
180 }
181
182 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
183 false
184 }
185
186 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
187 let (mapping, events) = match (&self.mapping, &self.events) {
188 (Some(m), Some(e)) => (m, e),
189 _ => return Err("LV2 processor not initialized".to_string()),
190 };
191 let ptr = mapping.as_ptr();
192 let header = unsafe { header_mut(ptr) };
193
194 header.request_type.store(1, Ordering::Release);
195 header.request_status.store(0, Ordering::Release);
196 if let Err(e) = events.signal_host() {
197 header.request_type.store(0, Ordering::Release);
198 return Err(format!("Failed to signal host for state save: {}", e));
199 }
200
201 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
202 header.request_type.store(0, Ordering::Release);
203 return Err(format!("Host did not respond to state save: {}", e));
204 }
205
206 let status = header.request_status.load(Ordering::Acquire);
207 let size = header.scratch_size.load(Ordering::Acquire) as usize;
208 if status != 1 {
209 header.request_type.store(0, Ordering::Release);
210 return Err("State save failed in host".to_string());
211 }
212
213 let scratch = unsafe { scratch_ptr(ptr) };
214 let mut bytes = vec![0u8; size];
215 unsafe {
216 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
217 }
218 header.request_type.store(0, Ordering::Release);
219 Ok(bytes)
220 }
221
222 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
223 let (mapping, events) = match (&self.mapping, &self.events) {
224 (Some(m), Some(e)) => (m, e),
225 _ => return Err("LV2 processor not initialized".to_string()),
226 };
227 let ptr = mapping.as_ptr();
228 let header = unsafe { header_mut(ptr) };
229
230 let scratch = unsafe { scratch_ptr(ptr) };
231 let size = state.len().min(SCRATCH_SIZE);
232 unsafe {
233 std::ptr::copy_nonoverlapping(state.as_ptr(), scratch, size);
234 }
235 header.scratch_size.store(size as u32, Ordering::Release);
236
237 header.request_type.store(2, Ordering::Release);
238 header.request_status.store(0, Ordering::Release);
239 if let Err(e) = events.signal_host() {
240 header.request_type.store(0, Ordering::Release);
241 return Err(format!("Failed to signal host for state restore: {}", e));
242 }
243
244 if let Err(e) = events.wait_host(Duration::from_secs(5)) {
245 header.request_type.store(0, Ordering::Release);
246 return Err(format!("Host did not respond to state restore: {}", e));
247 }
248
249 let status = header.request_status.load(Ordering::Acquire);
250 header.request_type.store(0, Ordering::Release);
251 if status != 1 {
252 return Err("State restore failed in host".to_string());
253 }
254 Ok(())
255 }
256
257 pub fn process_with_audio_io(&self, frames: usize) {
258 let _ = self.process_with_midi(frames, &[]);
259 }
260
261 pub fn process_with_midi(&self, frames: usize, _midi_in: &[MidiEvent]) -> Vec<MidiEvent> {
262 if self.bypassed.load(Ordering::Relaxed) {
263 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
264 return Vec::new();
265 }
266
267 {
269 let child = self.child.lock();
270 if let Some(ref mut c) = child.as_mut() {
271 match c.try_wait() {
272 Ok(Some(status)) if !status.success() => {
273 tracing::error!(
274 "LV2 plugin host crashed for '{}' ({})",
275 self.name,
276 self.uri
277 );
278 self.crash_count.fetch_add(1, Ordering::Relaxed);
279 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
280 return Vec::new();
281 }
282 Ok(Some(status)) => {
283 eprintln!("[LV2 debug] host exited with success: {:?}", status);
284 }
285 Ok(None) => {
286 eprintln!("[LV2 debug] host still alive");
287 }
288 Err(e) => {
289 eprintln!("[LV2 debug] try_wait error: {}", e);
290 }
291 }
292 }
293 }
294
295 let started = Instant::now();
296
297 let (mapping, events) = match (&self.mapping, &self.events) {
298 (Some(m), Some(e)) => (m, e),
299 _ => {
300 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
301 return Vec::new();
302 }
303 };
304
305 let ptr = mapping.as_ptr();
306 let num_in = self.audio_inputs.len();
307 let num_out = self.audio_outputs.len();
308 unsafe {
309 ipc::configure_shm_header(ptr, frames, num_in, num_out);
310 let t = transport_mut(ptr);
312 t.playhead_sample = 0;
313 t.tempo = 120.0;
314 t.numerator = 4;
315 t.denominator = 4;
316 t.flags = 1; ipc::copy_inputs_to_shm(&self.audio_inputs, ptr, frames);
320 }
321
322 if let Err(e) = events.signal_host() {
324 tracing::error!("Failed to signal LV2 host: {e}");
325 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
326 return Vec::new();
327 }
328 eprintln!("[LV2 debug] signal_host succeeded");
329
330 let timeout = Duration::from_millis(100);
332 match events.wait_host(timeout) {
333 Ok(()) => {
334 eprintln!("[LV2 debug] wait_host succeeded");
335 }
336 Err(e) => {
337 eprintln!(
338 "[LV2 debug] host did not respond for '{}' ({}): {}",
339 self.name, self.uri, e
340 );
341 ipc::bypass_copy_inputs_to_outputs(&self.audio_inputs, &self.audio_outputs);
342 return Vec::new();
343 }
344 }
345
346 unsafe {
348 ipc::copy_outputs_from_shm(&self.audio_outputs, ptr, frames);
349 }
350
351 let elapsed = started.elapsed();
352 if elapsed > Duration::from_millis(20) {
353 tracing::warn!(
354 "Slow LV2 process '{}' ({}) took {:.3} ms for {} frames",
355 self.name,
356 self.uri,
357 elapsed.as_secs_f64() * 1000.0,
358 frames
359 );
360 }
361
362 *self.last_process_time.lock() = Instant::now();
363 Vec::new()
364 }
365
366 pub fn uri(&self) -> &str {
367 &self.uri
368 }
369
370 pub fn name(&self) -> &str {
371 &self.name
372 }
373
374 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
375 Ok(())
376 }
377
378 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
379 Ok(())
380 }
381
382 pub fn run_host_callbacks_main_thread(&self) {}
383
384 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
385 Ok(false)
386 }
387
388 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
389 if let Some(ref mapping) = self.mapping {
390 let header = unsafe { header_mut(mapping.as_ptr()) };
391 header.set_parent_window(window);
392 return Ok(());
393 }
394 Err("No active host to set parent window".to_string())
395 }
396
397 pub fn gui_show(&self) -> Result<(), String> {
398 if let Some(ref mapping) = self.mapping
399 && let Some(ref events) = self.events
400 {
401 let header = unsafe { header_mut(mapping.as_ptr()) };
402 header.request_type.store(3, Ordering::Release);
403 let _ = events.signal_host();
404 return Ok(());
405 }
406 Err("No active host to show GUI".to_string())
407 }
408
409 pub fn gui_hide(&self) {
410 if let Some(ref mapping) = self.mapping
411 && let Some(ref events) = self.events
412 {
413 let header = unsafe { header_mut(mapping.as_ptr()) };
414 header.request_type.store(4, Ordering::Release);
415 let _ = events.signal_host();
416 }
417 }
418
419 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
420 let mut result = Vec::new();
421 if let Some(ref mapping) = self.mapping {
422 let ring = unsafe {
423 let buf = echo_ring_ptr(mapping.as_ptr());
424 let (w, r) = echo_indices(mapping.as_ptr());
425 RingBuffer::new(buf, w, r, RING_CAPACITY)
426 };
427 while let Some(ev) = ring.pop() {
428 result.push(ev);
429 }
430 }
431 result
432 }
433
434 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
435 let mut result = Vec::new();
436 if let Some(ref mapping) = self.mapping {
437 let ring = unsafe {
438 let buf = midi_out_ring_ptr(mapping.as_ptr());
439 let (w, r) = midi_out_indices(mapping.as_ptr());
440 RingBuffer::new(buf, w, r, RING_CAPACITY)
441 };
442 while let Some(ev) = ring.pop() {
443 result.push(crate::midi::io::MidiEvent {
444 frame: ev.sample_offset,
445 data: ev.data.to_vec(),
446 });
447 }
448 }
449 result
450 }
451}
452
453impl Drop for Lv2Processor {
454 fn drop(&mut self) {
455 ipc::drop_host(&self.mapping, &self.events, &self.child, &self.shm_name);
456 }
457}
458
459crate::impl_ipc_processor_wrapper!(Lv2Processor);
460
461impl UnsafeMutex<Lv2Processor> {
462 pub fn process_with_midi(&self, frames: usize, midi_events: &[MidiEvent]) -> Vec<MidiEvent> {
463 self.lock().process_with_midi(frames, midi_events)
464 }
465
466 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
467 self.lock().snapshot_state()
468 }
469
470 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
471 self.lock().restore_state(state)
472 }
473
474 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
475 self.lock().drain_echoed_parameters()
476 }
477
478 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
479 self.lock().drain_midi_outputs()
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 fn find_host_binary() -> PathBuf {
488 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
489 }
490
491 #[test]
492 fn find_host_binary_locates_binary() {
493 let host_bin = find_host_binary();
494 assert!(
495 host_bin.exists(),
496 "plugin-host binary should exist at {}",
497 host_bin.display()
498 );
499 }
500
501 #[test]
502 fn lv2_processor_crash_bypass() {
503 let host_bin = find_host_binary();
504
505 let processor = Lv2Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
506 .expect("should create LV2 processor for crash test");
507
508 processor.setup_audio_ports();
509
510 {
512 let buf = processor.audio_inputs()[0].buffer.lock();
513 buf.fill(1.0);
514 *processor.audio_inputs()[0].finished.lock() = true;
515 }
516
517 processor.process_with_audio_io(256);
519
520 let out_buf = processor.audio_outputs()[0].buffer.lock();
522 let first_few: Vec<f32> = out_buf.iter().take(10).copied().collect();
523 assert!(
524 out_buf.iter().all(|&s| s == 1.0),
525 "after crash, output should be bypass copy of input, got: {:?}",
526 first_few
527 );
528 }
529}