vst-rs

repository·master·Indexed 21 days ago

https://github.com/rustaudio/vst-rs

A Rust implementation of the VST 2.4 API (version 0.4.0) providing safe abstractions for creating VST plugins and hosts. It includes the AEffect core interface, tools for processing MIDI and SysEx events, audio buffer management via AudioBuffer, and the Editor trait for implementing plugin GUIs.

Tokens
10.6K
Snippets
26
Records
40
Agent score
76%

What's inside vst-rs

  1. Configure parameter knob behavior with KnobMode

    master

    The KnobMode enum allows the host to communicate how parameter knobs should behave when manipulated. This is used via the Editor::set_knob_mode method.

    Supported modes:

    • Circular: Standard circular movement.
    • CircularRelative: Circular movement that behaves relatively.
    • Linear: Linear movement (e.g., sliding up/down).
  2. How to process MIDI and SysEx events

    master

    VST events are passed to the plugin via the Events struct. Because event types (Midi vs SysEx) have different sizes, the VST API uses a generic Event struct that must be cast to the specific type.

    To process events in your process_events() implementation, use the events() method on the Events struct, which provides an iterator over crate::event::Event enums.

    Casting Events

    You can manually cast a raw Event pointer to a specific type using std::mem::transmute:

    # use vst::api::{Event, EventType, MidiEvent, SysExEvent};
    # let mut event: *mut Event = &mut unsafe { std::mem::zeroed() };
    match unsafe { (*event).event_type } {
        EventType::Midi => {
            let midi_event: &MidiEvent = unsafe { std::mem::transmute(event) };
            // Use midi_event...
        }
        EventType::SysEx => {
            let sys_event: &SysExEvent = unsafe { std::mem::transmute(event) };
            // Use sys_event...
        }
        _ => {}
    }
    // Example of manual event casting
    # use vst::api::{Event, EventType, MidiEvent, SysExEvent};
    # let mut event: *mut Event = &mut unsafe { std::mem::zeroed() };
    match unsafe { (*event).event_type } {
        EventType::Midi => {
            let midi_event: &MidiEvent = unsafe { std::mem::transmute(event) };
            // Use midi_event...
        }
        EventType::SysEx => {
            let sys_event: &SysExEvent = unsafe { std::mem::transmute(event) };
            // Use sys_event...
        }
        _ => {}
    }
  3. How PluginInstance and PluginParameters work together

    master

    A PluginInstance represents a loaded VST plugin and implements the Plugin trait. It provides high-level methods for audio processing (process, process_f64), state management (resume, suspend), and metadata retrieval (get_info).

    To manipulate the plugin's parameters (e.g., changing a knob value or reading a label), you must call get_parameter_object(). This returns an Arc<dyn PluginParameters>, which provides methods like:

    • set_parameter(index: i32, value: f32)
    • get_parameter(index: i32) -> f32
    • get_parameter_name(index: i32) -> String
    • change_preset(preset: i32)
    • get_preset_data() -> Vec<u8>
    • load_preset_data(data: &[u8])
  4. Define speaker arrangements with `SpeakerArrangementType`

    master

    The SpeakerArrangementType enum describes how a channel is used within a spatial configuration. It supports the following types:

    • Custom: An arrangement not specified to the host.
    • Empty: An empty arrangement.
    • Mono: A single mono channel.
    • Stereo(StereoConfig, StereoChannel): A stereo channel, specifying its configuration (e.g., L_R, Ls_Rs) and which side of the pair it represents (Left or Right).
    • Surround(SurroundConfig): A surround channel, specifying the surround configuration (e.g., S5_1, S7_1, or S3_0(ArrangementTarget)).

    Helper methods:

    • is_speaker_type(): Returns true if the type is a Surround arrangement.
    • is_left_stereo(): Returns true if the type is a Stereo arrangement and the channel is the Left speaker.
  5. Define Plugin Categories

    master

    The Category enum is used by the host to categorize your plugin. While most plugins are Effect or Synth, other categories exist for specialized tools:

    • Unknown: Not implemented.
    • Effect: General audio effects.
    • Synth: VST instruments.
    • Analysis: Scopes, tuners, spectrum analyzers.
    • Mastering: Dynamics processors for mastering.
    • Spacializer: Panners and spatial tools.
    • RoomFx: Delays and Reverbs.
    • SurroundFx: Dedicated surround processors.
    • Restoration: Denoisers, etc.
    • OfflineProcess: Offline processing tools.
    • Shell: Plugins that contain other plugins.
    • Generator: Tone generators, etc.
  6. Load a VST plugin using PluginLoader

    master

    To load an external VST plugin, use PluginLoader::load. You must provide the path to the library file (e.g., .dll on Windows, .so on Linux, or the mach-o file inside a .vst bundle on macOS) and an Arc<Mutex<T>> where T implements the Host trait.

    Once loaded, you can call .instance() to create a PluginInstance which allows you to interact with the plugin's parameters, process audio, and manage its editor.

    Platform-specific path notes:

    • Linux/Windows: Path to the library (e.g., C:\Plugins\plugin.dll or /path/to/plugin.so).
    • OS X: Path to the mach-o file within the .vst bundle (e.g., /Library/Audio/Plug-Ins/VST/Plugin.vst/Contents/MacOS/PluginHooksVST).
    # use std::path::Path;
    # use std::sync::{Arc, Mutex};
    # use vst::host::{Host, PluginLoader};
    # struct MyHost;
    # impl MyHost { fn new() -> MyHost { MyHost } }
    # impl Host for MyHost {
    #     fn automate(&self, _: i32, _: f32) {}
    #     fn get_plugin_id(&self) -> i32 { 0 }
    # }
    # let host = Arc::new(Mutex::new(MyHost::new()));
    # let path = Path::new(".");
    
    let mut plugin = PluginLoader::load(path, host.clone()).unwrap();
    let instance = plugin.instance().unwrap();
  7. How to create a VST host

    master

    To build a VST host, follow these steps:

    1. Implement the Host trait: Define your host logic (e.g., handling automation) by implementing the Host trait.
    2. Wrap the host for thread safety: Because the host is accessed across multiple threads, you must wrap your host instance in an Arc<Mutex<T>>.
    3. Load the plugin: Use PluginLoader::load by providing the file path to the VST plugin and a clone of your wrapped host. This returns a PluginLoader which can then be used to spawn plugin instances via .instance().

    Once an instance is obtained, you must call .init() before use.

    extern crate vst;
    
    use std::sync::{Arc, Mutex};
    use std::path::Path;
    
    use vst::host::{Host, PluginLoader};
    use vst::plugin::Plugin;
    
    struct SampleHost;
    
    impl Host for SampleHost {
        fn automate(&self, index: i32, value: f32) {
            println!("Parameter {} had its value changed to {}", index, value);
        }
    }
    
    fn main() {
        let host = Arc::new(Mutex::new(SampleHost));
        let path = Path::new("/path/to/vst");
    
        let mut loader = PluginLoader::load(path, host.clone()).unwrap();
        let mut instance = loader.instance().unwrap();
    
        println!("Loaded {}", instance.get_info().name);
    
        instance.init();
        println!("Initialized instance!");
    
        println!("Closing instance...");
        // The instance is shut down when it goes out of scope.
    }
  8. How to create a VST plugin

    master

    To create a VST plugin using vst-rs, you must follow these steps:

    1. Implement the Plugin trait: Your struct must implement Plugin and std::default::Default. The get_info method is mandatory and returns an Info struct containing metadata like name, unique ID, and parameter counts.
    2. Handle Parameters: If your plugin has parameters, implement the PluginParameters trait. Because the host may call parameter methods concurrently with audio processing, you must wrap your parameter implementation in an Arc and return it via the get_parameter_object method in your Plugin implementation.
    3. Export the plugin: Call the plugin_main! macro with your plugin struct name. This macro exports the necessary C-compatible functions (like main_macho on macOS or MAIN on Windows) required for a VST host to load your plugin.

    Note: The VST API is multi-threaded. The host typically calls into the plugin from two distinct threads: the processing thread and the UI thread. The crate's architecture is designed to maintain Safe Rust guarantees across these threads.

    #[macro_use]
    extern crate vst;
    
    use vst::plugin::{HostCallback, Info, Plugin};
    
    struct BasicPlugin;
    
    impl Plugin for BasicPlugin {
        fn new(_host: HostCallback) -> Self {
            BasicPlugin
        }
    
        fn get_info(&self) -> Info {
            Info {
                name: "Basic Plugin".to_string(),
                unique_id: 1357, // Used by hosts to differentiate between plugins.
    
                ..Default::default()
            }
        }
    }
    
    plugin_main!(BasicPlugin); // Important!
  9. Handle VST events using the Event enum

    master

    In vst-rs, communication from the host to the plugin occurs via the Event enum. These events are typically sent to the plugin before calling Plugin::processing() or Plugin::processing_f64(). The Event enum supports three variants:

    1. Midi(MidiEvent): Standard MIDI data.
    2. SysEx(SysExEvent): System Exclusive data blocks, typically used by MIDI controllers.
    3. Deprecated(api::Event): A fallback for legacy event structures.

    When implementing a plugin, you will receive these events to trigger note-on/off, parameter changes, or custom controller data.

    use vst::event::Event;
    
    // Example of matching on an event in a processing loop
    match event {
        Event::Midi(midi_event) => {
            // Handle MIDI data
            let _data = midi_event.data;
        },
        Event::SysEx(sysex_event) => {
            // Handle SysEx payload
            let _payload = sysex_event.payload;
        },
        Event::Deprecated(_raw) => {
            // Handle legacy events
        }
    }
  10. Use HostCallback to communicate with the host

    master

    The HostCallback struct is provided to your plugin during Plugin::new. It allows you to call back into the host to perform actions like automation, requesting time information, or sending events.

    Common Host Actions:

    • automate(index: i32, value: f32): Signals the host that a parameter value has changed.
    • begin_edit(index: i32) / end_edit(index: i32): Use these around parameter changes to signal a user gesture (e.g., dragging a knob), which allows the host to record automation.
    • process_events(&self, events: &api::Events): Sends events from the host to the plugin. Note: This must only be called within process or process_f64.
    • get_time_info(&self, mask: i32) -> Option<TimeInfo>: Requests tempo or position information. Use a bitmask to specify only the information you need to avoid expensive host calculations.
    • update_display(&self): Refreshes the host's UI after parameters have changed.
  11. Use the `plugin_main!` macro to export plugin symbols

    master

    The plugin_main! macro is required to export the necessary symbols for a VST plugin to be recognized and loaded by a host. It handles platform-specific entry points (e.g., main_macho on macOS, MAIN on Windows) and maps them to the internal VSTPluginMain function. Pass the name of your struct that implements the Plugin trait as the argument.

    plugin_main!(YourPluginStruct);
  12. Convert raw API events to high-level Event types

    master

    The Event::from_raw_event method allows you to convert a raw pointer from the underlying VST API into the high-level Event enum used by vst-rs.

    Safety Warning: This is an unsafe function. You must ensure that the provided pointer refers to a valid event of the correct type. For example, if the api::EventType is SysEx, the pointer must point to a valid api::SysExEvent structure, and the system_data and data_size fields must be correct.

    /// # Safety
    /// You must ensure that the given pointer refers to a valid event of the correct event type.
    unsafe fn from_raw_event(event: *const api::Event) -> Event<'a> {
        // ... implementation ...
    }