Skip to main content

vst3_host/
simple.rs

1//! Simplified API for common VST3 hosting tasks.
2//!
3//! This module provides convenience functions that make it easy to get started
4//! with VST3 plugin hosting without needing to understand all the configuration
5//! options and complex APIs.
6//!
7//! ## Quick Examples
8//!
9//! ### Load and play a plugin
10//! ```no_run
11//! use vst3_host::simple;
12//! use vst3_host::midi::MidiChannel;
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! // Load a plugin with sensible defaults
16//! let mut plugin = simple::load_plugin("/path/to/synth.vst3")?;
17//!
18//! // Start processing audio
19//! plugin.start_processing()?;
20//!
21//! // Play a note
22//! plugin.send_midi_note(60, 127, MidiChannel::Ch1)?; // Middle C
23//! # Ok(())
24//! # }
25//! ```
26//!
27//! ### Discover plugins easily
28//! ```no_run
29//! use vst3_host::simple;
30//!
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! // Find all plugins on the system
33//! let plugins = simple::discover_plugins()?;
34//!
35//! for plugin in plugins {
36//!     println!("Found: {} by {}", plugin.name, plugin.vendor);
37//! }
38//! # Ok(())
39//! # }
40//! ```
41
42use crate::{
43    audio::AudioBuffers,
44    error::{Error, Result},
45    host::Vst3Host,
46    midi::MidiEvent,
47    plugin::{Plugin, PluginInfo},
48};
49use std::path::Path;
50
51/// Load a VST3 plugin with sensible defaults.
52///
53/// This function creates a host with default audio settings and loads the
54/// specified plugin. It's the quickest way to get started with plugin hosting.
55///
56/// # Default Settings
57/// - Sample rate: 44100 Hz
58/// - Block size: 512 samples
59/// - Input channels: 2 (stereo)
60/// - Output channels: 2 (stereo)
61/// - Process isolation: disabled (in-process). Use [`load_plugin_isolated`] to opt in.
62///
63/// # Arguments
64/// * `path` - Path to the VST3 plugin (.vst3 file or directory)
65///
66/// # Returns
67/// A loaded and configured plugin ready for audio processing.
68///
69/// # Examples
70/// ```no_run
71/// use vst3_host::simple;
72/// use vst3_host::midi::MidiChannel;
73///
74/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
75/// let mut plugin = simple::load_plugin("/Applications/Dexed.vst3")?;
76/// plugin.start_processing()?;
77/// plugin.send_midi_note(60, 100, MidiChannel::Ch1)?;
78/// # Ok(())
79/// # }
80/// ```
81pub fn load_plugin<P: AsRef<Path>>(path: P) -> Result<Plugin> {
82    let mut host = Vst3Host::builder()
83        .sample_rate(44100.0)
84        .block_size(512)
85        .input_channels(2)
86        .output_channels(2)
87        .build()?;
88
89    host.load_plugin(path)
90}
91
92/// Load a plugin with custom audio settings.
93///
94/// This provides a middle ground between the fully automatic `load_plugin()`
95/// and the full control of the host builder pattern.
96///
97/// # Arguments
98/// * `path` - Path to the VST3 plugin
99/// * `sample_rate` - Audio sample rate in Hz (typically 44100 or 48000)
100/// * `block_size` - Audio buffer size (typically 512 or 1024)
101///
102/// # Examples
103/// ```no_run
104/// use vst3_host::simple;
105///
106/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107/// // Load with professional audio settings
108/// let mut plugin = simple::load_plugin_with_settings(
109///     "/path/to/plugin.vst3",
110///     48000.0,  // 48kHz sample rate
111///     256       // Small buffer for low latency
112/// )?;
113/// # Ok(())
114/// # }
115/// ```
116pub fn load_plugin_with_settings<P: AsRef<Path>>(
117    path: P,
118    sample_rate: f64,
119    block_size: usize,
120) -> Result<Plugin> {
121    let mut host = Vst3Host::builder()
122        .sample_rate(sample_rate)
123        .block_size(block_size)
124        .input_channels(2)
125        .output_channels(2)
126        .build()?;
127
128    host.load_plugin(path)
129}
130
131/// Load a plugin with crash protection enabled.
132///
133/// This loads the plugin in a separate process, which prevents plugin crashes
134/// from affecting your application. Use this for untested or problematic plugins.
135///
136/// # Arguments
137/// * `path` - Path to the VST3 plugin
138///
139/// # Examples
140/// ```no_run
141/// use vst3_host::simple;
142///
143/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
144/// // Load a potentially unstable plugin safely
145/// let mut plugin = simple::load_plugin_isolated("/path/to/sketchy_plugin.vst3")?;
146/// # Ok(())
147/// # }
148/// ```
149pub fn load_plugin_isolated<P: AsRef<Path>>(path: P) -> Result<Plugin> {
150    let mut host = Vst3Host::builder()
151        .sample_rate(44100.0)
152        .block_size(512)
153        .input_channels(2)
154        .output_channels(2)
155        .with_process_isolation(true) // Force process isolation
156        .build()?;
157
158    host.load_plugin(path)
159}
160
161/// Load a plugin and immediately start playing it through the default audio device.
162///
163/// The quickest way to actually hear a synth: load, then `play`. The returned
164/// [`AudioHandle`](crate::AudioHandle) keeps audio running until dropped, and lets
165/// you control the plugin while it plays.
166///
167/// # Examples
168/// ```no_run
169/// use vst3_host::{simple, midi::MidiChannel};
170///
171/// # fn main() -> vst3_host::Result<()> {
172/// let plugin = simple::load_plugin("/path/to/synth.vst3")?;
173/// let audio = simple::play(plugin)?;
174/// audio.lock().send_midi_note(60, 100, MidiChannel::Ch1)?; // middle C
175/// std::thread::sleep(std::time::Duration::from_secs(2));
176/// # Ok(())
177/// # }
178/// ```
179#[cfg(feature = "cpal-backend")]
180pub fn play(plugin: Plugin) -> Result<crate::AudioHandle> {
181    let backend = crate::backends::CpalBackend::new()?;
182    let config = crate::audio::AudioConfig {
183        output_channels: 2,
184        input_channels: 0,
185        ..Default::default()
186    };
187    crate::playback::play_with_backend(&backend, plugin, config)
188}
189
190/// Host an effect plugin on live audio input: capture from the default input device, process
191/// through the plugin, and play the result on the default output device.
192///
193/// The instrument counterpart is [`play`]. Returns an [`AudioHandle`](crate::AudioHandle)
194/// that keeps the streams alive and lets you control the plugin; dropping it stops audio.
195#[cfg(feature = "cpal-backend")]
196pub fn play_with_input(plugin: Plugin) -> Result<crate::AudioHandle> {
197    let backend = crate::backends::CpalBackend::new()?;
198    let config = crate::audio::AudioConfig {
199        input_channels: 2,
200        output_channels: 2,
201        ..Default::default()
202    };
203    crate::playback::play_with_input_backend(&backend, plugin, config)
204}
205
206/// Discover all VST3 plugins in the standard system locations.
207///
208/// Scans the platform's standard VST3 directories and returns metadata for each
209/// plugin found. For progress reporting during a long scan, use
210/// [`Vst3Host::discover_plugins_with_callback`](crate::Vst3Host::discover_plugins_with_callback).
211///
212/// # Examples
213/// ```no_run
214/// use vst3_host::simple;
215///
216/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
217/// for info in simple::discover_plugins()? {
218///     println!("Found: {} by {}", info.name, info.vendor);
219/// }
220/// # Ok(())
221/// # }
222/// ```
223pub fn discover_plugins() -> Result<Vec<PluginInfo>> {
224    let mut host = Vst3Host::builder()
225        .scan_default_paths() // Enable scanning system directories
226        .build()?;
227
228    host.discover_plugins()
229}
230
231/// Discover plugins in a specific directory.
232///
233/// # Arguments
234/// * `path` - Directory to scan for VST3 plugins
235///
236/// # Examples
237/// ```no_run
238/// use vst3_host::simple;
239///
240/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
241/// let plugins = simple::discover_plugins_in("/my/custom/vst3/folder")?;
242/// println!("{} plugins found", plugins.len());
243/// # Ok(())
244/// # }
245/// ```
246pub fn discover_plugins_in<P: AsRef<Path>>(path: P) -> Result<Vec<PluginInfo>> {
247    let mut host = Vst3Host::builder().add_scan_path(path).build()?;
248
249    host.discover_plugins()
250}
251
252/// Get information about a specific plugin without loading it.
253///
254/// This is useful for checking plugin compatibility or displaying plugin
255/// information before deciding whether to load it.
256///
257/// # Arguments
258/// * `path` - Path to the VST3 plugin
259///
260/// # Returns
261/// Plugin information including name, vendor, version, and capabilities.
262///
263/// # Examples
264/// ```no_run
265/// use vst3_host::simple;
266///
267/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
268/// let info = simple::get_plugin_info("/path/to/plugin.vst3")?;
269/// println!("Plugin: {} v{} by {}", info.name, info.version, info.vendor);
270/// println!("Has GUI: {}", info.has_gui);
271/// println!("Audio I/O: {} in, {} out", info.audio_inputs, info.audio_outputs);
272/// # Ok(())
273/// # }
274/// ```
275pub fn get_plugin_info<P: AsRef<Path>>(path: P) -> Result<PluginInfo> {
276    let path = path.as_ref();
277
278    if !path.exists() {
279        return Err(Error::PluginNotFound(path.display().to_string()));
280    }
281
282    // Create a minimal host just for info gathering
283    let mut host = Vst3Host::builder().build()?;
284
285    // Load plugin to get info, then immediately drop it
286    let plugin = host.load_plugin(path)?;
287    Ok(plugin.info().clone())
288}
289
290/// Check if a plugin path is valid and loadable.
291///
292/// This performs basic validation without actually loading the plugin.
293/// Useful for filtering plugin lists or validating user input.
294///
295/// # Arguments
296/// * `path` - Path to check
297///
298/// # Returns
299/// `true` if the path appears to be a valid VST3 plugin, `false` otherwise.
300///
301/// # Examples
302/// ```no_run
303/// use vst3_host::simple;
304///
305/// if simple::is_valid_plugin("/path/to/plugin.vst3") {
306///     println!("Plugin path looks valid");
307/// } else {
308///     println!("Not a valid VST3 plugin path");
309/// }
310/// ```
311pub fn is_valid_plugin<P: AsRef<Path>>(path: P) -> bool {
312    let path = path.as_ref();
313
314    // Basic checks
315    if !path.exists() {
316        return false;
317    }
318
319    // Check for .vst3 extension
320    if let Some(extension) = path.extension() {
321        if extension.to_string_lossy().to_lowercase() == "vst3" {
322            return true;
323        }
324    }
325
326    false
327}
328
329/// Render a plugin offline to a 32-bit float WAV file.
330///
331/// Drives `process_audio` faster-than-realtime for `duration_secs` (at the plugin's
332/// configured sample rate and block size), starting/stopping processing for you, and writes
333/// the output to `path`. Any `midi` events are sent at the start — pass a held `NoteOn` to
334/// bounce an instrument, or an empty slice for an effect (feed input via the lower-level
335/// `process_audio` loop if you need to process a signal). No audio hardware is used.
336///
337/// ```no_run
338/// use vst3_host::{simple, midi::{MidiEvent, MidiChannel}};
339/// # fn main() -> vst3_host::Result<()> {
340/// let mut plugin = simple::load_plugin("/path/synth.vst3")?;
341/// let note = MidiEvent::NoteOn { channel: MidiChannel::Ch1, note: 60, velocity: 100 };
342/// simple::render_to_wav(&mut plugin, 2.0, &[note], "out.wav")?;
343/// # Ok(())
344/// # }
345/// ```
346pub fn render_to_wav<P: AsRef<Path>>(
347    plugin: &mut Plugin,
348    duration_secs: f64,
349    midi: &[MidiEvent],
350    path: P,
351) -> Result<()> {
352    if duration_secs < 0.0 {
353        return Err(Error::Other("duration must be non-negative".to_string()));
354    }
355    let sample_rate = plugin.sample_rate();
356    let block = plugin.block_size().max(1);
357    let out_channels = plugin.output_channel_count().max(1);
358    let total_frames = (duration_secs * sample_rate).round() as usize;
359
360    plugin.start_processing()?;
361    for &event in midi {
362        plugin.send_midi_event(event)?;
363    }
364
365    let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
366    let mut rendered = 0;
367    while rendered < total_frames {
368        let frames = block.min(total_frames - rendered);
369        let mut buffers = AudioBuffers::new(0, out_channels, frames, sample_rate);
370        plugin.process_audio(&mut buffers)?;
371        for (ch, dst) in channels.iter_mut().enumerate() {
372            if let Some(src) = buffers.outputs.get(ch) {
373                dst.extend_from_slice(&src[..frames.min(src.len())]);
374            }
375        }
376        rendered += frames;
377    }
378    plugin.stop_processing()?;
379
380    crate::audio::write_wav(path, &channels, sample_rate as u32)
381}
382
383/// Offline-render a plugin to a WAV while feeding its audio input from an [`InputSource`]
384/// (a generated test signal or a loaded file) — for auditioning/regression-testing effects
385/// with a known input. Like [`render_to_wav`] but with `input_channels` filled each block.
386///
387/// [`InputSource`]: crate::audio::InputSource
388pub fn render_to_wav_with_input<P: AsRef<Path>>(
389    plugin: &mut Plugin,
390    duration_secs: f64,
391    midi: &[MidiEvent],
392    source: &mut dyn crate::audio::InputSource,
393    path: P,
394) -> Result<()> {
395    if duration_secs < 0.0 {
396        return Err(Error::Other("duration must be non-negative".to_string()));
397    }
398    let sample_rate = plugin.sample_rate();
399    let block = plugin.block_size().max(1);
400    let out_channels = plugin.output_channel_count().max(1);
401    let in_channels = plugin.info().audio_inputs.max(1) as usize;
402    let total_frames = (duration_secs * sample_rate).round() as usize;
403
404    plugin.start_processing()?;
405    for &event in midi {
406        plugin.send_midi_event(event)?;
407    }
408
409    let mut channels: Vec<Vec<f32>> = vec![Vec::with_capacity(total_frames); out_channels];
410    let mut rendered = 0;
411    while rendered < total_frames {
412        let frames = block.min(total_frames - rendered);
413        let mut buffers = AudioBuffers::new(in_channels, out_channels, frames, sample_rate);
414        source.fill(&mut buffers.inputs, frames, sample_rate);
415        plugin.process_audio(&mut buffers)?;
416        for (ch, dst) in channels.iter_mut().enumerate() {
417            if let Some(src) = buffers.outputs.get(ch) {
418                dst.extend_from_slice(&src[..frames.min(src.len())]);
419            }
420        }
421        rendered += frames;
422    }
423    plugin.stop_processing()?;
424
425    crate::audio::write_wav(path, &channels, sample_rate as u32)
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431
432    #[test]
433    fn test_is_valid_plugin() {
434        // Test non-existent path
435        assert!(!is_valid_plugin("/nonexistent/path.vst3"));
436
437        // Test wrong extension
438        assert!(!is_valid_plugin("plugin.dll"));
439        assert!(!is_valid_plugin("plugin.so"));
440
441        // Would need actual plugin files to test positive cases
442    }
443
444    #[test]
445    fn test_host_creation() {
446        // Test that we can create hosts with different configurations
447        let host1 = Vst3Host::builder()
448            .sample_rate(44100.0)
449            .block_size(512)
450            .build();
451        assert!(host1.is_ok());
452
453        let host2 = Vst3Host::builder()
454            .sample_rate(48000.0)
455            .block_size(256)
456            .with_process_isolation(true)
457            .build();
458        assert!(host2.is_ok());
459    }
460}