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