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_set_floating_mode(&self, floating: bool) -> Result<(), String> {
465        if let Some(ref mapping) = self.mapping {
466            let header = unsafe { header_mut(mapping.as_ptr()) };
467            header.set_gui_mode(if floating {
468                GuiMode::Floating
469            } else {
470                GuiMode::Embedded
471            });
472            return Ok(());
473        }
474        Err("No active host to set GUI mode".to_string())
475    }
476
477    pub fn gui_on_size(&self, _width: i32, _height: i32) -> Result<(), String> {
478        Err("GUI not yet supported for VST3 plugins".to_string())
479    }
480
481    pub fn gui_show(&self) -> Result<(), String> {
482        if let Some(ref mapping) = self.mapping
483            && let Some(ref events) = self.events
484        {
485            let header = unsafe { header_mut(mapping.as_ptr()) };
486            header.request_type.store(3, Ordering::Release);
487            let _ = events.signal_host();
488            return Ok(());
489        }
490        Err("No active host to show GUI".to_string())
491    }
492
493    pub fn gui_hide(&self) {
494        if let Some(ref mapping) = self.mapping
495            && let Some(ref events) = self.events
496        {
497            let header = unsafe { header_mut(mapping.as_ptr()) };
498            header.request_type.store(4, Ordering::Release);
499            let _ = events.signal_host();
500        }
501    }
502
503    pub fn gui_destroy(&self) {}
504
505    pub fn gui_on_main_thread(&self) {}
506
507    pub fn gui_on_timer(&self, _timer_id: u32) {}
508
509    pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
510        None
511    }
512
513    pub fn drain_echoed_parameters(&self) -> Vec<ParameterEvent> {
514        let mut result = Vec::new();
515        if let Some(ref mapping) = self.mapping {
516            let ring = unsafe {
517                let buf = echo_ring_ptr(mapping.as_ptr());
518                let (w, r) = echo_indices(mapping.as_ptr());
519                RingBuffer::new(buf, w, r, RING_CAPACITY)
520            };
521            while let Some(ev) = ring.pop() {
522                result.push(ev);
523            }
524        }
525        result
526    }
527}
528
529impl Drop for Vst3Processor {
530    fn drop(&mut self) {
531        ipc::drop_host(&self.mapping, &self.events, &self.child, &self.shm_name);
532    }
533}
534
535impl UnsafeMutex<Vst3Processor> {
536    pub fn setup_audio_ports(&self) {
537        self.lock().setup_audio_ports();
538    }
539
540    pub fn process_with_midi(&self, frames: usize, midi_events: &[MidiEvent]) -> Vec<MidiEvent> {
541        self.lock().process_with_midi(frames, midi_events)
542    }
543
544    pub fn set_bypassed(&self, bypassed: bool) {
545        self.lock().set_bypassed(bypassed);
546    }
547
548    pub fn is_bypassed(&self) -> bool {
549        self.lock().is_bypassed()
550    }
551
552    pub fn parameter_infos(&self) -> Vec<ParameterInfo> {
553        self.lock().parameter_infos()
554    }
555
556    pub fn set_parameter(&self, param_id: u32, value: f64) -> Result<(), String> {
557        self.lock().set_parameter(param_id, value)
558    }
559
560    pub fn set_parameter_at(&self, param_id: u32, value: f64, frame: u32) -> Result<(), String> {
561        self.lock().set_parameter_at(param_id, value, frame)
562    }
563
564    pub fn begin_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
565        self.lock().begin_parameter_edit_at(param_id, frame)
566    }
567
568    pub fn end_parameter_edit_at(&self, param_id: u32, frame: u32) -> Result<(), String> {
569        self.lock().end_parameter_edit_at(param_id, frame)
570    }
571
572    pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
573        self.lock().snapshot_state()
574    }
575
576    pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
577        self.lock().restore_state(state)
578    }
579
580    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
581        self.lock().audio_inputs()
582    }
583
584    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
585        self.lock().audio_outputs()
586    }
587
588    pub fn main_audio_input_count(&self) -> usize {
589        self.lock().main_audio_input_count()
590    }
591
592    pub fn main_audio_output_count(&self) -> usize {
593        self.lock().main_audio_output_count()
594    }
595
596    pub fn midi_input_count(&self) -> usize {
597        self.lock().midi_input_count()
598    }
599
600    pub fn midi_output_count(&self) -> usize {
601        self.lock().midi_output_count()
602    }
603
604    pub fn path(&self) -> String {
605        self.lock().path().to_string()
606    }
607
608    pub fn name(&self) -> String {
609        self.lock().name().to_string()
610    }
611
612    pub fn run_host_callbacks_main_thread(&self) {
613        self.lock().run_host_callbacks_main_thread();
614    }
615
616    pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
617        self.lock().reconfigure_ports_if_needed()
618    }
619
620    pub fn ui_begin_session(&self) {
621        self.lock().ui_begin_session();
622    }
623
624    pub fn ui_end_session(&self) {
625        self.lock().ui_end_session();
626    }
627
628    pub fn ui_should_close(&self) -> bool {
629        self.lock().ui_should_close()
630    }
631
632    pub fn ui_take_due_timers(&self) -> Vec<u32> {
633        self.lock().ui_take_due_timers()
634    }
635
636    pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
637        self.lock().ui_take_param_updates()
638    }
639
640    pub fn ui_take_state_update(&self) -> Option<Vst3PluginState> {
641        self.lock().ui_take_state_update()
642    }
643
644    pub fn gui_info(&self) -> Result<crate::plugins::types::Vst3GuiInfo, String> {
645        self.lock().gui_info()
646    }
647
648    pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
649        self.lock().gui_create(platform_type)
650    }
651
652    pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
653        self.lock().gui_get_size()
654    }
655
656    pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
657        self.lock().gui_set_parent(window, platform_type)
658    }
659
660    pub fn gui_set_floating_mode(&self, floating: bool) -> Result<(), String> {
661        self.lock().gui_set_floating_mode(floating)
662    }
663
664    pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
665        self.lock().gui_on_size(width, height)
666    }
667
668    pub fn gui_show(&self) -> Result<(), String> {
669        self.lock().gui_show()
670    }
671
672    pub fn gui_hide(&self) {
673        self.lock().gui_hide();
674    }
675
676    pub fn gui_destroy(&self) {
677        self.lock().gui_destroy();
678    }
679
680    pub fn gui_on_main_thread(&self) {
681        self.lock().gui_on_main_thread();
682    }
683
684    pub fn gui_on_timer(&self, timer_id: u32) {
685        self.lock().gui_on_timer(timer_id);
686    }
687
688    pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
689        self.lock().gui_check_resize()
690    }
691}
692
693fn serialize_vst3_state(scratch: *mut u8, state: &Vst3PluginState) -> Result<usize, String> {
694    let max_len = maolan_plugin_protocol::protocol::SCRATCH_SIZE;
695    let mut offset = 0usize;
696
697    let plugin_id_bytes = state.plugin_id.as_bytes();
698    if offset + 4 > max_len {
699        return Err("scratch overflow".to_string());
700    }
701    unsafe {
702        std::ptr::write_unaligned(
703            scratch.add(offset) as *mut u32,
704            plugin_id_bytes.len() as u32,
705        );
706    }
707    offset += 4;
708    if offset + plugin_id_bytes.len() > max_len {
709        return Err("scratch overflow".to_string());
710    }
711    unsafe {
712        std::ptr::copy_nonoverlapping(
713            plugin_id_bytes.as_ptr(),
714            scratch.add(offset),
715            plugin_id_bytes.len(),
716        );
717    }
718    offset += plugin_id_bytes.len();
719
720    if offset + 4 > max_len {
721        return Err("scratch overflow".to_string());
722    }
723    unsafe {
724        std::ptr::write_unaligned(
725            scratch.add(offset) as *mut u32,
726            state.component_state.len() as u32,
727        );
728    }
729    offset += 4;
730    if offset + state.component_state.len() > max_len {
731        return Err("scratch overflow".to_string());
732    }
733    unsafe {
734        std::ptr::copy_nonoverlapping(
735            state.component_state.as_ptr(),
736            scratch.add(offset),
737            state.component_state.len(),
738        );
739    }
740    offset += state.component_state.len();
741
742    if offset + 4 > max_len {
743        return Err("scratch overflow".to_string());
744    }
745    unsafe {
746        std::ptr::write_unaligned(
747            scratch.add(offset) as *mut u32,
748            state.controller_state.len() as u32,
749        );
750    }
751    offset += 4;
752    if offset + state.controller_state.len() > max_len {
753        return Err("scratch overflow".to_string());
754    }
755    unsafe {
756        std::ptr::copy_nonoverlapping(
757            state.controller_state.as_ptr(),
758            scratch.add(offset),
759            state.controller_state.len(),
760        );
761    }
762    offset += state.controller_state.len();
763
764    Ok(offset)
765}
766
767fn deserialize_vst3_state(scratch: *const u8, size: usize) -> Result<Vst3PluginState, String> {
768    if size < 12 {
769        return Err("scratch too small for VST3 state".to_string());
770    }
771    let mut offset = 0usize;
772
773    let plugin_id_len =
774        unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
775    offset += 4;
776    if offset + plugin_id_len > size {
777        return Err("scratch underflow".to_string());
778    }
779    let mut plugin_id_bytes = vec![0u8; plugin_id_len];
780    unsafe {
781        std::ptr::copy_nonoverlapping(
782            scratch.add(offset),
783            plugin_id_bytes.as_mut_ptr(),
784            plugin_id_len,
785        );
786    }
787    offset += plugin_id_len;
788    let plugin_id = String::from_utf8(plugin_id_bytes).map_err(|e| e.to_string())?;
789
790    let component_state_len =
791        unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
792    offset += 4;
793    if offset + component_state_len > size {
794        return Err("scratch underflow".to_string());
795    }
796    let mut component_state = vec![0u8; component_state_len];
797    unsafe {
798        std::ptr::copy_nonoverlapping(
799            scratch.add(offset),
800            component_state.as_mut_ptr(),
801            component_state_len,
802        );
803    }
804    offset += component_state_len;
805
806    let controller_state_len =
807        unsafe { std::ptr::read_unaligned(scratch.add(offset) as *const u32) } as usize;
808    offset += 4;
809    if offset + controller_state_len > size {
810        return Err("scratch underflow".to_string());
811    }
812    let mut controller_state = vec![0u8; controller_state_len];
813    unsafe {
814        std::ptr::copy_nonoverlapping(
815            scratch.add(offset),
816            controller_state.as_mut_ptr(),
817            controller_state_len,
818        );
819    }
820
821    Ok(Vst3PluginState {
822        plugin_id,
823        component_state,
824        controller_state,
825    })
826}
827
828#[cfg(test)]
829mod tests {
830    use super::*;
831
832    fn find_host_binary() -> PathBuf {
833        ipc::find_plugin_host_binary().expect("maolan-plugin-host binary should be built for tests")
834    }
835
836    #[test]
837    fn find_host_binary_locates_binary() {
838        let host_bin = find_host_binary();
839        assert!(
840            host_bin.exists(),
841            "plugin-host binary should exist at {}",
842            host_bin.display()
843        );
844    }
845
846    #[test]
847    fn vst3_state_serialization_roundtrip() {
848        let state = Vst3PluginState {
849            plugin_id: "test.plugin.vst3".to_string(),
850            component_state: vec![1, 2, 3, 4, 5],
851            controller_state: vec![10, 20, 30],
852        };
853        let mut scratch = vec![0u8; SCRATCH_SIZE];
854        let size =
855            serialize_vst3_state(scratch.as_mut_ptr(), &state).expect("serialize should succeed");
856        assert!(size > 0);
857        assert!(size < SCRATCH_SIZE);
858
859        let decoded =
860            deserialize_vst3_state(scratch.as_ptr(), size).expect("deserialize should succeed");
861        assert_eq!(decoded.plugin_id, state.plugin_id);
862        assert_eq!(decoded.component_state, state.component_state);
863        assert_eq!(decoded.controller_state, state.controller_state);
864    }
865
866    #[test]
867    fn vst3_processor_crash_bypass() {
868        let host_bin = find_host_binary();
869
870        let processor = Vst3Processor::new(48000.0, 256, "__crash__", 1, 1, host_bin)
871            .expect("should create VST3 processor for crash test");
872
873        processor.setup_audio_ports();
874
875        {
876            let buf = processor.audio_inputs()[0].buffer.lock();
877            buf.fill(1.0);
878            *processor.audio_inputs()[0].finished.lock() = true;
879        }
880
881        processor.process_with_audio_io(256);
882
883        let out_buf = processor.audio_outputs()[0].buffer.lock();
884        assert!(
885            out_buf.iter().all(|&s| s == 1.0),
886            "after crash, output should be bypass copy of input"
887        );
888    }
889}