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