Skip to main content

maolan_engine/plugins/vst3/
processor.rs

1// The vst3 crate uses platform-dependent types for enum constants, so explicit
2// casts are required for cross-platform compilation.
3#![allow(clippy::unnecessary_cast)]
4
5use super::interfaces::{HostPlugFrame, PluginInstance, Vst3GuiInfo, protected_call};
6use super::midi::{EventBuffer, ParameterChanges};
7use super::port::{BusInfo, ParameterInfo};
8use super::state::{MemoryStream, Vst3PluginState, ibstream_ptr};
9use crate::audio::io::AudioIO;
10use crate::midi::io::MidiEvent;
11use std::ffi::{CString, c_void};
12use std::fmt;
13use std::path::Path;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::{Arc, Mutex};
16use vst3::ComPtr;
17use vst3::ComWrapper;
18use vst3::Steinberg::Vst::ProcessModes_::kRealtime;
19use vst3::Steinberg::Vst::SymbolicSampleSizes_::kSample32;
20use vst3::Steinberg::Vst::{IEditControllerTrait, ViewType};
21use vst3::Steinberg::{FIDString, IPlugFrame, IPlugView, IPlugViewTrait, ViewRect, kResultOk};
22
23pub struct Vst3Processor {
24    // Plugin identity
25    path: String,
26    name: String,
27    plugin_id: String,
28
29    // COM interfaces
30    instance: PluginInstance,
31    // Keep factory/module alive for the plugin instance lifetime.
32    _factory: super::interfaces::PluginFactory,
33
34    // Audio I/O (reuse existing AudioIO)
35    audio_inputs: Vec<Arc<AudioIO>>,
36    audio_outputs: Vec<Arc<AudioIO>>,
37    midi_input_ports: usize,
38    midi_output_ports: usize,
39    main_audio_inputs: usize,
40    main_audio_outputs: usize,
41    input_buses: Vec<BusInfo>,
42    output_buses: Vec<BusInfo>,
43
44    // Parameters
45    parameters: Vec<ParameterInfo>,
46    scalar_values: Arc<Mutex<Vec<f32>>>,
47    previous_values: Arc<Mutex<Vec<f32>>>,
48    max_samples_per_block: usize,
49    processing_started: bool,
50    sample_rate: f64,
51    bypassed: AtomicBool,
52
53    // GUI session
54    gui_session: Arc<Mutex<Vst3GuiSession>>,
55}
56
57struct Vst3GuiSession {
58    view: Option<ComPtr<IPlugView>>,
59    plug_frame: Option<ComWrapper<HostPlugFrame>>,
60    ui_should_close: bool,
61    platform_type: Option<String>,
62}
63
64impl fmt::Debug for Vst3Processor {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_struct("Vst3Processor")
67            .field("path", &self.path)
68            .field("name", &self.name)
69            .field("plugin_id", &self.plugin_id)
70            .field("audio_inputs", &self.audio_inputs.len())
71            .field("audio_outputs", &self.audio_outputs.len())
72            .field("midi_input_ports", &self.midi_input_ports)
73            .field("midi_output_ports", &self.midi_output_ports)
74            .field("main_audio_inputs", &self.main_audio_inputs)
75            .field("main_audio_outputs", &self.main_audio_outputs)
76            .field("input_buses", &self.input_buses)
77            .field("output_buses", &self.output_buses)
78            .field("parameters", &self.parameters)
79            .field("max_samples_per_block", &self.max_samples_per_block)
80            .field("processing_started", &self.processing_started)
81            .finish()
82    }
83}
84
85impl Vst3Processor {
86    /// Create a new VST3 processor with explicit sample rate
87    pub fn new_with_sample_rate(
88        sample_rate: f64,
89        buffer_size: usize,
90        plugin_path: &str,
91        audio_inputs: usize,
92        audio_outputs: usize,
93    ) -> Result<Self, String> {
94        let path_buf = Path::new(plugin_path);
95        let name = path_buf
96            .file_stem()
97            .or_else(|| path_buf.file_name())
98            .and_then(|s| s.to_str())
99            .unwrap_or("Unknown VST3")
100            .to_string();
101
102        // Load plugin factory and create instance
103        let factory = super::interfaces::PluginFactory::from_module(path_buf)?;
104
105        let class_count = factory.count_classes();
106        if class_count == 0 {
107            return Err("No plugin classes found".to_string());
108        }
109
110        // Find the first Audio Module class, matching rust-vst3-host behavior
111        let mut class_info = None;
112        for i in 0..class_count {
113            if let Some(info) = factory.get_class_info(i)
114                && info.category.contains("Audio Module")
115            {
116                class_info = Some(info);
117                break;
118            }
119        }
120        // Fallback to first class if no Audio Module found
121        let class_info = class_info
122            .or_else(|| factory.get_class_info(0))
123            .ok_or("Failed to get class info")?;
124
125        let mut instance = factory.create_instance(&class_info.cid)?;
126
127        // Initialize the plugin
128        instance.initialize(&factory)?;
129
130        let (plugin_input_buses, plugin_output_buses) = instance.audio_bus_counts();
131        let (plugin_main_in_channels, plugin_main_out_channels) =
132            instance.main_audio_channel_counts();
133        let (midi_input_ports, midi_output_ports) = instance.event_bus_counts();
134
135        let requested_inputs = if plugin_input_buses > 0 {
136            audio_inputs
137                .max(1)
138                .min(plugin_main_in_channels.max(1))
139                .min(i32::MAX as usize)
140        } else {
141            0
142        };
143        let requested_outputs = if plugin_output_buses > 0 {
144            audio_outputs
145                .max(1)
146                .min(plugin_main_out_channels.max(1))
147                .min(i32::MAX as usize)
148        } else {
149            0
150        };
151        // Query buses (for now, use the provided counts)
152        let input_buses = if plugin_input_buses > 0 {
153            vec![BusInfo {
154                index: 0,
155                name: "Input".to_string(),
156                channel_count: requested_inputs.max(1),
157                is_active: true,
158            }]
159        } else {
160            vec![]
161        };
162
163        let output_buses = if plugin_output_buses > 0 {
164            vec![BusInfo {
165                index: 0,
166                name: "Output".to_string(),
167                channel_count: requested_outputs.max(1),
168                is_active: true,
169            }]
170        } else {
171            vec![]
172        };
173
174        // Create AudioIO for each channel
175        let mut audio_input_ios = Vec::new();
176        for _ in 0..requested_inputs {
177            audio_input_ios.push(Arc::new(AudioIO::new(buffer_size)));
178        }
179
180        let mut audio_output_ios = Vec::new();
181        for _ in 0..requested_outputs {
182            audio_output_ios.push(Arc::new(AudioIO::new(buffer_size)));
183        }
184
185        // Configure processing before activation so plugins see the final
186        // host sample rate/block size when they enter active state.
187        instance.setup_processing(
188            sample_rate,
189            buffer_size as i32,
190            requested_inputs as i32,
191            requested_outputs as i32,
192        )?;
193        instance.set_active(true)?;
194
195        let processing_started = false;
196
197        // Query parameters safely; if the plugin panics during enumeration,
198        // catch_unwind lets us fall back to an empty list.
199        let parameters = protected_call(|| instance.query_parameters()).unwrap_or_default();
200        let scalar_values = Arc::new(Mutex::new(
201            parameters.iter().map(|p| p.default_value as f32).collect(),
202        ));
203        let previous_values = Arc::new(Mutex::new(
204            parameters.iter().map(|p| p.default_value as f32).collect(),
205        ));
206        let plugin_id = format!("{:02X?}", class_info.cid);
207
208        let gui_session = Arc::new(Mutex::new(Vst3GuiSession {
209            view: None,
210            plug_frame: None,
211            ui_should_close: false,
212            platform_type: None,
213        }));
214
215        Ok(Self {
216            path: plugin_path.to_string(),
217            name,
218            plugin_id,
219            instance,
220            _factory: factory,
221            audio_inputs: audio_input_ios,
222            audio_outputs: audio_output_ios,
223            midi_input_ports,
224            midi_output_ports,
225            main_audio_inputs: requested_inputs,
226            main_audio_outputs: requested_outputs,
227            input_buses,
228            output_buses,
229            parameters,
230            scalar_values,
231            previous_values,
232            max_samples_per_block: buffer_size,
233            processing_started,
234            sample_rate,
235            bypassed: AtomicBool::new(false),
236            gui_session,
237        })
238    }
239
240    pub fn path(&self) -> &str {
241        &self.path
242    }
243
244    pub fn name(&self) -> &str {
245        &self.name
246    }
247
248    pub fn plugin_id(&self) -> &str {
249        &self.plugin_id
250    }
251
252    pub fn audio_inputs(&self) -> &[Arc<AudioIO>] {
253        &self.audio_inputs
254    }
255
256    pub fn audio_outputs(&self) -> &[Arc<AudioIO>] {
257        &self.audio_outputs
258    }
259
260    pub fn main_audio_input_count(&self) -> usize {
261        self.main_audio_inputs
262    }
263
264    pub fn main_audio_output_count(&self) -> usize {
265        self.main_audio_outputs
266    }
267
268    pub fn midi_input_count(&self) -> usize {
269        self.midi_input_ports
270    }
271
272    pub fn midi_output_count(&self) -> usize {
273        self.midi_output_ports
274    }
275
276    pub fn setup_audio_ports(&self) {
277        for port in &self.audio_inputs {
278            port.setup();
279        }
280        for port in &self.audio_outputs {
281            port.setup();
282        }
283    }
284
285    pub fn set_bypassed(&self, bypassed: bool) {
286        self.bypassed.store(bypassed, Ordering::Relaxed);
287    }
288
289    pub fn is_bypassed(&self) -> bool {
290        self.bypassed.load(Ordering::Relaxed)
291    }
292
293    fn bypass_copy_inputs_to_outputs(&self) {
294        for (input, output) in self.audio_inputs.iter().zip(self.audio_outputs.iter()) {
295            let src = input.buffer.lock();
296            let dst = output.buffer.lock();
297            dst.fill(0.0);
298            for (d, s) in dst.iter_mut().zip(src.iter()) {
299                *d = *s;
300            }
301            *output.finished.lock() = true;
302        }
303        for output in self.audio_outputs.iter().skip(self.audio_inputs.len()) {
304            output.buffer.lock().fill(0.0);
305            *output.finished.lock() = true;
306        }
307    }
308
309    pub fn process_with_audio_io(&self, frames: usize) {
310        // Process all input AudioIO ports
311        for input in &self.audio_inputs {
312            input.process();
313        }
314        if self.bypassed.load(Ordering::Relaxed) {
315            self.bypass_copy_inputs_to_outputs();
316            return;
317        }
318
319        // Get the audio processor
320        let processor = match &self.instance.audio_processor {
321            Some(proc) => proc,
322            None => {
323                self.process_silence();
324                return;
325            }
326        };
327
328        // Call real VST3 processing (no MIDI)
329        if self.process_vst3(processor, frames, &[]).is_err() {
330            self.process_silence();
331        }
332    }
333
334    /// Process audio with MIDI events
335    #[allow(clippy::unnecessary_cast)]
336    pub fn process_with_midi(&self, frames: usize, input_events: &[MidiEvent]) -> Vec<MidiEvent> {
337        // Process all input AudioIO ports
338        for input in &self.audio_inputs {
339            input.process();
340        }
341        if self.bypassed.load(Ordering::Relaxed) {
342            self.bypass_copy_inputs_to_outputs();
343            return Vec::new();
344        }
345
346        // Get the audio processor
347        let processor = match &self.instance.audio_processor {
348            Some(proc) => proc,
349            None => {
350                self.process_silence();
351                return Vec::new();
352            }
353        };
354
355        // Call real VST3 processing with MIDI
356        match self.process_vst3(processor, frames, input_events) {
357            Ok(output_buffer) => {
358                // Convert output events back to MIDI
359                output_buffer.to_midi_events()
360            }
361            Err(_) => {
362                self.process_silence();
363                Vec::new()
364            }
365        }
366    }
367
368    fn process_vst3(
369        &self,
370        processor: &vst3::ComPtr<vst3::Steinberg::Vst::IAudioProcessor>,
371        frames: usize,
372        input_events: &[MidiEvent],
373    ) -> Result<EventBuffer, String> {
374        use vst3::Steinberg::Vst::IAudioProcessorTrait;
375        use vst3::Steinberg::Vst::*;
376
377        // Keep buffer guards alive while the plugin reads/writes through raw pointers.
378        let input_guards: Vec<_> = self
379            .audio_inputs
380            .iter()
381            .map(|io| io.buffer.lock())
382            .collect();
383        let output_guards: Vec<_> = self
384            .audio_outputs
385            .iter()
386            .map(|io| io.buffer.lock())
387            .collect();
388
389        let mut input_channel_ptrs: Vec<*mut f32> = input_guards
390            .iter()
391            .map(|buf| buf.as_ptr() as *mut f32)
392            .collect();
393        let mut output_channel_ptrs: Vec<*mut f32> = output_guards
394            .iter()
395            .map(|buf| buf.as_ptr() as *mut f32)
396            .collect();
397
398        let max_input_frames = input_guards
399            .iter()
400            .map(|buf| buf.len())
401            .min()
402            .unwrap_or(frames);
403        let max_output_frames = output_guards
404            .iter()
405            .map(|buf| buf.len())
406            .min()
407            .unwrap_or(frames);
408        let num_frames = frames.min(max_input_frames).min(max_output_frames);
409        if num_frames == 0 {
410            return Ok(EventBuffer::new());
411        }
412
413        let mut input_buses = Vec::new();
414        if !self.input_buses.is_empty() && !input_channel_ptrs.is_empty() {
415            input_buses.push(AudioBusBuffers {
416                numChannels: input_channel_ptrs.len() as i32,
417                silenceFlags: 0,
418                __field0: AudioBusBuffers__type0 {
419                    channelBuffers32: input_channel_ptrs.as_mut_ptr(),
420                },
421            });
422        }
423
424        let mut output_buses = Vec::new();
425        if !self.output_buses.is_empty() && !output_channel_ptrs.is_empty() {
426            output_buses.push(AudioBusBuffers {
427                numChannels: output_channel_ptrs.len() as i32,
428                silenceFlags: 0,
429                __field0: AudioBusBuffers__type0 {
430                    channelBuffers32: output_channel_ptrs.as_mut_ptr(),
431                },
432            });
433        }
434
435        // Create ProcessData
436        let mut process_context: ProcessContext = unsafe { std::mem::zeroed() };
437        process_context.sampleRate = self.sample_rate;
438        process_context.tempo = 120.0;
439        process_context.timeSigNumerator = 4;
440        process_context.timeSigDenominator = 4;
441        #[allow(clippy::unnecessary_cast)]
442        {
443            process_context.state = (ProcessContext_::StatesAndFlags_::kPlaying
444                | ProcessContext_::StatesAndFlags_::kTempoValid
445                | ProcessContext_::StatesAndFlags_::kTimeSigValid
446                | ProcessContext_::StatesAndFlags_::kContTimeValid
447                | ProcessContext_::StatesAndFlags_::kSystemTimeValid)
448                as u32;
449        }
450        let input_event_list = if self.midi_input_ports > 0 {
451            Some(ComWrapper::new(EventBuffer::from_midi_events(
452                input_events,
453                0,
454            )))
455        } else {
456            None
457        };
458        let midi_mapping = self
459            .instance
460            .edit_controller
461            .as_ref()
462            .and_then(|controller| controller.cast::<IMidiMapping>());
463        let input_parameter_changes = midi_mapping.as_ref().and_then(|mapping| {
464            ParameterChanges::from_midi_events(input_events, mapping, 0).map(ComWrapper::new)
465        });
466        let output_event_list = if self.midi_output_ports > 0 {
467            Some(ComWrapper::new(EventBuffer::new()))
468        } else {
469            None
470        };
471        let mut process_data = ProcessData {
472            processMode: kRealtime as i32,
473            symbolicSampleSize: kSample32 as i32,
474            numSamples: num_frames as i32,
475            numInputs: input_buses.len() as i32,
476            numOutputs: output_buses.len() as i32,
477            inputs: if input_buses.is_empty() {
478                std::ptr::null_mut()
479            } else {
480                input_buses.as_mut_ptr()
481            },
482            outputs: if output_buses.is_empty() {
483                std::ptr::null_mut()
484            } else {
485                output_buses.as_mut_ptr()
486            },
487            inputParameterChanges: input_parameter_changes
488                .as_ref()
489                .map(ParameterChanges::changes_ptr)
490                .unwrap_or(std::ptr::null_mut()),
491            outputParameterChanges: std::ptr::null_mut(),
492            inputEvents: input_event_list
493                .as_ref()
494                .map(EventBuffer::event_list_ptr)
495                .unwrap_or(std::ptr::null_mut()),
496            outputEvents: output_event_list
497                .as_ref()
498                .map(EventBuffer::event_list_ptr)
499                .unwrap_or(std::ptr::null_mut()),
500            processContext: &mut process_context,
501        };
502
503        // Call VST3 process with crash protection
504        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
505            processor.process(&mut process_data)
506        }));
507
508        match result {
509            Ok(vst3::Steinberg::kResultOk) => {}
510            Ok(err_code) => {
511                return Err(format!("VST3 process failed with result: {}", err_code));
512            }
513            Err(_) => {
514                return Err("VST3 process panicked".to_string());
515            }
516        }
517
518        // Mark outputs as finished
519        for output in &self.audio_outputs {
520            *output.finished.lock() = true;
521        }
522
523        Ok(output_event_list
524            .as_ref()
525            .map(|events| EventBuffer::from_midi_events(&events.to_midi_events(), 0))
526            .unwrap_or_default())
527    }
528
529    fn process_silence(&self) {
530        for output in &self.audio_outputs {
531            let out_buf = output.buffer.lock();
532            out_buf.fill(0.0);
533            *output.finished.lock() = true;
534        }
535    }
536
537    pub fn parameters(&self) -> &[ParameterInfo] {
538        &self.parameters
539    }
540
541    pub fn get_parameter_value(&self, param_id: u32) -> Option<f32> {
542        let idx = self.parameters.iter().position(|p| p.id == param_id)?;
543        Some(self.scalar_values.lock().unwrap()[idx])
544    }
545
546    pub fn set_parameter_value(&self, param_id: u32, normalized_value: f32) -> Result<(), String> {
547        let idx = self
548            .parameters
549            .iter()
550            .position(|p| p.id == param_id)
551            .ok_or("Parameter not found")?;
552
553        self.scalar_values.lock().unwrap()[idx] = normalized_value;
554
555        // Update controller if available
556        if let Some(controller) = &self.instance.edit_controller {
557            use vst3::Steinberg::Vst::IEditControllerTrait;
558            unsafe {
559                controller.setParamNormalized(param_id, normalized_value as f64);
560            }
561        }
562
563        Ok(())
564    }
565
566    /// Snapshot the current plugin state for saving
567    pub fn snapshot_state(&self) -> Result<Vst3PluginState, String> {
568        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
569
570        let instance = &self.instance;
571
572        // Save component state
573        let comp_stream = vst3::ComWrapper::new(MemoryStream::new());
574        unsafe {
575            let result = instance
576                .component
577                .getState(ibstream_ptr(&comp_stream) as *mut _);
578            if result != vst3::Steinberg::kResultOk {
579                return Err("Failed to get component state".to_string());
580            }
581        }
582
583        // Save controller state (if available)
584        let ctrl_stream = vst3::ComWrapper::new(MemoryStream::new());
585        if let Some(controller) = &instance.edit_controller {
586            unsafe {
587                controller.getState(ibstream_ptr(&ctrl_stream) as *mut _);
588            }
589        }
590
591        Ok(Vst3PluginState {
592            plugin_id: self.plugin_id.clone(),
593            component_state: comp_stream.bytes(),
594            controller_state: ctrl_stream.bytes(),
595        })
596    }
597
598    /// Restore plugin state from a snapshot
599    pub fn restore_state(&self, state: &Vst3PluginState) -> Result<(), String> {
600        use vst3::Steinberg::Vst::{IComponentTrait, IEditControllerTrait};
601
602        if state.plugin_id != self.plugin_id {
603            return Err(format!(
604                "Plugin ID mismatch: expected '{}', got '{}'",
605                self.plugin_id, state.plugin_id
606            ));
607        }
608
609        let instance = &self.instance;
610
611        // Restore component state
612        if !state.component_state.is_empty() {
613            let comp_stream =
614                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.component_state));
615            unsafe {
616                let result = instance
617                    .component
618                    .setState(ibstream_ptr(&comp_stream) as *mut _);
619                if result != vst3::Steinberg::kResultOk {
620                    return Err("Failed to set component state".to_string());
621                }
622            }
623        }
624
625        // Restore controller state (if available)
626        if !state.controller_state.is_empty()
627            && let Some(controller) = &instance.edit_controller
628        {
629            let ctrl_stream =
630                vst3::ComWrapper::new(MemoryStream::from_bytes(&state.controller_state));
631            unsafe {
632                controller.setState(ibstream_ptr(&ctrl_stream) as *mut _);
633            }
634
635            // Re-sync parameter values after restoring state
636            for (idx, param) in self.parameters.iter().enumerate() {
637                let value = unsafe { controller.getParamNormalized(param.id) };
638                self.scalar_values.lock().unwrap()[idx] = value as f32;
639                self.previous_values.lock().unwrap()[idx] = value as f32;
640            }
641        }
642
643        Ok(())
644    }
645
646    // ------------------------------------------------------------------
647    // GUI abstraction (mirrors ClapProcessor pattern)
648    // ------------------------------------------------------------------
649
650    pub fn gui_info(&self) -> Result<Vst3GuiInfo, String> {
651        let controller = self
652            .instance
653            .edit_controller
654            .as_ref()
655            .ok_or("No edit controller available")?;
656        let view = unsafe { controller.createView(ViewType::kEditor) };
657        if view.is_null() {
658            return Ok(Vst3GuiInfo {
659                has_gui: false,
660                size: None,
661            });
662        }
663        // Release the probe view without calling removed() — it was never attached.
664        unsafe {
665            let _ = ComPtr::<IPlugView>::from_raw(view);
666        }
667        Ok(Vst3GuiInfo {
668            has_gui: true,
669            size: None,
670        })
671    }
672
673    pub fn gui_create(&self, platform_type: &str) -> Result<(), String> {
674        let mut session = self.gui_session.lock().unwrap();
675        if session.view.is_some() {
676            return Ok(());
677        }
678        let controller = self
679            .instance
680            .edit_controller
681            .as_ref()
682            .ok_or("No edit controller available")?;
683        let view = unsafe { controller.createView(ViewType::kEditor) };
684        if view.is_null() {
685            return Err("Plugin does not provide an editor view".to_string());
686        }
687        let view =
688            unsafe { ComPtr::<IPlugView>::from_raw(view) }.ok_or("Failed to wrap IPlugView")?;
689
690        let platform_cstr =
691            CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
692        let supported =
693            unsafe { view.isPlatformTypeSupported(platform_cstr.as_ptr() as FIDString) };
694        if supported != kResultOk {
695            return Err(format!("Platform type '{}' not supported", platform_type));
696        }
697
698        session.view = Some(view);
699        session.platform_type = Some(platform_type.to_string());
700        Ok(())
701    }
702
703    pub fn gui_get_size(&self) -> Result<(i32, i32), String> {
704        let session = self.gui_session.lock().unwrap();
705        let view = session.view.as_ref().ok_or("No GUI view created")?;
706        let mut rect = ViewRect {
707            left: 0,
708            top: 0,
709            right: 0,
710            bottom: 0,
711        };
712        let result = unsafe { view.getSize(&mut rect) };
713        if result != kResultOk {
714            return Err("Failed to get GUI size".to_string());
715        }
716        Ok((rect.right - rect.left, rect.bottom - rect.top))
717    }
718
719    pub fn gui_set_parent(&self, window: usize, platform_type: &str) -> Result<(), String> {
720        let mut session = self.gui_session.lock().unwrap();
721        let view = session.view.as_ref().ok_or("No GUI view created")?;
722
723        let plug_frame = ComWrapper::new(HostPlugFrame::new());
724        if let Some(frame_ptr) = plug_frame.to_com_ptr::<IPlugFrame>() {
725            unsafe {
726                let _ = view.setFrame(frame_ptr.into_raw());
727            }
728        }
729
730        let platform_cstr =
731            CString::new(platform_type).map_err(|e| format!("Invalid platform type: {e}"))?;
732        let result =
733            unsafe { view.attached(window as *mut c_void, platform_cstr.as_ptr() as FIDString) };
734        if result != kResultOk {
735            return Err(format!("Failed to attach GUI view: {:#x}", result));
736        }
737
738        session.plug_frame = Some(plug_frame);
739        Ok(())
740    }
741
742    pub fn gui_on_size(&self, width: i32, height: i32) -> Result<(), String> {
743        let session = self.gui_session.lock().unwrap();
744        let view = session.view.as_ref().ok_or("No GUI view created")?;
745        let mut rect = ViewRect {
746            left: 0,
747            top: 0,
748            right: width,
749            bottom: height,
750        };
751        unsafe {
752            let _ = view.onSize(&mut rect);
753        }
754        Ok(())
755    }
756
757    pub fn gui_show(&self) -> Result<(), String> {
758        let session = self.gui_session.lock().unwrap();
759        let view = session.view.as_ref().ok_or("No GUI view created")?;
760        unsafe {
761            let _ = view.onFocus(1);
762        }
763        Ok(())
764    }
765
766    pub fn gui_hide(&self) {
767        if let Ok(session) = self.gui_session.lock()
768            && let Some(view) = session.view.as_ref()
769        {
770            unsafe {
771                let _ = view.onFocus(0);
772            }
773        }
774    }
775
776    pub fn gui_destroy(&self) {
777        if let Ok(mut session) = self.gui_session.lock() {
778            if let Some(view) = session.view.take() {
779                unsafe {
780                    let _ = view.setFrame(std::ptr::null_mut());
781                    let _ = view.removed();
782                }
783            }
784            session.plug_frame.take();
785            session.platform_type.take();
786        }
787    }
788
789    pub fn ui_begin_session(&self) {
790        if let Ok(mut session) = self.gui_session.lock() {
791            session.ui_should_close = false;
792        }
793    }
794
795    pub fn ui_end_session(&self) {
796        if let Ok(mut session) = self.gui_session.lock() {
797            session.ui_should_close = false;
798        }
799    }
800
801    pub fn ui_should_close(&self) -> bool {
802        if let Ok(session) = self.gui_session.lock() {
803            return session.ui_should_close;
804        }
805        false
806    }
807
808    pub fn ui_take_param_updates(&self) -> Vec<(u32, f64)> {
809        let mut changes = Vec::new();
810        if let Ok(mut param_changes) = self.instance.parameter_changes.lock() {
811            std::mem::swap(&mut changes, &mut *param_changes);
812        }
813        changes
814    }
815
816    pub fn gui_check_resize(&self) -> Option<(i32, i32)> {
817        if let Ok(session) = self.gui_session.lock()
818            && let Some(ref frame) = session.plug_frame
819            && frame.resize_requested.swap(false, Ordering::Relaxed)
820            && let Ok(size) = frame.requested_size.lock()
821        {
822            return *size;
823        }
824        None
825    }
826
827    pub fn gui_on_main_thread(&self) {
828        super::interfaces::pump_host_run_loop();
829    }
830}
831
832impl Drop for Vst3Processor {
833    fn drop(&mut self) {
834        if self.processing_started {
835            self.instance.stop_processing();
836        }
837        let _ = self.instance.set_active(false);
838        let _ = self.instance.terminate();
839    }
840}
841
842// Standalone function for listing plugins (backward compatibility)
843pub fn list_plugins() -> Vec<super::host::Vst3PluginInfo> {
844    super::host::list_plugins()
845}