Code Monkey home page Code Monkey logo

nih-plug's Introduction

NIH-plug

Automated builds Tests Docs

NIH-plug is an API-agnostic audio plugin framework written in Rust, as well as a small collection of plugins. The idea is to have a stateful yet simple plugin API that gets rid of as much unnecessary ceremony wherever possible, while also keeping the amount of magic to minimum and making it easy to experiment with different approaches to things. See the current features section for more information on the project's current status.

Check out the documentation, or use the cookiecutter template to quickly get started with NIH-plug.

Table of contents

Plugins

Check each plugin's readme file for more details on what the plugin actually does. You can download the development binaries for Linux, Windows and macOS from the automated builds page. Or if you're not signed in on GitHub, then you can also find the latest nightly build here. You may need to disable Gatekeeper on macOS to be able to use the plugins.

Scroll down for more information on the underlying plugin framework.

  • Buffr Glitch is the plugin for you if you enjoy the sound of a CD player skipping This plugin is essentially a MIDI triggered buffer repeat plugin. When you play a note, the plugin will sample the period corresponding to that note's frequency and use that as a single waveform cycle. This can end up sounding like an in-tune glitch when used sparingly, or like a weird synthesizer when used less subtly.
  • Crisp adds a bright crispy top end to any low bass sound. Inspired by Polarity's Fake Distortion video.
  • Crossover is as boring as it sounds. It cleanly splits the signal into two to five bands using a variety of algorithms. Those bands are then sent to auxiliary outputs so they can be accessed and processed individually. Meant as an alternative to Bitwig's Multiband FX devices but with cleaner crossovers and a linear-phase option.
  • Diopser is a totally original phase rotation plugin. Useful for oomphing up kickdrums and basses, transforming synths into their evil phase-y cousin, and making everything sound like a cheap Sci-Fi laser beam.
  • Loudness War Winner does what it says on the tin. Have you ever wanted to show off your dominance by winning the loudness war? Neither have I. Dissatisfaction guaranteed.
  • Puberty Simulator is that patent pending One Weird Plugin that simulates the male voice change during puberty! If it was not already obvious from that sentence, this plugin is a joke, but it might actually be useful (or at least interesting) in some situations. This plugin pitches the signal down an octave, but it also has the side effect of causing things to sound like a cracking voice or to make them sound slightly out of tune.
  • Safety Limiter is a simple tool to prevent ear damage. As soon as there is a peak above 0 dBFS or the specified threshold, the plugin will cut over to playing SOS in Morse code, gradually fading out again when the input returns back to safe levels. Made for personal use during plugin development and intense sound design sessions, but maybe you'll find it useful too!
  • Soft Vacuum is a straightforward port of Airwindows' Hard Vacuum plugin with parameter smoothing and up to 16x linear-phase oversampling, because I liked the distortion and just wished it had oversampling. All credit goes to Chris from Airwindows. I just wanted to share this in case anyone else finds it useful.
  • Spectral Compressor can squash anything into pink noise, apply simultaneous upwards and downwards compressor to dynamically match the sidechain signal's spectrum and morph one sound into another, and lots more. Have you ever wondered what a 16384 band OTT would sound like? Neither have I.

Framework

Current features

  • Supports both VST3 and CLAP by simply adding the corresponding nih_export_<api>!(Foo) macro to your plugin's library.
  • Standalone binaries can be made by calling nih_export_standalone(Foo) from your main() function. Standalones come with a CLI for configuration and full JACK audio, MIDI, and transport support.
  • Rich declarative parameter system without any boilerplate.
    • Define parameters for your plugin by adding FloatParam, IntParam, BoolParam, and EnumParam<T> fields to your parameter struct, assign stable IDs to them with the #[id = "foobar"], and a #[derive(Params)] does all of the boring work for you.
    • Parameters can have complex value distributions and the parameter objects come with built-in smoothers and callbacks.
    • Use simple enums deriving the Enum trait with the EnumParam<T> parameter type for parameters that allow the user to choose between multiple discrete options. That way you can use regular Rust pattern matching when working with these values without having to do any conversions yourself.
    • Store additional non-parameter state for your plugin by adding any field that can be serialized with Serde to your plugin's Params object and annotating them with #[persist = "key"].
    • Optional support for state migrations, for handling breaking changes in plugin parameters.
    • Group your parameters into logical groups by nesting Params objects using the #[nested(group = "...")]attribute.
    • The #[nested] attribute also enables you to use multiple copies of the same parameter, either as regular object fields or through arrays.
    • When needed, you can also provide your own implementation for the Params trait to enable compile time generated parameters and other bespoke functionality.
  • Stateful. Behaves mostly like JUCE, just without all of the boilerplate.
  • Comes with a simple yet powerful way to asynchronously run background tasks from a plugin that's both type-safe and realtime-safe.
  • Does not make any assumptions on how you want to process audio, but does come with utilities and adapters to help with common access patterns.
    • Efficiently iterate over an audio buffer either per-sample per-channel, per-block per-channel, or even per-block per-sample-per-channel with the option to manually index the buffer or get access to a channel slice at any time.
    • Easily leverage per-channel SIMD using the SIMD adapters on the buffer and block iterators.
    • Comes with bring-your-own-FFT adapters for common (inverse) short-time Fourier Transform operations. More to come.
  • Optional sample accurate automation support for VST3 and CLAP that can be enabled by setting the Plugin::SAMPLE_ACCURATE_AUTOMATION constant to true.
  • Optional support for compressing the human readable JSON state files using Zstandard.
  • Comes with adapters for popular Rust GUI frameworks as well as some basic widgets for them that integrate with NIH-plug's parameter system. Currently there's support for egui, iced and VIZIA.
    • A simple and safe API for state saving and restoring from the editor is provided by the framework if you want to do your own internal preset management.
  • Full support for receiving and outputting both modern polyphonic note expression events as well as MIDI CCs, channel pressure, and pitch bend for CLAP and VST3.
    • MIDI SysEx is also supported. Plugins can define their own structs or sum types to wrap around those messages so they don't need to interact with raw byte buffers in the process function.
  • Support for flexible dynamic buffer configurations, including variable numbers of input and output ports.
  • First-class support several more exotic CLAP features:
    • Both monophonic and polyphonic parameter modulation are supported.
    • Plugins can declaratively define pages of remote controls that DAWs can bind to hardware controllers.
  • A plugin bundler accessible through the cargo xtask bundle <package> <build_arguments> command that automatically detects which plugin targets your plugin exposes and creates the correct plugin bundles for your target operating system and architecture, with cross-compilation support. The cargo subcommand can easily be added to your own project as an alias or globally as a regular cargo subcommand.
  • Tested on Linux and Windows, with limited testing on macOS. Windows support has mostly been tested through Wine with yabridge.
  • See the Plugin trait's documentation for an incomplete list of the functionality that has currently not yet been implemented.

Building

NIH-plug works with the latest stable Rust compiler.

After installing Rust, you can compile any of the plugins in the plugins directory in the following way, replacing gain with the name of the plugin:

cargo xtask bundle gain --release

Plugin formats

NIH-plug can currently export VST3 and CLAP plugins. Exporting a specific plugin format for a plugin is as simple as calling the nih_export_<format>!(Foo); macro. The cargo xtask bundle command will detect which plugin formats your plugin supports and create the appropriate bundles accordingly, even when cross compiling.

Example plugins

