A Rust integration of VSTGUI for the nih-plug audio plugin framework.
nih_plug_vstgui provides Rust bindings to VSTGUI and integrates it with nih-plug's plugin architecture. VSTGUI is a mature, cross-platform C++ GUI toolkit widely used in professional audio plugin development, particularly for VST3 and AAX plugins.
This integration allows you to create plugin interfaces using VSTGUI's extensive widget library while maintaining Rust's safety guarantees and nih-plug's ergonomic parameter system.
- Cross-platform: Supports Windows (Win32), macOS (Cocoa), and Linux (X11)
- Safe Rust API: Memory-safe wrappers around VSTGUI's C++ API
- Parameter Binding: Automatic synchronization between VSTGUI controls and nih-plug parameters
- Custom Drawing: Support for custom views with drawing primitives
- Event Handling: Mouse and keyboard event support with Rust callbacks
- Resource Management: Safe wrappers for bitmaps, fonts, and other resources
- DPI Scaling: Automatic handling of high-DPI displays
- Thread Safety: Debug-mode checks to ensure VSTGUI is only accessed from the main thread
| Platform | Backend | Status |
|---|---|---|
| Windows | Win32 | ✅ Supported (tested) |
| macOS | Cocoa | ⚪ Untested |
| Linux | X11 | ⚪ Untested |
Note: Current automated/manual testing is Windows-only.
- VSTGUI 4.15.0 UIDescription
parse()crashes on this setup (XML/JSON, memory/file providers). The XML is embedded successfully viainclude_str!, but loading viaUIDescription::from_memoryleads to an access violation in the VSTGUI parser. - Workaround: build UIs programmatically (e.g.,
CustomView) until the upstream parser bug is fixed or VSTGUI is upgraded.
Add this to your Cargo.toml:
[dependencies]
nih_plug = "0.0.0"
nih_plug_vstgui = { path = "path/to/nih_plug_vstgui" }- Rust: 1.70 or later
- CMake: 3.15 or later (for building VSTGUI)
- C++ Compiler: C++17 compatible compiler
Windows:
- Visual Studio 2019 or later
- Windows SDK
macOS:
- Xcode Command Line Tools
- macOS 10.13 or later
Linux:
- GCC 7 or later / Clang 5 or later
- X11 development libraries:
libx11-dev,libxcursor-dev,libxrandr-dev - Cairo development libraries:
libcairo2-dev
Here's a minimal example of a gain plugin with a VSTGUI interface:
use nih_plug::prelude::*;
use nih_plug_vstgui::*;
use std::sync::Arc;
struct GainPlugin {
params: Arc<GainParams>,
editor_state: Arc<VSTGUIState>,
}
#[derive(Params)]
struct GainParams {
#[id = "gain"]
pub gain: FloatParam,
}
impl Default for GainPlugin {
fn default() -> Self {
Self {
params: Arc::new(GainParams::default()),
editor_state: VSTGUIState::new(|| (400, 300)),
}
}
}
impl Plugin for GainPlugin {
// ... plugin implementation ...
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
create_vstgui_editor(
self.editor_state.clone(),
|frame, context| {
// Build your VSTGUI interface here
// This closure is called when the editor window is created
},
)
}
}use nih_plug_vstgui::*;
struct MyCustomView;
impl CustomViewHandler for MyCustomView {
fn draw(&mut self, ctx: &mut DrawContext, rect: Rect) {
// Set background color
ctx.set_color(Color::rgb(40, 40, 40));
ctx.fill_rect(rect);
// Draw a circle
ctx.set_color(Color::rgb(255, 100, 0));
ctx.fill_ellipse(Rect::new(10.0, 10.0, 60.0, 60.0));
}
}
// In your editor creation:
let custom_view = CustomView::new(
Rect::from_size(100.0, 100.0),
Box::new(MyCustomView)
).unwrap();
frame.add_view(custom_view.view().as_ptr()).unwrap();use nih_plug_vstgui::*;
// Assuming you have a control from VSTGUI
let control = unsafe { Control::from_ptr(control_ptr)? };
// Bind it to a parameter
let mut param_control = ParamControl::new(
control,
¶ms.gain,
context.clone(),
);
// The control will now automatically:
// - Update when the parameter changes
// - Call begin_edit/end_edit for automation
// - Set the parameter value when the user interacts with ituse nih_plug_vstgui::*;
struct MyMouseHandler;
impl MouseEventHandler for MyMouseHandler {
fn on_mouse_down(&mut self, event: MouseEvent) -> bool {
println!("Mouse down at ({}, {})", event.position.x, event.position.y);
true // Event handled
}
fn on_mouse_up(&mut self, event: MouseEvent) -> bool {
println!("Mouse up");
true
}
fn on_mouse_moved(&mut self, event: MouseEvent) -> bool {
false // Not handled, allow propagation
}
fn on_mouse_dragged(&mut self, event: MouseEvent) -> bool {
println!("Dragging to ({}, {})", event.position.x, event.position.y);
true
}
}
// Set the handler on a view
view.set_mouse_handler(Box::new(MyMouseHandler));The integration consists of three main layers:
- FFI Layer: Low-level Rust bindings to VSTGUI's C++ API, generated using bindgen and supplemented with manual wrappers
- Safe Wrapper Layer: Idiomatic Rust types that provide memory-safe access to VSTGUI functionality
- Integration Layer: Implementation of nih-plug's Editor trait and connection to the plugin parameter system
For more details, see ARCHITECTURE.md.
- API Documentation - Full API reference available via
cargo doc --open - Architecture Guide - Detailed design and implementation notes
- Troubleshooting Guide - Common issues and solutions
- Platform-Specific Notes - Platform-specific considerations
- VSTGUI and Rust Patterns - Understanding the differences between VSTGUI and Rust
- Example Plugin - Complete working example demonstrating all features
All fallible operations return Result<T, VSTGUIError>. The integration provides comprehensive error handling:
- Null Pointer Checks: All FFI boundaries include null pointer validation
- Panic Safety: All callbacks use
catch_unwindto prevent panics from crossing into C++ - Thread Safety: Debug builds check that VSTGUI is only accessed from the main thread
- Error Logging: Detailed error messages for debugging
Example:
use nih_plug_vstgui::*;
fn create_ui(frame: &mut Frame) -> Result<(), VSTGUIError> {
let view = View::new(Rect::from_size(100.0, 50.0))?;
frame.add_view(view.as_ptr())?;
Ok(())
}Important: VSTGUI is not thread-safe and must be accessed from the main/GUI thread only.
In debug builds, all VSTGUI operations include thread safety checks. Violations are logged and return errors. In release builds, these checks are disabled for performance.
// This will panic in debug builds if called from a non-GUI thread
let frame = Frame::new(rect, parent_window)?;- FFI Overhead: Minimize FFI calls by batching operations when possible
- Parameter Updates: Only update controls when values actually change
- Drawing: Use
invalid_rect()instead ofinvalid()to minimize redraws - Memory: Reuse allocations where possible, especially in hot paths
Contributions are welcome! Please see the main nih-plug repository for contribution guidelines.
This project is licensed under the ISC License - see the LICENSE file for details.
- VSTGUI by Steinberg Media Technologies
- nih-plug by Robbert van der Helm
- The Rust audio plugin development community
- nih-plug - The main plugin framework
- nih_plug_egui - egui integration
- nih_plug_vizia - VIZIA integration
- VSTGUI Documentation - Official VSTGUI docs