7
44
Fork
You've already forked nice-plug
22

Adding support for parameter updates from within the plugin itself #15

Open
sprucecloud wants to merge 13 commits from sprucecloud/nih-plug:main into main
pull from: sprucecloud/nih-plug:main
merge into: RustAudio:main
RustAudio:main
RustAudio:resizing
RustAudio:dev
RustAudio:standalone_fixes
RustAudio:FoobarIT/main
RustAudio:fixes
RustAudio:egui_3rd_party
RustAudio:vizia_baseview_update
RustAudio:egui_32
RustAudio:softbuffer
RustAudio:byo_gui_examples
RustAudio:raw_graphics_examples
First-time contributor
Copy link

Adding support for parameter updates from within the plugin itself. This is useful when plugin reacts to incoming MIDI, updates its parameter values and needs to propagate the changes to the DAW.

Also, a re-export of egui so that the plugin does not have to depend on egui crate itself.

A compilation fix for the removed feature rustanalyzer_opengl_workaround.

Unrelated side-note: There is https://github.com/tomoyanonymous/baseview/pull/1 which fixes exiting when running standalone on macOS.

Adding support for parameter updates from within the plugin itself. This is useful when plugin reacts to incoming MIDI, updates its parameter values and needs to propagate the changes to the DAW. Also, a re-export of egui so that the plugin does not have to depend on egui crate itself. A compilation fix for the removed feature rustanalyzer_opengl_workaround. Unrelated side-note: There is https://github.com/tomoyanonymous/baseview/pull/1 which fixes exiting when running standalone on macOS.

Is it common/recommended for plugins to change their parameter values in response to MIDI?

Is it common/recommended for plugins to change their parameter values in response to MIDI?
Author
First-time contributor
Copy link

For specific types of plugins, it makes sense.

Most plugins are audio effects and they do not need that. They present the parameters to the DAW and they are controlled from their UI. For these, the mapping of MIDI messages (MIDi controller controls) to plugin's parameters is done by the DAW.

Then there are plugins that do need to receive MIDI messages, process them themselves and update their internal state and parameters based on that:

  • MIDI effects - like e.g. https://www.frozenplain.com/product/obelisk - a plugin that reads its input MIDI notes and based on the combination, it sets its internal "chord" and then tranposes notes to that
  • plugins for device control - plugins that allow controlling a specific MIDI device like a synthesizer or audio effect:
    • exposing hidden parameters - either not reachable at all on the device or available on the device with menu diving, impairing the immediacy of using these parameters for expression of the sound.
    • presenting user with named parameters instead of the need to read the manual and control message assignment
    • reading and writing parameters exposed by "non-standard" means (or less convenient and requiring much more work to change them live): system exclusive messages, RPN/NRPN messages, message combinations.
    • reading data from the MIDI dumps of the device
    • examples of plugins and devices:
      • librarians and editors for instruments like e.g. https://squest.com/Products/MidiQuest13/Instruments/RolandFA-08/index.html
      • Moog Minitaur: https://www.soundonsound.com/reviews/moog-minitaur To cite from the article: "while you can use it successfully in isolation, there are no fewer than 20 additional voicing parameters (that I refer to as 'hidden' and Moog calls 'under the hood') that become accessible when you use it with its dedicated editor software (see below) or another source of MIDI CCs."
      • Korg M50 (or any other workstation-type instrument) https://www.soundonsound.com/reviews/korg-m50 - a load of parameters, most if not all of them settable via SysEx messages. Has its own Korg M50 Editor (32-bit, legacy). It is the same case with M3, Krome, Kronos
      • Korg Kronos has its own editor plugin that is 32-bit only https://shop.korg.com/kronos/EditorSoftware.aspx
      • Roland's synthesizers and workstations - Integra, Fantom EX, Fantom 08, FA-08 - a lot parameters available through SysEx messages. Some have their own editor. For older instruments and devices, there are some device scripts for Ctrlr.
      • Yamaha MOTIF, MOXF, Montage, Montage M, MODX, MODX+, MODX M - again, a lot of parameters available through SysEx messages. Some have and editor plugin (the M series even have an instrument plugin that sounds like them and controls them), but the others have only older 32-bit plugins without scaling that expose a lot of parameters, but not all of them.

