Skip to main content

maolan_engine/plugins/vst3/
processor.rs

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