Skip to content

CuteDSP/nih-plug-vstgui

Repository files navigation

nih_plug_vstgui

A Rust integration of VSTGUI for the nih-plug audio plugin framework.

Overview

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.

Features

  • 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 Support

Platform Backend Status
Windows Win32 ✅ Supported (tested)
macOS Cocoa ⚪ Untested
Linux X11 ⚪ Untested

Note: Current automated/manual testing is Windows-only.

Known issue: UIDescription parser

  • VSTGUI 4.15.0 UIDescription parse() crashes on this setup (XML/JSON, memory/file providers). The XML is embedded successfully via include_str!, but loading via UIDescription::from_memory leads 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.

Installation

Add this to your Cargo.toml:

[dependencies]
nih_plug = "0.0.0"
nih_plug_vstgui = { path = "path/to/nih_plug_vstgui" }

Build Requirements

  • Rust: 1.70 or later
  • CMake: 3.15 or later (for building VSTGUI)
  • C++ Compiler: C++17 compatible compiler

Platform-Specific Requirements

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

Quick Start

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
            },
        )
    }
}

Usage Examples

Creating a Custom View with Drawing

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();

Binding Parameters to Controls

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,
    &params.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 it

Handling Mouse Events

use 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));

Architecture

The integration consists of three main layers:

  1. FFI Layer: Low-level Rust bindings to VSTGUI's C++ API, generated using bindgen and supplemented with manual wrappers
  2. Safe Wrapper Layer: Idiomatic Rust types that provide memory-safe access to VSTGUI functionality
  3. Integration Layer: Implementation of nih-plug's Editor trait and connection to the plugin parameter system

For more details, see ARCHITECTURE.md.

Documentation

Error Handling

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_unwind to 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(())
}

Thread Safety

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)?;

Performance Considerations

  • FFI Overhead: Minimize FFI calls by batching operations when possible
  • Parameter Updates: Only update controls when values actually change
  • Drawing: Use invalid_rect() instead of invalid() to minimize redraws
  • Memory: Reuse allocations where possible, especially in hot paths

Contributing

Contributions are welcome! Please see the main nih-plug repository for contribution guidelines.

License

This project is licensed under the ISC License - see the LICENSE file for details.

Acknowledgments

  • VSTGUI by Steinberg Media Technologies
  • nih-plug by Robbert van der Helm
  • The Rust audio plugin development community

See Also

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors