Skip to main content

maolan_engine/plugins/
vst3_proc.rs

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