#cross-platform #vst #audio #window #plugin

vst_window

Cross-platform windowing library for VST plugins

3 releases (breaking)

0.3.0 Feb 8, 2022
0.2.0 Jan 20, 2022
0.1.0 Sep 27, 2020

#630 in Operating systems

33 downloads per month

MIT/Apache

39KB
610 lines

vst_window

crates.io Docs License

vst_window is a cross-platform windowing library for VST plugins, written in Rust! You can use it to build an editor interface for a plugin using the vst crate.

Ideology

Full cross-platform compatibility is the top priority for this library.

There's no good reason to continue restricting users from access to high quality audio tools on the basis of their platform of choice. Plugin developers should be able to compile and test on the machine they are most comfortable with, and be pleasantly surprised to learn that it "just works" out of the box everywhere else as well.

With that in mind, features will only be upstreamed if they are supported equally on Linux, macOS, and Windows. However, single-platform PRs are always welcome! Development branches for new features will be maintained until cross-platform parity is achieved.

Features

  • Open and close windows using a host-provided pointer
  • Customizable window size
  • Provide a raw-window-handle::RawWindowHandle for rendering with popular Rust graphics backends
  • Recognize mouse movement
  • Recognize mouse buttons
    • Left button press
    • Left button release
    • Right button press
    • Right button release
    • Middle button press
    • Middle button release
  • Recognize mouse scrolling
    • Vertical scrolling
    • Horizontal scrolling
  • Recognize keyboard events
    • Key presses
    • Key releases
  • Update mouse cursor
  • Spawn a compatible parent window for hosts or standalone use

Sample usage

In the wild

ampli-Fe is a minimal yet complete VST plugin example, freely licensed under MIT and Apache-2.0.

From scratch

First, it's recommended to follow the example plugin section from the vst crate to get a working plugin without an editor interface.

Be sure to add vst_window and raw-window-handle as dependencies to your Cargo.toml manifest.

Then, implement vst::editor::Editor for a new struct, such that it can build vst_window cross platform window handle and event source wrappers and manage them.

use core::ffi::c_void;
use vst::editor::Editor;
use vst_window::{setup, EventSource, WindowEvent};

#[derive(Default)]
struct MyPluginEditor {
    renderer: Option<MyRenderer>,
    window_events: Option<EventSource>,
}

const WINDOW_DIMENSIONS: (i32, i32) = (300, 200);

impl Editor for MyPluginEditor {
    fn size(&self) -> (i32, i32) {
        (WINDOW_DIMENSIONS.0 as i32, WINDOW_DIMENSIONS.1 as i32)
    }

    fn position(&self) -> (i32, i32) {
        (0, 0)
    }

    fn open(&mut self, parent: *mut c_void) -> bool {
        if self.window_events.is_none() {
            let (window_handle, event_source) = setup(parent, WINDOW_DIMENSIONS);
            self.renderer = Some(MyRenderer::new(window_handle));
            self.window_events = Some(event_source);
            true
        } else {
            false
        }
    }

    fn is_open(&mut self) -> bool {
        self.window_events.is_some()
    }

    fn close(&mut self) {
        drop(self.renderer.take());
        drop(self.window_events.take());
    }

    fn idle(&mut self) {
        if let Some(window_events) = &mut self.window_events {
            while let Some(event) = window_events.poll_event() {
                match event {
                    WindowEvent::MouseClick(_) => println!("Click!"),
                    _ => (),
                }
            }
        }
        if let Some(renderer) = &mut self.renderer {
            renderer.draw_frame();
        }
    }
}

struct MyRenderer;

impl MyRenderer {
    pub fn new<W: raw_window_handle::HasRawWindowHandle>(_handle: W) -> Self {
        Self
    }
    pub fn draw_frame(&mut self) {
        /* ... */
    }
}

Rendering code is out of the scope of this example, but there are plenty of excellent raw-window-handle compatible rendering solutions. wgpu is a great choice for high-performance cross-platform rendering.

Finally, implement the vst::plugin::Plugin::get_editor method for your plugin. You'll likely want to use a pattern similar to the following:

use vst::{
    editor::Editor,
    plugin::{HostCallback, Plugin},
};
use vst_window::{setup, EventSource, WindowEvent};

struct BasicPlugin {
    editor_placeholder: Option<BasicPluginEditor>,
}

impl Plugin for BasicPlugin {
    // ...

    fn new(_host: HostCallback) -> Self {
        Self {
            editor_placeholder: Some(MyPluginEditor::default()),
        }
    }

    fn get_editor(&mut self) -> Option<Box<dyn Editor>> {
        self.editor_placeholder
            .take()
            .map(|editor| Box::new(editor) as Box<dyn Editor>)
    }
}

With the above code implemented, you should now be able to build and load a plugin with a blank window in your DAW. If the DAW exposes stdout from plugins, you'll see a new "Click!" message whenever the mouse is pressed within the window.

Full code for this sample is available in the examples directory.

Dependencies

~63–560KB