Having access to these parameters and being able to change them easily from the plugin would significantly improve the expressiveness of the instruments, allow for expanding their features and for some, extend their usefulness.

NOTE: All the brands and names are trademarks of the companies that own them.

For specific types of plugins, it makes sense. Most plugins are *audio effects* and they do not need that. They present the parameters to the DAW and they are controlled from their UI. For these, the mapping of MIDI messages (MIDi controller controls) to plugin's parameters is done by the DAW. Then there are plugins that do need to receive MIDI messages, process them themselves and update their internal state and parameters based on that: - *MIDI effects* - like e.g. https://www.frozenplain.com/product/obelisk - a plugin that reads its input MIDI notes and based on the combination, it sets its internal "chord" and then tranposes notes to that - *plugins for device control* - plugins that allow controlling a specific MIDI device like a synthesizer or audio effect: - exposing hidden parameters - either not reachable at all on the device or available on the device with menu diving, impairing the immediacy of using these parameters for expression of the sound. - presenting user with named parameters instead of the need to read the manual and control message assignment - reading and writing parameters exposed by "non-standard" means (or less convenient and requiring much more work to change them live): system exclusive messages, RPN/NRPN messages, message combinations. - reading data from the MIDI dumps of the device - examples of plugins and devices: - librarians and editors for instruments like e.g. https://squest.com/Products/MidiQuest13/Instruments/RolandFA-08/index.html - Moog Minitaur: https://www.soundonsound.com/reviews/moog-minitaur To cite from the article: "while you can use it successfully in isolation, there are no fewer than 20 additional voicing parameters (that I refer to as 'hidden' and Moog calls 'under the hood') that become accessible when you use it with its dedicated editor software (see below) or another source of MIDI CCs." - Korg M50 (or any other workstation-type instrument) https://www.soundonsound.com/reviews/korg-m50 - a load of parameters, most if not all of them settable via SysEx messages. Has its own Korg M50 Editor (32-bit, legacy). It is the same case with M3, Krome, Kronos - Korg Kronos has its own editor plugin that is 32-bit only https://shop.korg.com/kronos/EditorSoftware.aspx - Roland's synthesizers and workstations - Integra, Fantom EX, Fantom 08, FA-08 - a lot parameters available through SysEx messages. Some have their own editor. For older instruments and devices, there are some device scripts for Ctrlr. - Yamaha MOTIF, MOXF, Montage, Montage M, MODX, MODX+, MODX M - again, a lot of parameters available through SysEx messages. Some have and editor plugin (the M series even have an instrument plugin that sounds like them and controls them), but the others have only older 32-bit plugins without scaling that expose a lot of parameters, but not all of them. Having access to these parameters and being able to change them easily from the plugin would significantly improve the expressiveness of the instruments, allow for expanding their features and for some, extend their usefulness. NOTE: All the brands and names are trademarks of the companies that own them.
@ -128,0 +130,4 @@
matchself.wrapper.param_ptr_to_hash.get(&param){
Some(hash)=>{
letstep_count=unsafe{param.step_count().unwrap_or(1)}asf64;
letclap_plain_value=normalizedasf64*step_count;
Owner
Copy link

Are you sure the correct way is to multiply the normalized value by the step count?

Are you sure the correct way is to multiply the normalized value by the step count?
Author
First-time contributor
Copy link

It should be correct.

For VST3 format, the parameter value presented to host is normalized (double - 0 to 1).

For CLAP format, the parameter value presented to host can be double from "min" to "max" - any fitting range. nih-plug for CLAP:

Practically, I tested this in a DAW with the same input data with both plugin formats and the parameter automation curves were the same.

It should be correct. For VST3 format, the parameter value presented to host is normalized (double - 0 to 1). For CLAP format, the parameter value presented to host can be double from "min" to "max" - any fitting range. nih-plug for CLAP: - for continuous parameters, internally normallizes the value to interval from 0 to 1 in order to apply value skewing (configurable) and presents the normalized value to host. According to https://codeberg.org/BillyDM/nih-plug/src/commit/10b716a394b178a9aa7263ea2775ec2e319a996a/crates/nih_plug/src/wrapper/clap/wrapper.rs#L3078 - for stepped parameters, it presents parameter values to host as float values from 0 to "count of steps" https://codeberg.org/BillyDM/nih-plug/src/commit/10b716a394b178a9aa7263ea2775ec2e319a996a/crates/nih_plug/src/wrapper/clap/wrapper.rs#L3085 Practically, I tested this in a DAW with the same input data with both plugin formats and the parameter automation curves were the same.
@ -24,6 +25,14 @@ use crate::wrapper::state::{self, PluginState};
usecrate::wrapper::util::buffer_management::BufferManager;
usecrate::wrapper::util::{hash_param_id,process_wrapper};
constOUTPUT_PARAM_QUEUE_CAPACITY: usize =2048;
Owner
Copy link

Is a capacity of 2048 really needed? If this is only used for occasional parameters, then this value could probably be smaller.

Is a capacity of 2048 really needed? If this is only used for occasional parameters, then this value could probably be smaller.
Author
First-time contributor
Copy link

The queue is for parameter changes. Theoretically, if many parameters change at once (through multiple automation curves in the DAW), large queue might be needed.

However, I should probably test it on a use case with something like 12 automation lanes (that is quite a lot) and a lot of value points. Will try.

The queue is for parameter changes. Theoretically, if many parameters change at once (through multiple automation curves in the DAW), large queue might be needed. However, I should probably test it on a use case with something like 12 automation lanes (that is quite a lot) and a lot of value points. Will try.
Author
First-time contributor
Copy link

Actually, lowered the number to 512

Actually, lowered the number to 512
@ -1655,6 +1655,32 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
}
}
ifletSome(param_changes)=data.output_param_changes.upgrade(){
Owner
Copy link

Are you sure this is the correct way to notify the host of parameter updates? I'm not sure I'm comfortable accepting a PR with this much unsafe unless you know exactly what you are doing.

Are you sure this is the correct way to notify the host of parameter updates? I'm not sure I'm comfortable accepting a PR with this much unsafe unless you know exactly what you are doing.
Owner
Copy link

Keep in mind I am only just maintaining nih-plug for the community. I don't understand the exact innerworkings of all the unsafe APIs myself.

Keep in mind I am only just maintaining nih-plug for the community. I don't understand the exact innerworkings of all the unsafe APIs myself.
Author
First-time contributor
Copy link

I am pretty sure.

The function flow for VST3 for update of the host parameter value from within the plugin is

data.outputParameterChanges → IParameterChanges
 ::addParameterData(id, index) → IParamValueQueue
 ::addPoint(sampleOffset, normalizedValue, index)

According to https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Parameters+Automation/Index.html :

When the controller transmits a parameter change to the host, the host synchronizes the processor by passing the new values as IParameterChanges to the process call. The processor can transmit outgoing parameter changes to the host as well via Vst::ProcessData::outputParameterChanges

The unsafe's are unfortunately necessary. The functions IParameterChanges.add_parameter_data() and IParamValueQueue.add_point() are defined as unsafe themselves in crate vst3-sys, since they invoke native functions on VST host through FFI.
Source: https://github.com/RustAudio/vst3-sys/blob/master/src/vst/ivstparameterchanges.rs

I am pretty sure. The function flow for VST3 for update of the host parameter value from within the plugin is ``` data.outputParameterChanges → IParameterChanges ::addParameterData(id, index) → IParamValueQueue ::addPoint(sampleOffset, normalizedValue, index) ``` According to https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/Parameters+Automation/Index.html : > When the controller transmits a parameter change to the host, the host synchronizes the processor by passing the new values as IParameterChanges to the process call. The processor can transmit outgoing parameter changes to the host as well via Vst::ProcessData::outputParameterChanges The unsafe's are unfortunately necessary. The functions `IParameterChanges.add_parameter_data()` and `IParamValueQueue.add_point()` are defined as unsafe themselves in crate `vst3-sys`, since they invoke native functions on VST host through FFI. Source: https://github.com/RustAudio/vst3-sys/blob/master/src/vst/ivstparameterchanges.rs
@ -18,6 +18,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
compile_error!("There's currently no software rendering support for egui");
/// Re-export for convenience.
pubuseegui;
Owner
Copy link

Actually, I intentionally didn't re-export egui because the crate imports egui with all of the features disabled. So re-exporting like this will not work. Please remove it.

Actually, I intentionally didn't re-export egui because the crate imports egui with all of the features disabled. So re-exporting like this will not work. Please remove it.
Author
First-time contributor
Copy link

Gotcha, removed it.

Gotcha, removed it.

I don't appreciate the AI generated response, but I guess it makes sense enough? I want to get a second opinion on it though.

I don't appreciate the AI generated response, but I guess it makes sense enough? I want to get a second opinion on it though.
Author
First-time contributor
Copy link

Commented on the comments and addressed 2 problems.

The longer answer is not AI generated. It was fully written by me. But I agree that I talk a bit like Claude. 😅 The note about trademarks was intended more as of a security measure.

I am writing a plugin for controlling a synthesizer and sharing back the code to a project that is actively maintained. Great work, by the way.

Commented on the comments and addressed 2 problems. The longer answer is not AI generated. It was fully written by me. But I agree that I talk a bit like Claude. 😅 The note about trademarks was intended more as of a security measure. I am writing a plugin for controlling a synthesizer and sharing back the code to a project that is actively maintained. Great work, by the way.

Sorry, kind of did a big refactor, so there are probably some merge conflicts.

Also, others in the Rust Audio Discord server weren't too keen on allowing parameters to be updated from inside the plugin itself. Maybe you could discuss your specific use case to them and why you need this? It is possible that what you are trying to achieve can be accomplished without the use of parameters.

Sorry, kind of did a big refactor, so there are probably some merge conflicts. Also, others in the Rust Audio Discord server weren't too keen on allowing parameters to be updated from inside the plugin itself. Maybe you could discuss your specific use case to them and why you need this? It is possible that what you are trying to achieve can be accomplished without the use of parameters.
This pull request has changes conflicting with the target branch.
  • baseview-adapters/egui-baseview/src/window.rs
  • baseview-adapters/iced-baseview/Cargo.toml
  • baseview-adapters/iced-baseview/src/shell/mod.rs
  • baseview-adapters/iced-baseview/src/shell/settings.rs
  • baseview-adapters/iced-baseview/src/shell/window.rs
  • crates/nice-plug-egui/src/lib.rs
  • crates/nice-plug/src/wrapper/vst3/inner.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u main:sprucecloud-main
git switch sprucecloud-main

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff sprucecloud-main
git switch sprucecloud-main
git rebase main
git switch main
git merge --ff-only sprucecloud-main
git switch sprucecloud-main
git rebase main
git switch main
git merge --no-ff sprucecloud-main
git switch main
git merge --squash sprucecloud-main
git switch main
git merge --ff-only sprucecloud-main
git switch main
git merge sprucecloud-main
git push origin main
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
RustAudio/nice-plug!15
Reference in a new issue
RustAudio/nice-plug
No description provided.
Delete branch "sprucecloud/nih-plug:main"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?