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