Audio parameters not preserved when plugin is updated

Howdy, my first question here so please be kind to the noob :slight_smile:

I’m writing a plugin that displays MIDI notes on the grand staff, and I have some parameters that I want preserved when updating the plugin.
This works fine for an AudioParameterChoice, but not for envelopes or even just the last values on AudioParameterBool and AudioParameterInt params.
Note that this is on Reaper, so it’s maybe only a problem there. The Reaper error message is: “parameter envelope on non-automatable parameter”, even though one can add envelope and automate those parameters within the same plugin version.
Incidentally, persisting ints and bools didn’t work on the JUCE standalone app until I manually set the appropriate values on the AudioProcessorValueTreeState.
Here’s the beginning of the constructor, where the audio parameters are constructed:

    GrandStaffMIDIVisualizerProcessor()
        : AudioProcessor (getBusesLayout()),
        parameters(*this, nullptr, juce::Identifier("GrandStaffMIDIVisualizerParameters"),
            {
                std::make_unique<juce::AudioParameterChoice>("key",
                    "Key",
                    StringArray {
                        "Sharp", "Flat",
                        "C", "C#", "Db", "D", "Eb", "E", "F", "F#", "Gb", "G", "Ab", "A", "Bb", "B"
                    },
                    0
                ),
                std::make_unique<juce::AudioParameterBool>("holdNotes",                       // parameterID
                                                             "Hold Notes",                    // parameter name
                                                             false),                          // default value
                std::make_unique<juce::AudioParameterInt>(OCTAVES_ID,                          // parameterID
                                                            "Octaves",                        // parameter name
                                                            -3,                               // min value
                                                            3,                                // max value
                                                            0),                               // default value
                std::make_unique<juce::AudioParameterInt>("chordPlacement",                  // parameterID
                                                             "Chords Placement",              // parameter name
                                                             0,                               // min value
                                                             2,                               // max value
                                                             1),                              // default value
                std::make_unique<juce::AudioParameterBool>("chordFontBold",                   // parameterID
                                                             "Display chords with bold font", // parameter name
                                                             false),                          // default value

            })

All of the source code is here: https://github.com/brynjar-reynisson/GrandStaffMIDIVisualizer/tree/main

I have a hunch that Reaper expects there to be something that listens to host notifications for these params, but don’t know what to look for.
Hope that someone can help, seems like I can’t be the only one to have encountered this.

TIA!

I have just had a quick look at your code. It seems that the plugin does not tell the APVTS to store the change when you interact with the GUI.

auto *para = parametersRef.getParameter(paraID);
para->beginChangeGesture();
para->setValueNotifyingHost(newValue);
para->endChangeGesture();

BTW, if you do want to automate those parameters, you may have to do the following things:

  • handle parameter changes in void parameterChanged() (of a juce::AudioProcessorValueTreeState::Listener)
  • use attachments to link GUI components with parameters

Thanks
I’ve moved all param handling to a new class named VSTParameters.
The param values were already updated (now in VSTParameters::pluginModelChangedFromUI). I tried the setValueNotifyingHost block on one param, but that made it stuck in its min value - I’m probably doing something wrong there.
Automation works within a single version of the plugin, I can draw param envelopes and my plugin receives updates from the host (previously in processBlock, but now in parameterChanged which is much cleaner of course, thanks again). Closing Reaper and opening it again is fine within the same version, it remembers value choices and automation.

The problem is that it only recognizes the ‘key’ parameter after updating the plugin (and it has problem with displaying correctly the first time after update, second time is no problem).
Reaper displays this window on un-recognized parameters after update:

Interestingly, I changed one of the parameters from int to choice, but that doesn’t make it any more memorable in the eyes of Reaper after plugin update, it only remembers the ‘key’ param.

The param values were already updated (now in VSTParameters::pluginModelChangedFromUI). I tried the setValueNotifyingHost block on one param, but that made it stuck in its min value - I’m probably doing something wrong there.

Yes, you change the parameters in your model. However, you also have to notify the host about the change (if you change it from GUI) and save the updated APVTS in getStateInformation(). You can do it via setValueNotifyingHost(newValue) (newValue is a normalised float between 0.0~1.0) or via an attachment.

BTW, it seems that you should consider thread safety issues more carefully. If you want to update GUI based on MIDI from the audio thread or parameters from parameterChanged(), you have to find a thread-safe way to do that (e.g., via atomics, lock-free FIFO, etc).

Thanks again

I tried setting the vst parameters like this:
parameters.getParameter(OCTAVES)->setValueNotifyingHost(*octavesParameter);
in
void VSTParameters::setStateInformation(const void* data, int size)
as shown in e.g. the Gain Plugin Demo.

It doesn’t help Reaper to recognize the parameter after plugin update:
image

I think you’re right that I shouldn’t call for UI changes directly as a result of parameter changes, I’ll make that a command post. Midi changes are already calling for repaint as a command post, so I think that part is ok.

I think you should call setValueNotifyingHost when your model is changed from the UI. But I am not sure whether it is the cause of this Reaper warning message. Maybe you could try recording automation when you change from UI and see whether automation is correctly recorded.

I think you’re right that I shouldn’t call for UI changes directly as a result of parameter changes

It seems that you write some variables on the audio thread and read the same variables on the GUI thread without any protection.

pluginModel.midiNotes[metadata.getMessage().getNoteNumber()] = onOff;

I would guess there is a thread safety issue here.

Thanks, I finally figured out why Reaper wasn’t preserving parameters between updates. It’s probably a rookie mistake on my behalf.
The problem was that when experimenting earlier, I had installed a version of the plugin with a different name, and I probably just renamed some stuff (including the project name), but didn’t create a new project. A generated file called moduleinfo.json contains something called CID, and it seems like Reaper got confused because it was the same in the old differently named version. Removing the old plugin from plugin paths resolved the problem.
Incidentally, this was also the reason why I couldn’t install the plugin in C:\Program Files\Common\VST3. Seems like that’s more sensitive for Reaper than C:\Program Files\VstPlugins. I was actually trying to fix that problem when it occurred to me that this could be the reason that Reaper didn’t recognize VST parameters after update :slight_smile:

There is a potential thread issue in the case you mentioned. However, I don’t think it’s too serious, because the GUI thread just reads these bools to determine if it should display that note or not. There’s no problematic race or cascading issues from it - at most it will display notes that are no longer being played, or not play notes that recently were played (both issues resolve themselves on the next buffer update :slight_smile: )

Thanks again!

I am glad that you solve this! Yes, DAW will get confused if it see two plugins with the same identity (depends on the actual format).

Regarding the thread safety issue, data-race is undefined behaviour:

Because correct C++ programs are free of undefined behavior, compilers may produce unexpected results when a program that actually has UB is compiled with optimization enabled.

If those are plain bools, the fix is very simple: use std::atomic<bool> to replace them.