1use crate::audio::io::AudioIO;
2use crate::midi::io::{MIDIIO, MidiEvent};
3use crate::plugins::ipc;
4use arc_swap::ArcSwapOption;
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::cell::UnsafeCell;
10use std::collections::HashMap;
11use std::path::PathBuf;
12use std::process::{Child, ChildStderr};
13use std::sync::Arc;
14use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17fn wait_for_host_request_complete(
18 header: &ShmHeader,
19 events: &EventPair,
20 timeout: Duration,
21) -> Result<(), String> {
22 let start = Instant::now();
23 loop {
24 if header.request_status.load(Ordering::Acquire) != 0 {
25 return Ok(());
26 }
27 let elapsed = start.elapsed();
28 if elapsed >= timeout {
29 return Err("Host did not respond to request".to_string());
30 }
31 match events.wait_host(timeout - elapsed) {
32 Ok(()) => {
33 std::sync::atomic::fence(Ordering::Acquire);
34 continue;
35 }
36 Err(e) if e.kind() == std::io::ErrorKind::TimedOut => continue,
37 Err(e) => return Err(format!("Host did not respond to request: {e}")),
38 }
39 }
40}
41
42pub struct Lv2Processor {
43 uri: String,
44 name: String,
45 audio_inputs: Vec<Arc<AudioIO>>,
46 audio_outputs: Vec<Arc<AudioIO>>,
47 main_audio_inputs: usize,
48 main_audio_outputs: usize,
49 midi_input_ports: Vec<Arc<MIDIIO>>,
50 midi_output_ports: Vec<Arc<MIDIIO>>,
51 param_values: HashMap<u32, AtomicU64>,
54 bypassed: Arc<AtomicBool>,
55
56 child: UnsafeCell<Option<Child>>,
64 stderr: ArcSwapOption<ChildStderr>,
67 mapping: Option<ShmMapping>,
68 events: Option<EventPair>,
69 shm_name: String,
70
71 crash_count: AtomicU32,
72}
73
74unsafe impl Sync for Lv2Processor {}
78
79pub type SharedLv2Processor = Arc<Lv2Processor>;
80
81impl Lv2Processor {
82 pub fn new(
83 sample_rate: f64,
84 buffer_size: usize,
85 plugin_uri: &str,
86 input_count: usize,
87 output_count: usize,
88 host_binary: PathBuf,
89 ) -> Result<Self, String> {
90 let audio_inputs = (0..input_count.max(1))
91 .map(|_| Arc::new(AudioIO::new(buffer_size)))
92 .collect::<Vec<_>>();
93 let audio_outputs = (0..output_count.max(1))
94 .map(|_| Arc::new(AudioIO::new(buffer_size)))
95 .collect::<Vec<_>>();
96
97 let instance_id = ipc::unique_instance_id("lv2");
98 let sample_rate_str = sample_rate.to_string();
99 let buffer_size_str = buffer_size.to_string();
100 let num_inputs_str = input_count.max(1).to_string();
101 let num_outputs_str = output_count.max(1).to_string();
102 let (mut child, mapping, events, shm_name, stderr) = ipc::spawn_host(ipc::HostSpawnArgs {
103 host_binary: &host_binary,
104 format: "lv2",
105 plugin_spec: plugin_uri,
106 instance_id: &instance_id,
107 extra_args: &[
108 &sample_rate_str,
109 &buffer_size_str,
110 &num_inputs_str,
111 &num_outputs_str,
112 ],
113 })?;
114
115 let header = unsafe { header_ref(mapping.as_ptr()) };
116 if !ipc::wait_for_ready(header, Duration::from_secs(10)) {
117 let _ = child.kill();
118 return Err("LV2 host did not signal ready".to_string());
119 }
120
121 let name = unsafe {
122 maolan_plugin_protocol::protocol::read_plugin_name_from_scratch(mapping.as_ptr())
123 .unwrap_or_else(|| {
124 plugin_uri
125 .rsplit_once('/')
126 .map(|(_, name)| name)
127 .unwrap_or(plugin_uri)
128 .to_string()
129 })
130 };
131
132 let header = unsafe { header_ref(mapping.as_ptr()) };
133 let midi_in_count = header.midi_in_port_count.load(Ordering::Acquire) as usize;
134 let midi_out_count = header.midi_out_port_count.load(Ordering::Acquire) as usize;
135 let midi_input_ports: Vec<_> = (0..midi_in_count)
136 .map(|_| Arc::new(MIDIIO::new()))
137 .collect();
138 let midi_output_ports: Vec<_> = (0..midi_out_count)
139 .map(|_| Arc::new(MIDIIO::new()))
140 .collect();
141
142 let param_values: HashMap<u32, AtomicU64> = HashMap::new();
143
144 Ok(Self {
145 uri: plugin_uri.to_string(),
146 name,
147 audio_inputs,
148 audio_outputs,
149 main_audio_inputs: input_count.max(1),
150 main_audio_outputs: output_count.max(1),
151 midi_input_ports,
152 midi_output_ports,
153 param_values,
154 bypassed: Arc::new(AtomicBool::new(false)),
155 child: UnsafeCell::new(Some(child)),
156 stderr: ArcSwapOption::from_pointee(stderr),
157 mapping: Some(mapping),
158 events: Some(events),
159 shm_name,
160 crash_count: AtomicU32::new(0),
161 })
162 }
163
164 unsafe fn with_child<R>(&self, f: impl FnOnce(&mut Option<Child>) -> R) -> R {
172 f(unsafe { &mut *self.child.get() })
173 }
174
175 pub fn setup_audio_ports(&self) {
176 for port in &self.audio_inputs {
177 port.setup();
178 }
179 for port in &self.audio_outputs {
180 port.setup();
181 }
182 }
183
184 pub fn setup_midi_ports(&self) {
185 for port in &self.midi_input_ports {
186 unsafe { port.setup() };
190 }
191 for port in &self.midi_output_ports {
192 unsafe { port.setup() };
194 }
195 }
196
197 pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
198 &self.audio_inputs
199 }
200
201 pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
202 &self.audio_outputs
203 }
204
205 pub fn main_audio_input_count(&self) -> usize {
206 self.main_audio_inputs
207 }
208
209 pub fn main_audio_output_count(&self) -> usize {
210 self.main_audio_outputs
211 }
212
213 pub fn midi_input_count(&self) -> usize {
214 self.midi_input_ports.len()
215 }
216
217 pub fn midi_output_count(&self) -> usize {
218 self.midi_output_ports.len()
219 }
220
221 pub fn midi_input_ports(&self) -> &[Arc<MIDIIO>] {
222 &self.midi_input_ports
223 }
224
225 pub fn midi_output_ports(&self) -> &[Arc<MIDIIO>] {
226 &self.midi_output_ports
227 }
228
229 pub fn set_bypassed(&self, bypassed: bool) {
230 self.bypassed.store(bypassed, Ordering::Relaxed);
231 }
232
233 pub fn is_bypassed(&self) -> bool {
234 self.bypassed.load(Ordering::Relaxed)
235 }
236
237 pub fn parameter_values(&self) -> HashMap<u32, f64> {
238 self.param_values
239 .iter()
240 .map(|(&id, value)| (id, f64::from_bits(value.load(Ordering::Relaxed))))
241 .collect()
242 }
243
244 pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
245 self.set_parameter_at(param_id, value, 0)
246 }
247
248 pub fn set_parameter_at(&self, param_id: u32, value: f64, _frame: u32) -> Result<(), String> {
249 if let Some(slot) = self.param_values.get(¶m_id) {
250 slot.store(value.to_bits(), Ordering::Relaxed);
251 } else {
252 tracing::warn!("LV2 set_parameter_at: unknown parameter id {param_id}");
253 }
254
255 if let Some(ref mapping) = self.mapping {
256 let ring = unsafe {
257 let buf = param_ring_ptr(mapping.as_ptr());
258 let (w, r) = param_indices(mapping.as_ptr());
259 RingBuffer::new(buf, w, r, RING_CAPACITY)
260 };
261 let ev = ParameterEvent {
262 param_index: param_id,
263 value: value as f32,
264 sample_offset: 0,
265 event_kind: maolan_plugin_protocol::PARAM_EVENT_VALUE,
266 };
267 if !ring.push(ev) {}
268 }
269 Ok(())
270 }
271
272 pub fn begin_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
273 Ok(())
274 }
275
276 pub fn end_parameter_edit(&self, _param_id: u32) -> Result<(), String> {
277 Ok(())
278 }
279
280 pub fn is_parameter_edit_active(&self, _param_id: u32) -> bool {
281 false
282 }
283
284 pub fn snapshot_state(&self) -> Result<Vec<u8>, String> {
285 let (mapping, events) = match (&self.mapping, &self.events) {
286 (Some(m), Some(e)) => (m, e),
287 _ => return Err("LV2 processor not initialized".to_string()),
288 };
289 let ptr = mapping.as_ptr();
290 let header = unsafe { header_mut(ptr) };
291
292 tracing::info!("LV2 snapshot_state: sending request to host");
293 header.request_type.store(1, Ordering::Release);
294 header.request_status.store(0, Ordering::Release);
295 if let Err(e) = events.signal_host() {
296 header.request_type.store(0, Ordering::Release);
297 return Err(format!("Failed to signal host for state save: {}", e));
298 }
299
300 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
301 header.request_type.store(0, Ordering::Release);
302 return Err(format!("Host did not respond to state save: {}", e));
303 }
304
305 let status = header.request_status.load(Ordering::Acquire);
306 let size = header.scratch_size.load(Ordering::Acquire) as usize;
307 tracing::info!(status, size, "LV2 snapshot_state: host responded");
308 if status != 1 {
309 header.request_type.store(0, Ordering::Release);
310 return Err("State save failed in host".to_string());
311 }
312
313 let scratch = unsafe { scratch_ptr(ptr) };
314 let mut bytes = vec![0u8; size];
315 unsafe {
316 std::ptr::copy_nonoverlapping(scratch, bytes.as_mut_ptr(), size);
317 }
318 header.request_type.store(0, Ordering::Release);
319 Ok(bytes)
320 }
321
322 pub fn restore_state(&self, state: &[u8]) -> Result<(), String> {
323 let (mapping, events) = match (&self.mapping, &self.events) {
324 (Some(m), Some(e)) => (m, e),
325 _ => return Err("LV2 processor not initialized".to_string()),
326 };
327 let ptr = mapping.as_ptr();
328 let header = unsafe { header_mut(ptr) };
329
330 let scratch = unsafe { scratch_ptr(ptr) };
331 let size = state.len().min(SCRATCH_SIZE);
332 unsafe {
333 std::ptr::copy_nonoverlapping(state.as_ptr(), scratch, size);
334 }
335 header.scratch_size.store(size as u32, Ordering::Release);
336
337 header.request_type.store(2, Ordering::Release);
338 header.request_status.store(0, Ordering::Release);
339 if let Err(e) = events.signal_host() {
340 header.request_type.store(0, Ordering::Release);
341 return Err(format!("Failed to signal host for state restore: {}", e));
342 }
343
344 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
345 header.request_type.store(0, Ordering::Release);
346 return Err(format!("Host did not respond to state restore: {}", e));
347 }
348
349 let status = header.request_status.load(Ordering::Acquire);
350 header.request_type.store(0, Ordering::Release);
351 if status != 1 {
352 return Err("State restore failed in host".to_string());
353 }
354 Ok(())
355 }
356
357 fn deserialize_lv2_control_ports(
358 scratch: *const u8,
359 size: usize,
360 ) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
361 if size < 4 {
362 return Err("scratch too small for LV2 control ports".to_string());
363 }
364 let mut offset = 0usize;
365
366 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
367 offset += 4;
368
369 let mut ports = Vec::with_capacity(count);
370 for _ in 0..count {
371 if offset + 4 > size {
372 return Err("scratch underflow".to_string());
373 }
374 let index = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) };
375 offset += 4;
376
377 if offset + 4 > size {
378 return Err("scratch underflow".to_string());
379 }
380 let name_len =
381 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
382 offset += 4;
383 if offset + name_len > size {
384 return Err("scratch underflow".to_string());
385 }
386 let mut name_bytes = vec![0u8; name_len];
387 unsafe {
388 std::ptr::copy_nonoverlapping(
389 scratch.add(offset),
390 name_bytes.as_mut_ptr(),
391 name_len,
392 );
393 }
394 offset += name_len;
395 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
396
397 if offset + 12 > size {
398 return Err("scratch underflow".to_string());
399 }
400 let min = f32::from_bits(unsafe {
401 std::ptr::read_unaligned(scratch.add(offset) as *const u32)
402 });
403 let max = f32::from_bits(unsafe {
404 std::ptr::read_unaligned(scratch.add(offset + 4) as *const u32)
405 });
406 let value = f32::from_bits(unsafe {
407 std::ptr::read_unaligned(scratch.add(offset + 8) as *const u32)
408 });
409 offset += 12;
410
411 ports.push(crate::message::Lv2ControlPortInfo {
412 index,
413 name,
414 min,
415 max,
416 value,
417 });
418 }
419
420 Ok(ports)
421 }
422
423 fn deserialize_lv2_note_names(
424 scratch: *const u8,
425 size: usize,
426 ) -> Result<HashMap<u8, String>, String> {
427 if size < 4 {
428 return Err("scratch too small for LV2 note names".to_string());
429 }
430 let mut offset = 0usize;
431 let count = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
432 offset += 4;
433
434 let mut note_names = HashMap::with_capacity(count);
435 for _ in 0..count {
436 if offset + 4 > size {
437 return Err("scratch underflow".to_string());
438 }
439 let note = unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as u8;
440 offset += 4;
441
442 if offset + 4 > size {
443 return Err("scratch underflow".to_string());
444 }
445 let name_len =
446 unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
447 offset += 4;
448 if offset + name_len > size {
449 return Err("scratch underflow".to_string());
450 }
451 let mut name_bytes = vec![0u8; name_len];
452 unsafe {
453 std::ptr::copy_nonoverlapping(
454 scratch.add(offset),
455 name_bytes.as_mut_ptr(),
456 name_len,
457 );
458 }
459 offset += name_len;
460 let name = String::from_utf8(name_bytes).map_err(|e| e.to_string())?;
461 note_names.insert(note, name);
462 }
463
464 Ok(note_names)
465 }
466
467 pub fn control_ports(&self) -> Result<Vec<crate::message::Lv2ControlPortInfo>, String> {
468 let (mapping, events) = match (&self.mapping, &self.events) {
469 (Some(m), Some(e)) => (m, e),
470 _ => return Err("LV2 processor not initialized".to_string()),
471 };
472 let ptr = mapping.as_ptr();
473 let header = unsafe { header_mut(ptr) };
474
475 tracing::info!("LV2 control_ports: sending request to host");
476 header.request_type.store(
477 maolan_plugin_protocol::protocol::REQUEST_LV2_CONTROL_PORTS,
478 Ordering::Release,
479 );
480 header.request_status.store(0, Ordering::Release);
481 if let Err(e) = events.signal_host() {
482 header.request_type.store(0, Ordering::Release);
483 return Err(format!("Failed to signal host for LV2 control ports: {e}"));
484 }
485
486 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
487 header.request_type.store(0, Ordering::Release);
488 return Err(format!("Host did not respond to LV2 control ports: {e}"));
489 }
490
491 let status = header.request_status.load(Ordering::Acquire);
492 let size = header.scratch_size.load(Ordering::Acquire) as usize;
493 tracing::info!(status, size, "LV2 control_ports: host responded");
494 if status != 1 {
495 header.request_type.store(0, Ordering::Release);
496 return Err("LV2 control port enumeration failed in host".to_string());
497 }
498
499 let scratch = unsafe { scratch_ptr(ptr) };
500 let result = Self::deserialize_lv2_control_ports(scratch, size);
501 match &result {
502 Ok(ports) => tracing::info!(count = ports.len(), "LV2 control_ports: deserialized"),
503 Err(e) => tracing::error!("LV2 control_ports: deserialize failed: {e}"),
504 }
505 header.request_type.store(0, Ordering::Release);
506 result
507 }
508
509 pub fn note_names(&self) -> Result<HashMap<u8, String>, String> {
510 let (mapping, events) = match (&self.mapping, &self.events) {
511 (Some(m), Some(e)) => (m, e),
512 _ => return Err("LV2 processor not initialized".to_string()),
513 };
514 let ptr = mapping.as_ptr();
515 let header = unsafe { header_mut(ptr) };
516
517 header.request_type.store(
518 maolan_plugin_protocol::protocol::REQUEST_LV2_MIDNAM,
519 Ordering::Release,
520 );
521 header.request_status.store(0, Ordering::Release);
522 if let Err(e) = events.signal_host() {
523 header.request_type.store(0, Ordering::Release);
524 return Err(format!("Failed to signal host for LV2 midnam: {e}"));
525 }
526
527 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
528 header.request_type.store(0, Ordering::Release);
529 return Err(format!("Host did not respond to LV2 midnam: {e}"));
530 }
531
532 let status = header.request_status.load(Ordering::Acquire);
533 let size = header.scratch_size.load(Ordering::Acquire) as usize;
534 if status != 1 {
535 header.request_type.store(0, Ordering::Release);
536 return Err("LV2 midnam enumeration failed in host".to_string());
537 }
538
539 let scratch = unsafe { scratch_ptr(ptr) };
540 let result = Self::deserialize_lv2_note_names(scratch, size);
541 header.request_type.store(0, Ordering::Release);
542 result
543 }
544
545 pub fn set_resource_directory(&self, dir: &std::path::Path) -> Result<(), String> {
546 let (mapping, events) = match (&self.mapping, &self.events) {
547 (Some(m), Some(e)) => (m, e),
548 _ => return Err("LV2 processor not initialized".to_string()),
549 };
550 let ptr = mapping.as_ptr();
551 let header = unsafe { header_mut(ptr) };
552 let path_str = dir.to_string_lossy().to_string();
553 unsafe {
554 write_resource_directory_to_scratch(ptr, &path_str)
555 .map_err(|e| format!("Failed to write resource directory: {e}"))?;
556 }
557 std::sync::atomic::fence(Ordering::SeqCst);
558
559 header.request_type.store(5, Ordering::Release);
560 header.request_status.store(0, Ordering::Release);
561 if let Err(e) = events.signal_host() {
562 header.request_type.store(0, Ordering::Release);
563 return Err(format!("Failed to signal host for resource directory: {e}"));
564 }
565
566 if let Err(e) = wait_for_host_request_complete(header, events, Duration::from_secs(5)) {
567 header.request_type.store(0, Ordering::Release);
568 return Err(format!("Host did not respond to resource directory: {e}"));
569 }
570
571 let status = header.request_status.load(Ordering::Acquire);
572 header.request_type.store(0, Ordering::Release);
573 if status != 1 {
574 return Err("Resource directory update failed in host".to_string());
575 }
576 Ok(())
577 }
578
579 pub fn process_with_audio_buffers(
580 &self,
581 frames: usize,
582 audio_inputs: &[&[f32]],
583 audio_outputs: &mut [&mut [f32]],
584 ) -> Vec<MidiEvent> {
585 if self.bypassed.load(Ordering::Relaxed) {
586 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
587 return Vec::new();
588 }
589
590 let crashed = unsafe {
593 self.with_child(|child| {
594 if let Some(c) = child.as_mut()
595 && let Ok(Some(status)) = c.try_wait()
596 && !status.success()
597 {
598 self.crash_count.fetch_add(1, Ordering::Relaxed);
599 return true;
600 }
601 false
602 })
603 };
604 if crashed {
605 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
606 return Vec::new();
607 }
608
609 let (mapping, events) = match (&self.mapping, &self.events) {
610 (Some(m), Some(e)) => (m, e),
611 _ => {
612 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
613 return Vec::new();
614 }
615 };
616
617 let ptr = mapping.as_ptr();
618 let num_in = audio_inputs.len();
619 let num_out = audio_outputs.len();
620 let midi_in_count = self.midi_input_ports.len();
621 let midi_out_count = self.midi_output_ports.len();
622 unsafe {
623 ipc::configure_shm_header(ptr, frames, num_in, num_out, midi_in_count, midi_out_count);
624
625 let t = transport_mut(ptr);
626 t.playhead_sample = 0;
627 t.tempo = 120.0;
628 t.numerator = 4;
629 t.denominator = 4;
630 t.flags = 1;
631
632 ipc::copy_input_slices_to_shm(audio_inputs, ptr, frames);
633
634 for (port_idx, port) in self.midi_input_ports.iter().enumerate() {
635 let buf = midi_in_ring_ptr(ptr, port_idx);
636 let (w, r) = midi_in_indices(ptr, port_idx);
637 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
638 let port_buffer = port.buffer();
643 for ev in port_buffer {
644 let data = {
645 let mut d = [0u8; 3];
646 for (i, b) in ev.data.iter().enumerate().take(3) {
647 d[i] = *b;
648 }
649 d
650 };
651 let _ = ring.push(maolan_plugin_protocol::MidiEvent {
652 sample_offset: ev.frame,
653 data,
654 channel: ev.data.first().copied().unwrap_or(0) & 0x0F,
655 flags: 0,
656 _pad: 0,
657 });
658 }
659 port.mark_finished();
660 }
661 }
662
663 if events.signal_host().is_err() {
664 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
665 return Vec::new();
666 }
667
668 let timeout = Duration::from_millis(100);
669 match events.wait_host(timeout) {
670 Ok(()) => {}
671 Err(_) => {
672 ipc::bypass_copy_input_slices_to_outputs(audio_inputs, audio_outputs);
673 return Vec::new();
674 }
675 }
676
677 unsafe {
678 ipc::copy_outputs_from_shm_to_slices(audio_outputs, ptr, frames);
679
680 let mut output_events = Vec::new();
681 for (port_idx, port) in self.midi_output_ports.iter().enumerate() {
682 let buf = midi_out_ring_ptr(ptr, port_idx);
683 let (w, r) = midi_out_indices(ptr, port_idx);
684 let ring = RingBuffer::new(buf, w, r, RING_CAPACITY);
685 let mut port_buffer = port.buffer_mut();
688 port_buffer.clear();
689 while let Some(ev) = ring.pop() {
690 let event = MidiEvent {
691 frame: ev.sample_offset,
692 data: ev.data.to_vec(),
693 };
694 port_buffer.push(event.clone());
695 output_events.push(event);
696 }
697 port.mark_finished();
698 }
699 output_events
700 }
701 }
702
703 pub fn uri(&self) -> &str {
704 &self.uri
705 }
706
707 pub fn name(&self) -> &str {
708 &self.name
709 }
710
711 pub fn take_stderr(&self) -> Option<ChildStderr> {
712 self.stderr.swap(None).and_then(|s| Arc::try_unwrap(s).ok())
715 }
716
717 pub fn begin_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
718 Ok(())
719 }
720
721 pub fn end_parameter_edit_at(&self, _param_id: u32, _frame: u32) -> Result<(), String> {
722 Ok(())
723 }
724
725 pub fn run_host_callbacks_main_thread(&self) {}
726
727 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
728 Ok(false)
729 }
730
731 pub fn gui_set_parent_x11(&self, window: usize) -> Result<(), String> {
732 if let Some(ref mapping) = self.mapping {
733 let header = unsafe { header_mut(mapping.as_ptr()) };
734 header.set_parent_window(window);
735 return Ok(());
736 }
737 Err("No active host to set parent window".to_string())
738 }
739
740 pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
741 if let Some(ref mapping) = self.mapping {
742 let header = unsafe { header_mut(mapping.as_ptr()) };
743 header.set_gui_mode(if floating {
744 GuiMode::Floating
745 } else {
746 GuiMode::Embedded
747 });
748 return Ok(());
749 }
750 Err("No active host to set GUI mode".to_string())
751 }
752
753 pub fn gui_show(&self) -> Result<(), String> {
754 if let Some(ref mapping) = self.mapping
755 && let Some(ref events) = self.events
756 {
757 let header = unsafe { header_mut(mapping.as_ptr()) };
758 header.request_type.store(3, Ordering::Release);
759 let _ = events.signal_host();
760 return Ok(());
761 }
762 Err("No active host to show GUI".to_string())
763 }
764
765 pub fn gui_hide(&self) {
766 if let Some(ref mapping) = self.mapping
767 && let Some(ref events) = self.events
768 {
769 let header = unsafe { header_mut(mapping.as_ptr()) };
770 header.request_type.store(4, Ordering::Release);
771 let _ = events.signal_host();
772 }
773 }
774
775 pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
776 let mut result = Vec::new();
777 if let Some(ref mapping) = self.mapping {
778 let ring = unsafe {
779 let buf = echo_ring_ptr(mapping.as_ptr());
780 let (w, r) = echo_indices(mapping.as_ptr());
781 RingBuffer::new(buf, w, r, RING_CAPACITY)
782 };
783 while let Some(ev) = ring.pop() {
784 result.push(ev);
785 }
786 }
787 result
788 }
789
790 pub fn drain_midi_outputs(&self) -> Vec<crate::midi::io::MidiEvent> {
791 let mut result = Vec::new();
792 if let Some(ref mapping) = self.mapping {
793 let ring = unsafe {
794 let buf = midi_out_ring_ptr(mapping.as_ptr(), 0);
795 let (w, r) = midi_out_indices(mapping.as_ptr(), 0);
796 RingBuffer::new(buf, w, r, RING_CAPACITY)
797 };
798 while let Some(ev) = ring.pop() {
799 result.push(crate::midi::io::MidiEvent {
800 frame: ev.sample_offset,
801 data: ev.data.to_vec(),
802 });
803 }
804 }
805 result
806 }
807}
808
809impl Drop for Lv2Processor {
810 fn drop(&mut self) {
811 let mapping = self.mapping.take();
812 let events = self.events.take();
813 let child = self.child.get_mut().take();
814 let shm_name = std::mem::take(&mut self.shm_name);
815 ipc::drop_host(mapping, events, child, shm_name);
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822
823 fn find_host_binary() -> PathBuf {
824 ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
825 }
826
827 #[cfg_attr(
828 all(miri, target_os = "freebsd"),
829 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
830 )]
831 #[test]
832 fn find_host_binary_locates_binary() {
833 let host_bin = find_host_binary();
834 assert!(
835 host_bin.exists(),
836 "plugin-host binary should exist at {}",
837 host_bin.display()
838 );
839 }
840
841 #[cfg_attr(
842 all(miri, target_os = "freebsd"),
843 ignore = "plugin host discovery/runtime uses OS facilities not supported by Miri on FreeBSD"
844 )]
845 #[test]
846 fn lv2_processor_crash_bypass() {
847 let host_bin = find_host_binary();
848
849 let processor = Lv2Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
850 .expect("should create LV2 processor for crash test");
851
852 processor.setup_audio_ports();
853
854 let input_buffers = [vec![1.0; 256]];
855 let mut output_buffers = [vec![0.0; 256]];
856 let inputs = input_buffers.iter().map(Vec::as_slice).collect::<Vec<_>>();
857 let mut outputs = output_buffers
858 .iter_mut()
859 .map(Vec::as_mut_slice)
860 .collect::<Vec<_>>();
861 processor.process_with_audio_buffers(256, &inputs, &mut outputs);
862
863 let out_buf = &output_buffers[0];
864 let first_few: Vec<f32> = out_buf.iter().take(10).copied().collect();
865 assert!(
866 out_buf.iter().all(|&s| s == 1.0),
867 "after crash, output should be bypass copy of input, got: {:?}",
868 first_few
869 );
870 }
871}