The best way to get an idea for what the API looks like is to look at the examples.

  • gain is a simple smoothed gain plugin that shows off a couple other parts of the API, like support for storing arbitrary serializable state.
  • gain-gui is the same plugin as gain, but with a GUI to control the parameter and a digital peak meter. Comes in three exciting flavors: egui, iced, and VIZIA.
  • midi_inverter takes note/MIDI events and flips around the note, channel, expression, pressure, and CC values. This example demonstrates how to receive and output those events.
  • poly_mod_synth is a simple polyphonic synthesizer with support for polyphonic modulation in supported CLAP hosts. This demonstrates how polyphonic modulation can be used in NIH-plug.
  • sine is a simple test tone generator plugin with frequency smoothing that can also make use of MIDI input instead of generating a static signal based on the plugin's parameters.
  • stft shows off some of NIH-plug's other optional higher level helper features, such as an adapter to process audio with a short-term Fourier transform using the overlap-add method, all using the compositional Buffer interfaces.
  • sysex is a simple example of how to send and receive SysEx messages by defining custom message types.

Licensing

The framework, its libraries, and the example plugins in plugins/examples/ are all licensed under the ISC license. However, the VST3 bindings used by nih_export_vst3!() are licensed under the GPLv3 license. This means that unless you replace these bindings with your own bindings made from scratch, any VST3 plugins built with NIH-plug need to be able to comply with the terms of the GPLv3 license.

The other plugins in the plugins/ directory may be licensed under the GPLv3 license. Check the plugin's Cargo.toml file for more information.

nih-plug's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nih-plug's Issues

Make skew_factor functions const

The skew_factor functions inside of FloatRange are not currently const. This means they can't be used when creating FloatRange's in a const context, meaning they can't be used for FloatRange::Reversed.

Minimum reproduction:

use nih_plug::prelude::*;
use std::sync::Arc;

static RATIO_RANGE: FloatRange = FloatRange::Skewed {
    min: 0.0,
    max: 1.0,
    factor: FloatRange::skew_factor(-1.0),
};

#[derive(Params)]
struct PluginParams {
    pub param: FloatParam,
}

impl Default for PluginParams {
    fn default() -> Self {
        Self {
            param: FloatParam::new(
                "param",
                1.0,
                FloatRange::Reversed(&RATIO_RANGE),
            )
        }
    }
}

Possible to use DISTRHO's travesty bindings for VST3?

Hi, I'm not sure if this is the right place to ask, but I wonder whether it would be feasible to use DISTRHO/DPF's travesty bindings for VST3 support instead of vst3-sys. travesty is a pure C interface to VST3 that is liberally licensed (ISC licence), unlike vst3-sys which is GPLv3 due to its reliance on Steinberg's dual-licensed SDK, so that should be preferable for the MIT licensing model on nih-plug if anyone wishes to build commercial plugins with it. They can be found here: https://github.com/DISTRHO/DPF/tree/df6d7f50377266b1869c6c590ff7412d2241856e/distrho/src/travesty

Unfortunately there isn't really much documentation for this, but DPF itself could be used as a reference I imagine, as it does itself use travesty to provide its VST3 support. Cardinal (also by DISTRHO) is a popular plugin built with DPF, and I can personally confirm that the VST3 build works well. I'd love to hear your thoughts and whether you think it would be reasonable to implement here.

Keyboard input

Hello there,
is it possible to have keyboard input for plugins?

Tried with VST3 and egui in REAPER, however I was only able to get several special characters - [;
Tried also REAPER's option Send all keyboard input to plugin.
Tried building the examples to test, however they don't seem to have place for keyboard input (or maybe I missed that).

Is there any special setup for keyboard input required? Tried requiring the focus on egui, however still no luck.

How to use state variables which don't implement Sync trait?

Hi,
I'm trying to implement a variable clock delay plugin in the NIH-plug framework. Here's the WIP: https://github.com/teowoz/nih-plug/tree/varispeed-delay

The problem is, it looks like all members of plugin state struct must implement Sync trait, but the library I want to use (rubato) doesn't implement it:

$ cargo xtask bundle varispeed_delay --release
    Finished release [optimized] target(s) in 0.10s
     Running `target/release/xtask bundle varispeed_delay --release`
   Compiling varispeed_delay v0.1.0 (/home/teo/Projects/nih-plug/plugins/varispeed_delay)
error[E0277]: `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)` cannot be shared between threads safely
  --> plugins/varispeed_delay/src/lib.rs:84:6
   |
84 | impl Plugin for VariSpeedDelay {
   |      ^^^^^^ `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)` cannot be shared between threads safely
   |
   = help: the trait `Sync` is not implemented for `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)`
   = note: required because of the requirements on the impl of `Sync` for `Unique<(dyn rubato::asynchro::SincInterpolator<f64> + 'static)>`
   = note: required because it appears within the type `Box<(dyn rubato::asynchro::SincInterpolator<f64> + 'static)>`
   = note: required because it appears within the type `SincFixedIn<f64>`
note: required because it appears within the type `VariSpeedDelay`
  --> plugins/varispeed_delay/src/lib.rs:25:8
   |
25 | struct VariSpeedDelay {
   |        ^^^^^^^^^^^^^^
note: required by a bound in `nih_plug::prelude::Plugin`
  --> /home/teo/Projects/nih-plug/src/plugin.rs:26:36
   |
26 | pub trait Plugin: Default + Send + Sync + 'static {
   |                                    ^^^^ required by this bound in `nih_plug::prelude::Plugin`

error[E0277]: `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)` cannot be shared between threads safely
   --> plugins/varispeed_delay/src/lib.rs:189:6
    |
189 | impl Vst3Plugin for VariSpeedDelay {
    |      ^^^^^^^^^^ `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)` cannot be shared between threads safely
    |
    = help: the trait `Sync` is not implemented for `(dyn rubato::asynchro::SincInterpolator<f64> + 'static)`
    = note: required because of the requirements on the impl of `Sync` for `Unique<(dyn rubato::asynchro::SincInterpolator<f64> + 'static)>`
    = note: required because it appears within the type `Box<(dyn rubato::asynchro::SincInterpolator<f64> + 'static)>`
    = note: required because it appears within the type `SincFixedIn<f64>`
note: required because it appears within the type `VariSpeedDelay`
   --> plugins/varispeed_delay/src/lib.rs:25:8
    |
25  | struct VariSpeedDelay {
    |        ^^^^^^^^^^^^^^
note: required by a bound in `nih_plug::prelude::Vst3Plugin`
   --> /home/teo/Projects/nih-plug/src/plugin.rs:201:23
    |
201 | pub trait Vst3Plugin: Plugin {
    |                       ^^^^^^ required by this bound in `nih_plug::prelude::Vst3Plugin`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `varispeed_delay` due to 2 previous errors
Error: Could not build varispeed_delay

The compiler says that resampler struct cannot be shared between threads safely, but it won't be used in multiple threads at once anyway, so why is the Sync trait required?
Is there way around it? Sorry if it's a stupid question, I'm just starting to learn Rust.

FL Studio problem + some SPECTRAL COMPRESSOR suggestions

When I load your VST3 versions into FL Studio 12 (on Win 10 x64) most of them (except 3) show no GUI at all, so they are useless.

ON THE OTHER HAND, I am totally blown off by your SPECTRAL COMPRESSOIR which is the only one to my knowledge that supports both downward and upward compression at the same time and does it without artifacts (the only other one is that internal Cubase Pro 12 plug but it is basically useless, very bad plug although it comes from Steinberg, believe it or not!) + it contains such unique parameters that it makes me thrilled even to tweak them (haha) so it might become one of my most beloved comps!

FLOOR VALUE FOR THE UPWARD COMP (which I actually never saw on any upward compressor, wondering why in the world?!), so that one can tell OK, does not upward anything below specific level (basically low-end limit so that it would avoid upwarding hiss and hum) - what do you think, could you implement it?

INPUT PARAMETER VALUES MANUALLY BY KEYBOARD TYPING, not just ad hoc with the mouse (when I double left click the parameter value it just resets to defaults instead of becoming input filed where one could type the desired parameter value + I also tried CTRL, ALT, SHIFT combination...nothing, well actually by ALT+LMB it seems the value becomes selectable but not changeable - typing does nothing, it only selects the value).

INITIALIZE PLUGIN WITH THIS PARAMETERS: this would make any new instance automatically load such by default, it would replace its internal default values - would be quite handy so one does not need to do repetitive tasks all the time (sure, I know I can save it as preset, but still it is several clicks more compared to default values loaded immediately with the plug instance being loaded into fx slot :-D).

BYPASS BUTTON would be great too, to have it - as other plugs do - inside its GUI (I see you have MIX value there which is really great, but why do I need to constantly setting mix to 0% only to hear the difference without the plug, right?).

HOST SYNC FOR INDIVIDUAL VALUES - each individual parameter should have its own SYNC option, meaning you could select this for some parameters, while others would stay as normal (that is when option SYNC is enabled values in milliseconds would turn into 1/1, 1/2, 1/4, 1/8 etc.): it would be great to have this implemented, so that we can all "ins" ands "outs" nicely "pump" with the actual tempo without us manually trying to hit the right value. Like having HOST SUNC for Attack and Release would make things much much much easier to adjust than actual simple numeric value, right?

Implementing the Param trait

Use case + Background info

I have recently come upon a few use cases where I found that the Param implementations included with nih-plug are not super well-suited, so I wanted to implement a custom Param.

However, I found a few things that I did not expect: The as_ptr method requires me to construct a ParamPtr instance, which is not possible for fully custom implementations since the possible types of parameters are hard-coded in nih-plug. Still, as I was delegating a bit of my Params basic functionality to an IntParam anyway, I could return something like self.inner.as_ptr() here.

Next, I found that a few methods from my Param implementation were never called (normalized_value_to_string, string_to_normalized_value). As far as I see, these are only ever called through ParamPtr - so even though I have to provide these functions, they are never used. For my concrete case, I could also hack around this by customizing my underlying IntParam using with_value_to_string and with_string_to_value.

The actual questions

Implementing Param out-of-tree seems rather unergonomic to me. Am I missing some obvious alternative here? Is this currently an intended use of Param at all?

Parameter inconsistency on startup with VST3 host

First of all, this is a great project. Thanks for building something hat can set so much creativity in motion.

I've noticed that on startup the parameter values reported by my VST3 host do not match the internal state of the plugin. As in, the nob on the host will be set to zero, but the plugin will clearly have a different value. As soon as I touch the nob things get in sync. This is true for the gain example as well as Diopser and the project that I am starting on. I have also noticed that the plugin host reports that the plugin has zero parameter inputs.

I have been using Carla as VST host, which seems to work fine, although it took me a while to realize that you have to set your VST3 path to the bundled directory or the plugins won't be found. (You also have to close the scanning window before new plugins will show up in the list). I am open to an alternative host if anyone has a recommendation.

Bonus question: It seems like Buffer always contains f32s. Is there any support for f64 there?

[macOS] [gain_gui] Wrong Cursor Position

OS: macOS 12.2.1
DAW: REAPER 6.49

First of all, thank you for building this framework. It looks very cool.

I wanted to try out gain_gui, so I built it using cargo xtask bundle gain_gui --release --bundle-vst3, then copied target/gain_gui.vst3 to /Library/Audio/Plug-Ins/VST3.

While the plugin works, the cursor position appears to be off. This is most obvious with the slider, which isn't draggable unless I drag on a specific spot below the handle. Maybe a scaling issue?

Screen.Recording.2022-02-25.at.18.28.20.mov

I understand that this stuff hasn't been tested on macOS, so feel free to close this issue if it's not actionable :)

README typos

Wanted to open a new PR for these, but didn't have access to push a new branch for changes.

Crossover is as boring as it sounds. It splits cleanly splits the signal into two to five bands using a variety of algorithms and outputs those through auxiliary outputs so they can be accessed and processed individually. Meant as an alternative to Bitwig's Multiband FX devices but with cleaner crossovers and a linear-phase option.

splits cleanly splits

[Feature] flake.nix/dockerfile for simple cross compilation

I want to be able to enter an environment and be able to build for all targets with no extra setup.

Currently cross compiling isn't smooth, and setting up an environment with the correct development dependencies is not always straight forward.

Nix Flakes are great for this, but a Dockerfile (where I have started) works too, albeit not as nice to work with as Flakes.

It's taken me a few days to figure out the linux/linux and linux/windows build environments.
Not sure how far I'll get with osx pipeline.

I also want to thank you so much for putting all this together. I've been dreaming of making a VST for about 10 years and now I feel I'm able to. Besides setting up the environment, you've made it a straight forward process <3.

# Can build linux and windows, gain_gui_iced
FROM rust

RUN apt-get update  \
    && \
    apt-get install -y \
    libasound2-dev \
    libgl-dev \ 
    libjack-dev \
    libxcb1-dev \
    libxcb-icccm4-dev \
    libxcursor-dev \
    libxkbcommon-dev \
    libxcb-shape0-dev \
    libxcb-xfixes0-dev \
    libx11-xcb-dev \
    libxcb-dri2-0-dev \
    curl \
    python3 \
    cmake \
    clang \
    gcc \
    g++ \
    zlib1g-dev \
    libmpc-dev \
    libmpfr-dev \
    libgmp-dev \
    gcc-mingw-w64 \
    && \
    rustup target add \
    x86_64-pc-windows-gnu \
    x86_64-apple-darwin \
    && \
    cargo install cross 


RUN git clone https://github.com/tpoechtrager/osxcross /usr/local/osxcross &&\
    cd /usr/local/osxcross && \ 
    wget -nc https://s3.dockerproject.org/darwin/v2/MacOSX10.10.sdk.tar.xz && \
    mv MacOSX10.10.sdk.tar.xz tarballs/ && \
    UNATTENDED=yes OSX_VERSION_MIN=10.7 ./build.sh 


ENV PATH "$(pwd)/osxcross/target/bin:$PATH"

WORKDIR /home/local/project

ENTRYPOINT ["/bin/bash"]

midi_inverter example could not be opened in Ableton Live

On mac when exporting the midi_inverter example with cargo xtask bundle midi_inverter --release, Ableton Live finds the vst3 plugin but theres an error when adding it to a track:

Failed to create the VST3 plug-in "MIDI Inverter"
The VST3 plug-in could not be opened.

Screenshot 2023-01-11 at 21 45 34

Gain and Poly mod synth examples works without any issues.

[Question] How to add logging to a plugin?

What's the best way to add logging (to a log file) to a plugin? I tried the following, which works with rust vst2 (adapted from https://github.com/DGriffin91/egui_baseview_test_vst2). It creates the log file, but doesn't log anything to it:

use simplelog::{Config};

pub(crate) fn init(name: String, version: i32) {
    if let Some(local_dir) = dirs::data_local_dir() {
        // check if clockwork-vst folder exists, if not create it
        let mut path = local_dir.clone();
        path.push("clockwork-vst");
        path.push("logs");
        if !path.exists() {
            std::fs::create_dir_all(&path).ok();
        }

        let logging_dir = path.clone();
        if logging_dir.exists() {
            let filename = format!("{name}-{version}-log.txt");
            let log_file_path = logging_dir.join(filename);

            if let Ok(log_file) = std::fs::OpenOptions::new()
                .append(true)
                .create(true)
                .open(&log_file_path)
            {
                simplelog::WriteLogger::init(simplelog::LevelFilter::Info, Config::default(), log_file)
                    .ok();
                log_panics::init();
                log::info!("Starting VST");

            } else {
                panic!("Could not open log file");
            }
        }
    }
}

[Feature] Supporting Web UI with Tauri

Hey, thanks for your great work!
I would love to never touch JUCE again and stick to this framework for audio plugins.
As I was building some Juce Plugins with Web UI using Websockets to connect to the audio process, I was wondering if it would not be way more elegant with Tauri's messaging system in Plugins.
It is just soo much faster to write guis in javascript, that this would be a killer argument for using Rust for plugins.

So have you thought about integrating Tauri as UI Framework or do you see a possibility for this at all?

#[nested] does not work correctly

Structs implementing Params using the derive marco can have their fields annotated with #[nested([array | id_prefix = "foo"], [group = "group name"]) to nest Params structs, prefixing/suffixing them accordingly to ensure parameter ids are unique. Simply using #[nested] should just include the nested struct's parameters, without doing any prefixing. This worked previously, but no longer does.

`SysExMessage::Buffer` requires `Default`, limiting buffer size to 32 bytes

Hi there!

Thank you so much for all the hard work that's gone into NIH-plug and its ecosystem.

I'm just wondering about the Default requirement on the SysExMessage::Buffer type, effectively limiting the size to 32 bytes because of delightful Rust array shenanigans. I removed this bound locally: everything compiles and all of the tests still pass.

This Default doesn't seem necessary here, since the buffer is custom-created via calls to to_buffer. The code internals clearly don't rely on this bound and making the type more permissive should be strictly non-breaking. For instance, you could still call Buffer::default() on smaller buffers as before (if you're not doing something more direct like [a, b, c, d, ..]), whereas [0; N]` or similar would make larger arrays doable.

As for the motivation: for my plugin work, I need comparatively huge SysEx buffers (typically 1kB+) to collect incoming notation/articulation/arbitrary text information from the DAW, update this data and add/modify MIDI events in various ways based on the data contained therein. For debug builds I use a human-readable format but switch to a more compact binary representation for release. It's all working phenomenally well so far, especially with VST3, and I would really like to use the official repo instead of my patched version.

If there are no objections, I'd be happy to submit a pull request for this one-liner.

Thanks again,
Iain

Translate db_to_gain before smoothing the parameter

You should definitely add the ability for a parameter to translate its value using util::db_to_gain before smoothing it.

The current gain example is woefully inefficient since it is converting from db to gain every single sample:

for channel_samples in buffer.iter_samples() {
    // Smoothing is optionally built into the parameters themselves
    let gain = self.params.gain.smoothed.next();

    for sample in channel_samples {
        *sample *= util::db_to_gain(gain);
    }
}

This is how baseplug implements this: https://github.com/wrl/baseplug/blob/trunk/src/parameter.rs#L116

Headless build

I am new to nih-plug. To me this project looks very interesting. I was able to build a CLAP plugin for Reaper in a few hours including some small UI. Thats very cool, thanks for all your valuable work.

My next experiment was to build a VST3 plugin to run on Elk Audio OS (completly headless). I dont need any UI for this plugin.

However, already when building (the starting point was the examples/gain plugin) I recognised that there are dependencies to X11. I first thought its just for the standalone version, but when I tried to run the plugin in the target system I got the following error:

[2023-03-30 17:49:41.871] [error] [vst3] Failed to load VST3 Module: dlopen failed.
libxcb.so.1: cannot open shared object file: No such file or directory

Can I use some build flags to completely remove the X11 or any other UI dependencies?

Many thanks

Fork assert_no_alloc to add logging support and backtraces for the assert_process_allocs feature

The assert_process_allocs feature is very useful you'll immediately know whether or not you're allocating in your process function. The problem is that while this does work, it can be tricky to know what is causing those allocations. We could improve this by having it use the logger instead of printing directly to STDERR, and to have it print a backtrace in addition to the allocation size.

[NIH-PLUG+EGUI] How to scale interface? egui_ctx.set_pixels_per_point(2.0) has no effect.

Hi there. I'm pretty new in Rust and audio in general.

Recently i made simple app in egui - calculator, and able to do interface scaling through

egui_ctx.set_pixels_per_point(5.0);

works fine.

So i tried same in egui+nih-plug:

        create_egui_editor(
            self.params.editor_state.clone(),
            (),
            |_, _| {},
            move |egui_ctx, setter, _state| {
                egui::CentralPanel::default().show(egui_ctx, |ui| {
                    // TODO: this is not working 
                    egui_ctx.set_pixels_per_point(5.0);
                    // configure_text_styles(egui_ctx);

And it's do not has visible changes. What i'm doing wrong?

Safe way to set many Params at once? (VST3 threading issue)

Hello!
I'm working on a preset browser for my second NIH-plug + egui plugin, and I'm running into issues trying to restore a preset from the UI thread. Many VST3 hosts seem to crash when modifying ~150 plugin params all at once. I've seen it so far in Waveform11/12, Ardour, and Bitwig Studio.

My initial attempt used ParamSetter:

for (param, value) in list_of_params {
        setter.begin_set_parameter(param);
        setter.set_parameter(param, value);
        setter.end_set_parameter(param);
}

... that appeared to work in Standalone and CLAP, but VST3 crashes in the hosts I tried.

My second attempt used the GuiContext directly like this:

let ctx = &setter.raw_context;
let mut state = ctx.get_state().clone();
// Update the state.params here...
ctx.set_state(state);

... that had the same outcome, but also appeared to be executing on the wrong thread according to this Carla error:

JUCE Assertion failure in juce_VST3PluginFormat.cpp:3636

Is there a safe way to set all these parameters?

KeyEvents not received on windows

On my Debian machine, I'm able to process key events with the following code:

for evt in i.events.iter() {
    if let egui::Event::Key{key, pressed, repeat, ..} = evt {
        //Do stuff
    }
}

This does not work on windows. I've confirmed with nih_log!() that there are no actual key events to process. Any thoughts on why the plugin is receiving events on one platform but not the other? I've run it both in standalong JACK mode and as a plugin under Carla with the same behavior.

FPS drops when GUI displayed on FL Studio

I'm creating a VST plugin with nih-plug. When using the created plugin on FL Studio, the FPS of the entire FL Studio drops (from 60 to 30) while displaying the GUI.
I've confirmed the same with gain_gui_egui and gain_gui_iced.

While the FPS drops, I see the CPU usage rise, so I thought it might be in a busy loop somewhere, but I haven't been able to locate it.

My environment: windows 11, FL Studio 21

Subtle eroding of velocity values (CLAP only)

Hi again folks!

I recently noticed a weird thing, after much scratching of the head, that whenever I put an nih-plug CLAP plugin on a track, processed MIDI events would generally decrease their velocity by a small amount: most notes would lose 1 "point" of velocity per instance, while some would remain unchanged. Other details, such as CC values, were unaffected, however. For reference, this was with the latest Reaper version on Windows 11, even for plugins that do absolutely nothing to the MIDI (see the bottom of this post for a quick example).

I tried the equivalent VST3 builds and everything was fine. I considered that this could be a fault in Reaper's handling of CLAP, but I downloaded another CLAP plugin to see (namely, Ny by Full Bucket Music) and that one's MIDI-thru worked perfectly.

After this initial round of testing, I thought that maybe there was a calibration error between the velocity values of nih-plug and CLAP, and that does indeed seem to be the case: as far as I can tell, CLAP's f64 velocities operate on a range from 0.0 (0) to 1.0 (128) (i.e. value / 128.0) whereas here we use value / 127.0 for our f32s (EDIT: oops, wrote that backwards before). In any case, I patched the velocity conversion logic and the problem went away completely, working across the full range of inputs. A pull request is incoming with these minor changes for consideration.

Thanks as always and all the best,
Iain

use std::sync::Arc;

use nih_plug::prelude::*;

#[derive(Default, Params)]
pub struct PluginParams {}

#[derive(Default)]
pub struct Stub {
    params: Arc<PluginParams>,
}

impl Plugin for Stub {
    const NAME: &'static str = "stub";
    const VENDOR: &'static str = "stubby stubbins";
    const URL: &'static str = "[stub.org](http://stub.org/)";
    const EMAIL: &'static str = "[[email protected]](mailto:[email protected])";
    const VERSION: &'static str = env!("CARGO_PKG_VERSION");
    const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[];
    const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs;
    const MIDI_OUTPUT: MidiConfig = MidiConfig::MidiCCs;
    const SAMPLE_ACCURATE_AUTOMATION: bool = true;

    type SysExMessage = ();
    type BackgroundTask = ();

    fn params(&self) -> Arc<dyn Params> {
        self.params.clone()
    }

    fn process(
        &mut self,
        _buffer: &mut Buffer,
        _aux: &mut AuxiliaryBuffers,
        context: &mut impl ProcessContext<Self>,
    ) -> ProcessStatus {
        while let Some(event) = context.next_event() {
            context.send_event(event);
        }

        ProcessStatus::Normal
    }

    fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
        None
    }

    fn initialize(
        &mut self,
        _audio_io_layout: &AudioIOLayout,
        _buffer_config: &BufferConfig,
        _context: &mut impl InitContext<Self>,
    ) -> bool {
        true
    }
}

impl ClapPlugin for Stub {
    const CLAP_ID: &'static str = "org.stub.stubby";
    const CLAP_DESCRIPTION: Option<&'static str> = None;
    const CLAP_MANUAL_URL: Option<&'static str> = None;
    const CLAP_SUPPORT_URL: Option<&'static str> = None;
    const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::NoteEffect, ClapFeature::Utility];
}

impl Vst3Plugin for Stub {
    const VST3_CLASS_ID: [u8; 16] = *b"stubstubstubstub";
    const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
        &[Vst3SubCategory::Instrument, Vst3SubCategory::Tools];
}

nih_export_clap!(Stub);
nih_export_vst3!(Stub);

SysEx message support

Hello,
first, thanks for the nih-plug library. :)

Would it be possible to add SysEx message support? Just a generic support for sending an array of bytes would be enough.

[nih_plug_iced] Two different versions of baseview if updated

Hi there!

I am trying to use nih_plug_iced and found that I have two different versions of baseview, which is not allowing me to build.

nih_plug_iced <- baseview
nih_plug_iced <- ice_baseview <- baseview

These resolve as two incompatible versions of the baseview (I think).

First
https://github.com/RustAudio/baseview.git#f0639b78

Second
https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25#1d9806d5

Currently seeing if patching in Cargo.toml helps.

Running the following gives me this output:

cargo update &&
git diff Cargo.lock |
grep -n baseview
 name = "baseview"
-source = "git+https://github.com/RustAudio/baseview.git#c129b12ead4f5ac02126f559ceb8ce43cc982200"
+source = "git+https://github.com/RustAudio/baseview.git#f0639b787bbda506434d3f6b5c91e94ca59904c6"
 name = "iced_baseview"
-source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#07f1b07d3a8bf2a9af0ce6ee57443105ac99de33"
+source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#df3a852a15cf0e9fcc8d2b32f5718e56780beaf3"
- "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git)",
+ "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25)",
  "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25)",

It shows that the newest commit of iced_baseview doesn't work as expected.

-source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#07f1b07d3a8bf2a9af0ce6ee57443105ac99de33"
+source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#df3a852a15cf0e9fcc8d2b32f5718e56780beaf3"

Memory leak when opening and closing plugin window

Every time I close and reopen a plugin's window, its plugin host process in Bitwig uses more memory, resetting only on deactivating or deleting the plugin instance. I can increase the memory footprint indefinitely by repeatedly opening and closing the plugin window. This doesn't happen with any of the non nih-plug plugins that I've tried.

On macOS Sonoma, the memory increase is ~50 mb each time for Diopser, ~40mb for Crisp, and ~10mb for gain_gui_egui. I didn't notice a difference between CLAP and VST3.

I tested Diopser on Windows 11 as well, and the leak was much smaller: ~2mb each time.

[Crash] Plugin crashes when inserting item into Hash Map (process function)

I defined my Plugin struct like so:

struct ClockworkPlugin {
    params: Arc<ClockworkPluginParams>,
    active_notes: HashMap<u8, NoteEvent>,
    last_midi_send_timestamp: SystemTime,
}

In the process function, I call on_note_on when a note on Event is received:

fn process(
    &mut self,
    _buffer: &mut Buffer,
    _aux: &mut AuxiliaryBuffers,
    context: &mut impl ProcessContext,
) -> ProcessStatus {
    while let Some(event) = context.next_event() {
        match event {
            NoteEvent::NoteOn { .. } => self.on_note_on(event),
            NoteEvent::NoteOff { .. } => self.on_note_off(event),
            _ => (),
        }
    }
    self.on_midi_send_opportunity(context);
    ProcessStatus::Normal
}

In on_note_on I am trying to insert the triggered note into the Hash Map:

fn on_note_on (&mut self, note_event: NoteEvent) {
    if let NoteEvent::NoteOn {note, ..} = note_event {
        self.active_notes.insert(note, note_event);
    }
}

When running the plugin, it crashes as soon as it receives MIDI input. By commenting out other parts of the code, I pinned down the crash to the self.active_notes.insert(note, note_event); line.

Link to the full code: https://github.com/AlexW00/clockwork/blob/master/src/lib.rs

How to avoid this crash? I already tried wrapping the HashMap in a Mutex, but it still crashes.

Fail to build diopser, spectral_compressor

I'm having trouble building Diopser. cargo xtask bundle gain --release is running fine, but cargo xtask bundle diopser --release gives:

error[E0554]: `#![feature]` may not be used on the stable release channel
  --> src/lib.rs:86:39
   |
86 | #![cfg_attr(feature = "simd", feature(portable_simd))]
   |                                       ^^^^^^^^^^^^^

So I tried cargo +nightly xtask bundle diopser --release , but that gives me some linker errors:

  = note: /usr/bin/ld: cannot find -lxcb-icccm: No such file or directory
          collect2: error: ld returned 1 exit status
          

error: could not compile `diopser` (lib) due to previous error
Error: Could not build diopser

The same thing happens with spectral_compressor.

I'm on Fedora 39. I installed libXcm-devel and some other packages I thought could solve this, but it didn't help.

Universal binaries for macOS

Creating this issue as a reminder for myself to implement this. Cargo does not support universal binaries out of the box (rust-lang/cargo#8875) which is why I haven't added this yet, but adding them should not be as I initially thought. This would involve a new build flag that builds for both the native target and for the 'other' target (either x86_64-apple-darwin or aarch64-apple-darwin), and then replaces the binary in target/{debug,release} with a lipo'd version before continuing. I've done something similar in clap-validator here: https://github.com/robbert-vdh/clap-validator/blob/6386b97cc8eafc7c9439550ddf27acaa1700bfe5/.github/workflows/build.yml#L128

VST3 Parameters not appearing in FL Studio 20

Hey there, I'm compiling the cookiecutter template on windows using:
cargo xtask bundle >plugin name< --release

And in my Cargo.toml I have
nih_plug = {git = "https://github.com/robbert-vdh/nih-plug.git", default_features = false, features = ["vst3"] }

The VST3 plugin processes perfectly fine, however the parameters don't appear when I load it into FL20. It's just an empty box.
image

It seems to work fine in Ableton though.
image

Example plugin can not build

Hello, I am new to rust audio so I just want to learn from the examples. I cloned this repository but when I try to run with cargo xtask bundle gain --release it failed to compile. I am using the lastest stable Rust on windows 11 and the error message look like this:

error[E0369]: binary operation `==` cannot be applied to type `LitStr`
  --> nih_plug_derive\src\params.rs:65:55
   |
65 |                         if params.iter().any(|p| p.id == s) {
   |                                                  ---- ^^ - LitStr
   |                                                  |
   |                                                  LitStr

error[E0369]: binary operation `==` cannot be applied to type `LitStr`
   --> nih_plug_derive\src\params.rs:106:67
    |
106 |                         if persistent_fields.iter().any(|p| p.key == s) {
    |                                                             ----- ^^ - LitStr
    |                                                             |
    |                                                             LitStr

   Compiling assert_no_alloc v1.1.2 (https://github.com/robbert-vdh/rust-assert-no-alloc.git?branch=nih-plug#95120d99)
error[E0277]: `LitStr` doesn't implement `Debug`
   --> nih_plug_derive\src\params.rs:475:5
    |
470 | #[derive(Debug)]
    |          ----- in this derive macro expansion
...
475 |     id: syn::LitStr,
    |     ^^^^^^^^^^^^^^^ `LitStr` cannot be formatted using `{:?}` because it doesn't implement `Debug`
    |
    = help: the trait `Debug` is not implemented for `LitStr`
    = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `LitStr` doesn't implement `Debug`
   --> nih_plug_derive\src\params.rs:484:5
    |
479 | #[derive(Debug)]
    |          ----- in this derive macro expansion
...
484 |     key: syn::LitStr,
    |     ^^^^^^^^^^^^^^^^ `LitStr` cannot be formatted using `{:?}` because it doesn't implement `Debug`
    |
    = help: the trait `Debug` is not implemented for `LitStr`
    = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `LitStr` doesn't implement `Debug`
   --> nih_plug_derive\src\params.rs:494:9
    |
489 | #[derive(Debug)]
    |          ----- in this derive macro expansion
...
494 |         group: Option<syn::LitStr>,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ `LitStr` cannot be formatted using `{:?}` because it doesn't implement `Debug`
    |
    = help: the trait `Debug` is not implemented for `LitStr`
    = help: the trait `Debug` is implemented for `Option<T>`
    = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `LitStr` doesn't implement `Debug`
   --> nih_plug_derive\src\params.rs:500:9
    |
489 | #[derive(Debug)]
    |          ----- in this derive macro expansion
...
500 |         id_prefix: syn::LitStr,
    |         ^^^^^^^^^^^^^^^^^^^^^^ `LitStr` cannot be formatted using `{:?}` because it doesn't implement `Debug`
    |
    = help: the trait `Debug` is not implemented for `LitStr`
    = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `LitStr` doesn't implement `Debug`
   --> nih_plug_derive\src\params.rs:508:9
    |
489 | #[derive(Debug)]
    |          ----- in this derive macro expansion
...
508 |         group: Option<syn::LitStr>,
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^ `LitStr` cannot be formatted using `{:?}` because it doesn't implement `Debug`
    |
    = help: the trait `Debug` is not implemented for `LitStr`
    = help: the trait `Debug` is implemented for `Option<T>`
    = note: this error originates in the derive macro `Debug` (in Nightly builds, run with -Z macro-backtrace for more info)

I also tried several gain_gui also got errors. I would appreciate it if someone could tell me how run examples.

VST3 com interface issue / fix

Hello,
I found an issue with the VST3 com interface where any calls to create_instance() appear to return an IComponent, ignoring the IID that was requested. Typically this isn't a problem as the IComponent requested first, but the Steinberg AUWrapper requests an IAudioProcessor first, which wreaks havoc. For example, calls to audioProcessor->setProcessing() actually invoke get_bus_info() with nonsensical parameters. This was pretty difficult to figure out!

The vst3-sys project seems aware of this which I assume is why macros require IComponent to be listed first. Related issue for reference: RustAudio/vst3-sys#14

Here's a quick-and-dirty fix in wrapper/vst3/factory.rs. I'm sure there's a better way to do this, but I'm new to Rust.

    unsafe fn create_instance(
        &self,
        cid: *const vst3_sys::IID,
        iid: *const vst3_sys::IID,
        obj: *mut *mut vst3_sys::c_void,
    ) -> tresult {
        check_null_ptr!(cid, obj);

        if (*cid).data != P::PLATFORM_VST3_CLASS_ID {
            return kNoInterface;
        }

        //Fix: Returns the requested interface:
        let wrapper = Box::into_raw(Wrapper::<P>::new());
        if (*wrapper).query_interface(iid, obj) == vst3_com::sys::NOERROR {
            kResultOk
        } else {
            kResultFalse
        }
    }

Thanks for the wonderful work you're doing on NIH-plug, I'm really loving the combo with egui. ๐Ÿป

Not sure how to use the `timing` of emitted midi events

First, great work ! I could get a vst3+clap plugin going in 5 minutes.

I'm trying to make a simple midi arpeggiator - one note in, multiple notes out. How would I emit a NoteOn midi event, let's say, one quarter noter later than the initial note received ? I'd love a quick sample.

Thanks !

Multiple E0632 errors when building

I just cloned the repository and ran cargo xtask bundle gain --release on both Linux and Windows using the latest stable rust compiler to build the gain plugin. For both platforms, the compiler failed to build and ran into the following errors while compiling nih-plug:

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
    --> src/wrapper/clap/wrapper.rs:1547:39
     |
1547 |             state::serialize_object::<P>(
     |                                       ^ explicit generic argument not allowed
     |
     = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
    --> src/wrapper/clap/wrapper.rs:3073:50
     |
3073 |         let serialized = state::serialize_json::<P>(
     |                                                  ^ explicit generic argument not allowed
     |
     = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
   --> src/wrapper/state.rs:139:43
    |
139 |     let plugin_state = serialize_object::<P>(plugin_params, params_iter);
    |                                           ^ explicit generic argument not allowed
    |
    = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
   --> src/wrapper/vst3/inner.rs:417:39
    |
417 |             state::serialize_object::<P>(
    |                                       ^ explicit generic argument not allowed
    |
    = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
   --> src/wrapper/vst3/wrapper.rs:544:50
    |
544 |         let serialized = state::serialize_json::<P>(
    |                                                  ^ explicit generic argument not allowed
    |
    = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

For more information about this error, try `rustc --explain E0632`.
error: could not compile `nih_plug` due to 5 previous errors
Error: Could not build gain

Standalone application has very crackly audio when set to non-44.1khz sample rate.

I am on an M1 Apple Macbook Pro using CoreAudio running macOS Monterey 12.6. When running a plugin via the standalone binary, crackly audio occurs unless the sample rate is set to 44100. Note that this doesn't seem to be an issue with the plugin, but rather with how the standalone binary uses the CoreAudio backend.

To reproduce, compile the poly_mod_synth example as a standalone binary. Note that I had to modify the plugin's existing Cargo.toml and add a main.rs file since it was not already configured to make a standalone binary (see below for exact changes I made)

[package]
name = "poly_mod_synth"
version = "0.1.0"
edition = "2021"
authors = ["Robbert van der Helm <[email protected]>"]
license = "ISC"

[lib]
crate-type = ["cdylib", "lib"] # adding "lib" to the crate type

[dependencies]
nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } # adding "standalone" feature

rand = "0.8.5"
rand_pcg = "0.3.1"
// poly_mod_synth/main.rs
use poly_mod_synth::PolyModSynth;

fn main() {
    nih_plug::nih_export_standalone::<PolyModSynth>();
}

// additionally, mark PolyModSynth as public in lib.rs

When running the plugin using ./poly_mod_synth --midi-input "USB Axiom 49 Port 1", there will be crackly audio. When running with ./poly_mod_synth --midi-input "USB Axiom 49 Port 1" --sample-rate 44100, no crackly audio is heard.

Additionally, when I run the synth as a vst3 plugin and play it in vPlayer 3, I do not hear crackly audio regardless of the sample rate.

I have not tested the plugin on the JACK audio backend (mostly because I do not have that set up).

Passing back a string from UI to the plugin

Hey!

First of all, I'm pretty new to Rust and DSP, in general, so bear with me :P

I love the fact that you made this framework, and I'm running and modifying some of your examples to get the hang of things right now.

What I'm really interested in doing, is scaffolding some basic UI that I can get a string out of (and possibly more complex data), so that I can then continue developing on some kind of creative coding plugin. I.e. let the user "program" some code in the UI, and then compile or interpret it on the fly while processing buffers.

However, because of Rust ownership and thread stuff, I can't "get a string out of the UI", haha, and I'm not sure how to even start to tackle this problem, but I was hoping maybe you could provide me a pointer or some help? :)

My silly attempt, you can see the obvious borrow-checker problem on line 194:


I also wonder... In my case, I don't necessarily need the string to be an actual parameter of the VST, but, I do believe VST(3?) allows string parameters, it's just that they can't be automated then. So instead of possibly hacking a way to send over data from the UI to the plugin, it could also be done via your existing system of params / ParamSetter, if we could add a StringParam. Is such a thing possible? It would not need to be smoothed/automated etc. of course. Or is such a thing impossible for Rust-y thread safety kinds of reasons?

Parameter order: Nested parameters are always behind non-nested parameters

How to reproduce

Have a plugin where the parameter struct contains "non-nested" parameters aft`r nested ones:

#[derive(Params)]
struct PluginParams {
    #[id = "one"]
    pub one: FloatParam,

    #[nested(group = "inner")]
    pub two_and_three: InnerParams,

    #[id = "four"]
    pub four: FloatParam,
}

#[derive(Params)]
struct InnerParams {
    #[id = "two"]
    pub two: FloatParam,

    #[id = "three"]
    pub three: FloatParam,
}

See also: minimal example plugin

Expected behavior

When opening the plugin in the host, the parameters are displayed in this order:

  1. One
  2. Two
  3. Three
  4. Four

Actual Behavior

The nested parameters from InnerParams are displayed after all non-nested parameters:

  1. One
  2. Four
  3. Two
  4. Three

See, for example, Bitwig:
Screenshot_2022-11-07_22-18-03

More percise Buffer::len documentation.

Hi,

I just stumbeled uppon a minor issue in the documentation of Buffer::len. It says:

Returns the number of samples in this buffer.

While trying to implement my own time tracking I thought something like this should get me the "time the buffer occupies"

let buffer_time_len = (buffer.len() / buffer.channel()) as f32 / transport.sample_rate;

The reasoning being: If buffer.len() is the number of samples in the buffer, I got len/num_channels samples per channel.

However, buffer.len() actually returns number of samples per channel. So it might be good to include that information in the docs. Something like this:

Returns the number of samples per channel in this buffer.

Cheers!

Question: How does the Buffer type handle mono in/stereo out scenarios

I have a general question regarding the Buffer type. As it the Buffer type abstracts both the input and the output frame I find it difficult to understand how it is supposed to handle scenarios in which one has a mono input and wants to create a stereo output signal or in extension how to implement a plugin that can handle both mono and stereo inputs. In my concrete case I am planning to write a stereo delay plugin that can handle both mono and stereo inputs. I think for the input case things a relatively clear as I could write something in this direction:

use iter_tools::IterTools; // for tuples iterator

for channel_samples in buffer.iter_samples() {
   for (sl, sr) in channel_samples.iter_mut().tuples() {
      let (ol, or) = self.process_sample(sl, sr);
      (*sl, *sr)  = (ol, or);
   }
}

How would things work though if I a have mono input and a stereo output?
In that case at least in theory can't just iterate over the input samples and then write it back into the buffer for two reasons:

  1. In theory input and output buffers would have a different size. If I am not completely missing something the output buffer would need to have a twice the size of the input because of left and right samples.
  2. If I write in one iteration the two output samples I would already overwrite the next input sample

So It could be that I am completely mistaken about the statements above in which case I would appreciate a small example on how to handle the scenario correctly. On another note I would also be thankful for feedback about the example I have given above. I am really not sure if it the right approach to handle samples as in theory there should only be a single pair of values in the channel_samples it feels quite clunky to use the iterator to access sample values.

VST3 not recognized in Ableton on Mac M1

Hi, thank you for making free plugins, I'd like to try them. I found a link from a youtube video and tried to install on my MacBook Pro M1 with Ableton Live 11 and Ableton Live 10 and neither one showed the plugins in Ableton's browser. I followed Ableton's help instructions and none of them worked, do you know what might be wrong? Thanks

`nih_plug_vizia` custom cursors don't seem to work

I'm having some issues with setting cursor icons through Vizia's WindowEvent::SetCursor(). It does work in regular Vizia, but not under nih_plug_vizia. Copying the code from this Vizia example into a new plugin editor and running it, everything displays correctly and without errors. However, none of the labels change the cursor when hovered. I'm on Windows 11, if that helps.

Behavior in the original example:

Vizia standalone screenshot

Behavior when used within nih_plug_vizia:

nih-plug-vizia screenshot

Crisp, Diopser and Spectral Compressor Win7 GUI problems

Hi!

I'm having a problem running these plugins, they give these errors while being scanned :
Crisp - SetProcessDpiAwarenessContext missing in user32.dll
Diopser - GetDpiForWindow missing in user32.dll
Spectral Compressor - GetDpiForWindow missing in user32.dll
Both VST3 and CLAP variants.

If impossible to add Win7 support, would it be possible to have GUI-less variants of them?

nih-plugs-15c859a-windows - Windows 7 64bit - REAPER 64bit

Crisp: need more precision in low amount value range

I've started using Crisp to bring back some high frequency content especially to down-pitched sources.

However I've found I am using Amount values between 1% and 4% and I wish I could go lower yet.
image

As far as I'm concerned, this control could go from 0.1% to 10% rather than from 1% to 100%.

I wonder if anyone else has the same issue?

assert_process_allocs does not affect standalone.

Thanks for creating assert_process_allocs, it's exactly the kind of thing I need to prevent me from shooting myself in the foot! ๐Ÿ˜€

It seems it doesn't apply to the standalone version though.
Any chance of adding it there as well?

Different SysEx messages in the same block all come through as duplicates (VST3)

Hello NIH-ers!

I've run into a problem case whenever more than one SysEx event is processed during the same block using VST3. Basically, the last event appears duplicated as many times as needed instead of each one having its own data.

In contrast, the CLAP version handles this just fine.

Here's some sample JSON debugging output for comparison, where [xx] is used for unprintable characters...

correct (CLAP):

[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":243,"channel":0,"note":59}}[f7] (98 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":289,"channel":0,"note":8}}[f7] (97 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":312,"channel":0,"note":104}}[f7] (99 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":496,"channel":0,"note":57}}[f7] (98 bytes)

incorrect (VST3):

[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":243,"channel":0,"note":57}}[f7] (98 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":289,"channel":0,"note":57}} (97 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":312,"channel":0,"note":57}}[f7][00] (99 bytes)
[f0]{"SysEx":{"bar":0,"beats":2.0,"samples":44100,"tempo":120.0,"timing":496,"channel":0,"note":57}}[f7] (98 bytes)

Note the truncated trailing [f7] (line 2) and the extra [00] padding (line 3) where the buffer lengths don't match the repeated event. The reported lengths themselves are accurate, however.

Thanks in advance for any thoughts on this ๐Ÿš€

Iain

Smoothing code is quite expensive

Looking through the code, it appears that this if statement is executed every single frame for every single parameter:

https://github.com/robbert-vdh/nih-plug/blob/master/src/param/smoothing.rs#L198

It would be better if somehow this if statement is only executed once per parameter per process cycle.

I still vouch for baseplug's method which is to fill a buffer with smoothed values for each parameter beforehand. I can help you with it if you want.

https://github.com/wrl/baseplug/blob/trunk/src/smooth.rs#L47

Updating to Latest Vizia

I'm interested in bringing nih-plug's Vizia dependency up to date.
I'm not sure how large of an undertaking this actually is, as the fork does not seem to have large changes.

I've gotten things compiling with the latest Vizia but I'm stumped here & looking for leads. The GUI is unresponsive in standalone mode as the majority of the CPU time is spent in this stack trace re-styling the interface.

Any thoughts on what I might be missing here?

Let me know if there is a better way for me to get input on this issue. I would love to be able to use the latest Vizia as the patched fork is quite far behind main.

20432	crisp (60668)	
17306	Main Thread  0x5f8225	
17306	start	
17306	main	
17306	std::rt::lang_start::h529363aee13a6c62	
17306	std::rt::lang_start_internal::h434fe84e11a92429	
17306	std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h11ab781115c04bcf	
17306	std::sys_common::backtrace::__rust_begin_short_backtrace::h4a882756e564a802	
17306	core::ops::function::FnOnce::call_once::h2586012a7f0b7b1b	
17306	crisp::main::h90babba28b85b71c	
17306	nih_plug::wrapper::standalone::nih_export_standalone::h6392a52de9779039	
17306	nih_plug::wrapper::standalone::nih_export_standalone_with_args::h79bb98ca4ce11711	
17306	core::result::Result$LT$T$C$E$GT$::or_else::h55aafd6c10fd263b	
17306	nih_plug::wrapper::standalone::nih_export_standalone_with_args::_$u7b$$u7b$closure$u7d$$u7d$::h16f8cbb54676226d	
17306	nih_plug::wrapper::standalone::run_wrapper::hd2d70150e7a8e42b	
17306	nih_plug::wrapper::standalone::wrapper::Wrapper$LT$P$C$B$GT$::run::h2699ffd24d47e27d	
17306	baseview::window::Window::open_blocking::h97a50e827f25e1e8	
17306	baseview::macos::window::Window::open_blocking::h9de9916bf8a48644	
17306	_$LT$$BP$mut$u20$objc..runtime..Object$u20$as$u20$cocoa..appkit..NSApplication$GT$::run::hcf2921796ce7adf6	
17306	objc::message::platform::send_unverified::h188f516897c74c44	
17306	_$LT$$LP$$RP$$u20$as$u20$objc..message..MessageArguments$GT$::invoke::hdc27c3db03cbd3c0	
17306	-[NSApplication run]	
17306	-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:]	
17306	_DPSNextEvent	
17306	_BlockUntilNextEventMatchingListInModeWithFilter	
17306	ReceiveNextEventCommon	
17306	RunCurrentEventLoopInMode	
17306	CFRunLoopRunSpecific	
17306	__CFRunLoopRun	
17229	__CFRunLoopDoTimers	
17227	__CFRunLoopDoTimer	
17222	__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__	
17222	baseview::macos::window::WindowState::setup_timer::timer_callback::h9e4ee84e7fbf4037	
17222	baseview::macos::window::WindowState::trigger_frame::h3dd68f6ccda1a5d0	
17221	_$LT$vizia_baseview..window..ViziaWindow$u20$as$u20$baseview..window..WindowHandler$GT$::on_frame::h1a1fc8d766fd72d1	
10337	vizia_baseview::application::ApplicationRunner::on_frame_update::h65619d0115cd22fb	
10294	vizia_core::context::backend::BackendContext::process_style_updates::haf29c635292cebcf	
10182	vizia_core::systems::style::style_system::hee4b9919174c6c43	
9754	vizia_core::systems::style::compute_matched_rules::hd8b4f574bed92254	
7281	selectors::matching::matches_complex_selector_internal::h3d8404151ab26811	
4885	selectors::matching::matches_compound_selector::h13237745c6db8ca5	
2164	selectors::matching::matches_local_name::h8a01d9d0d78049ee	
2117	_$LT$vizia_core..systems..style..Node$u20$as$u20$selectors..tree..Element$GT$::has_local_name::h57341f2f2946c5f4	
1725	std::collections::hash::map::HashMap$LT$K$C$V$C$S$GT$::get::h1a1edbd6698661a2	
1680	hashbrown::map::HashMap$LT$K$C$V$C$S$C$A$GT$::get_inner::h22b60ea0b1e1cdc1	
924	hashbrown::raw::RawTable$LT$T$C$A$GT$::get::h44d87ba416aec045	
885	hashbrown::raw::RawTable$LT$T$C$A$GT$::find::h486693bb02b42787	
427	hashbrown::raw::RawTable$LT$T$C$A$GT$::find::h486693bb02b42787	
275	hashbrown::raw::RawTableInner$LT$A$GT$::find_inner::h3a3fe0b13719c4b6	

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.