Possible Tracktion Waveform 11.5 sandbox bug with non-automatable audio parameters and VST3

I’ve got a reproducible crash narrowed down to the use of non-automatable audio parameters in a VST3 inside of Tracktion Waveform’s sandbox from the GUI thread; and there is no issue with sandbox disabled and the same code. I ran into this with a plugin I am developing and narrowed it to a test case/simplified project that seems to faithfully reproduce the issue for me. I may also just be missing something in the implementation.

Here are a few key observations:

  • Automatable parameters work fine in all scenarios when manipulated from the GUI - sandbox or not
  • Not using the sandbox in Waveform produces stable use with no crashes in all scenarios tested in the example code, somehow making this a sandbox-specific crash only
  • Subclassed audio parameters who only override isAutomatable() and return false (non-automable audio parameters) crash in the sandbox when manipulated from the GUI, especially on first load
  • After first load/first crash, often restarting the plugin sandbox puts the plugin in a state where the GUI works without crashses for the duration of the session, but occasionally it does not

System Info
I’m using JUCE 6.0.7 on Win10 in VS 2019 and testing Tracktion Waveform 11.5.2, testing only VST3 as a synth/instrument plugin, C++14.

This was tested with the legacy and new experimental audio engine, but only with the Hybrid processing algorithm.

The Basic Highlights

  • The tests lets you click on the background of the editor which will update a slider value randomly by updating an AudioProcessorValueTreeState parameter that the slider is attached/listening to.
  • The test plugin that can reproduce the crash uses an AudioProcessorValueTreeState to store a single test parameter named ‘testValue’
  • A reference to the ValueTreeState is passed to the editor in its constructor.
  • The editor has a single Slider and uses an AudioProcessorValueTreeState::SliderAttachment to attach itself to the ‘testValue’ parameter.
  • The editor also listens to mouse events on itself for clicks and when it receives one, generates a random float and invokes a method on the plugin that calls testValue->setValueAndNotiftyHost(value); which in turn updates the Slider position/value.

Here are a few key takeaways of what it looks like:

Test Plugin Constructor

NonAutomatableSandboxTestProcessor::NonAutomatableSandboxTestProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                     #endif
                       ),
    valueTreeState(*this, nullptr, juce::Identifier("Tester"),
    {
        std::make_unique<NonAutomatableAudioParameterFloat>("testValue", "Test Value", juce::NormalisableRange<float>{0.f, 1.f, 0.01f}, 0, "?"),
    })
#endif
{
    testValueParam = valueTreeState.getParameter("testValue");
}

When initialising ‘valueTreeState’ above a special class named ‘NonAutomatableAudioParameterFloat’ is used, it looks like this:

/* Uncomment 'ALLOW_AUTOMATION' below to allow automation and the sandbox crash should disappear, 
 * showing that this is somehow related to something responding to the VST wrapper automation flag.
 * >>>
 */
//#define ALLOW_AUTOMATION 1

// The implementation of this class appears to introduce the bug whereas AudioParameterFloat is fine
class NonAutomatableAudioParameterFloat : public juce::AudioParameterFloat
{
public:
    using juce::AudioParameterFloat::AudioParameterFloat;

protected:
#ifdef ALLOW_AUTOMATION
    bool isAutomatable() const override { return true; }
#else
    bool isAutomatable() const override { return false; }
#endif
};

By default, it disables host automation of the parameter - which is what produces the crash when Waveform’s sandbox is enabled on this plugin produced by this code. By commenting out the line above the class or by defining ‘ALLOW_AUTOMATION’, automation can be turned on which will show that no crashes occur in any scenario even though that is the only difference (returning true vs false to enable automation or not for the single test audio parameter).

The editor sets up the Slider and listens to the ValueTreeState:

NonAutomatableSandboxTestProcessorEditor::NonAutomatableSandboxTestProcessorEditor (NonAutomatableSandboxTestProcessor& p, juce::AudioProcessorValueTreeState& vts)
    : AudioProcessorEditor (&p), audioProcessor (p), valueTreeState(vts)
{
    setSize (400, 300);
    testValueAttachment.reset(new juce::AudioProcessorValueTreeState::SliderAttachment(valueTreeState, "testValue", testValueSlider));
    addAndMakeVisible(testValueSlider);
}

The plugin provides a method to update and notify the ‘testValue’ parameter which is called by the editor on mouseUp(), any of the following implementations will trigger the crash:

void NonAutomatableSandboxTestProcessorEditor::mouseUp(const juce::MouseEvent& event)
{
    float value = (float)rand() / (float)RAND_MAX;
    testValueSlider.setValue(value);

    // The alternatives below all also crash when sandbox is running
    //testValueSlider.setValue(value, juce::NotificationType::sendNotificationAsync);
    //testValueSlider.setValue(value, juce::NotificationType::sendNotificationSync);
    //testValueSlider.setValue(value, juce::NotificationType::dontSendNotification);
}

or

void NonAutomatableSandboxTestProcessorEditor::mouseUp(const juce::MouseEvent& event)
{
    float value = (float)rand() / (float)RAND_MAX;
    auto* parameter = valueTreeState.getParameter("testValue");
    parameter->setValueNotifyingHost(value);
}

or

void NonAutomatableSandboxTestProcessorEditor::mouseUp(const juce::MouseEvent& event)
{
    float value = (float)rand() / (float)RAND_MAX;
    audioProcessor.setTestValue(value);
}

The setTestValue() method on the plugin itself that is being called there looks like this:

void NonAutomatableSandboxTestProcessor::setTestValue(float newValue) { testValueParam->setValueNotifyingHost(newValue); }

So that’s it, via the custom subclass of AudioParameterFloat named NonAutomatableAudioParameterFloat you can build the plugin where the subclass’s isAutomatable() return’s true or false using a simple #define flag.

When it returns true, the above code initiated by clicking the background of the editor runs fine in Waveform’s sandbox or with sandbox disabled. When it returns false however, sandbox will experience a crash when clicking the background, especially when first loading a project that has an instance of this plugin saved on a track and when first opening the editor and clicking upon initial load.

Essentially the matrix is:

  • automatable: true, sandbox: true → crash: false
  • automatable: true, sandbox:false → crash: false
  • automatable: false, sandbox: false → crash: false
  • automatable: false, sandbox:true → crash: true

I’m attaching the source files. This was only tested with the system info I posted above so mileage may vary on other setups.

I also played with placement of MessageManagerLock but it didn’t seem to offer any help.

Am I just missing something? I can’t claim to know all the ins and outs of what might possibly be going on here.

NonAutomatableSandboxTestProcessor.cpp (4.3 KB) NonAutomatableSandboxTestProcessor.h (3.0 KB) NonAutomatableSandboxTestProcessorEditor.cpp (1.5 KB) NonAutomatableSandboxTestProcessorEditor.h (943 Bytes)

Any parameters that aren’t automatable are effectively supposed to be hidden from the host.
It could be some logic error in my map that maps automatable parameters to all parameters in the sandbox bridge…

When you load the plugin in Waveform and select it, does this parameter that is declared not automatable appear in the parameters list? E.g:

I built with the test parameter both enabled for automation and disabled and checked in Waveform. In both cases, in sandbox mode, Waveform behaves as expected: when the parameter is automatable it appears in the list, and when isAutomatable() returns false, it does not appear in the parameter list.

The thing that’s interesting is that it is possible to sometimes restart the sandbox while in Waveform and have things all behave perfectly with no crash - it’s just inconsistent for me. Also, when I see this behavior, it seems like it occurs most during things like first opening Waveform → the project edit → the plugin’s editor, or when restarting the plugin sandbox; in other words, when lots of initializing of the plugins has theoretically just occurred.

With the example code I provided, when I first start Waveform and open a project with the plugin instance saved to the track, open the test plugin’s editor, and click to test - I get a crash more or less on the first click, basically every time. Usually, if I restart the plugin sandbox, and open the editor again and click, it’s stable and I can’t generate another crash without reloading the project.

I also tried testing keeping Waveform open and just reopening the same project edit after the plugin crashes; reopening/retesting it many times. Sometimes on project/edit load the plugin is fine and doesn’t crash, often times it crashes however upon first interaction after loading the edit.

Also, of note in this scenario is that even during the same edit, even when the crash was initially avoided upon first load, I tried opening and closing the editor many times and testing it (without restarting Waveform, and keeping the same edit open). The bizarre thing is in the same edit session, I might open the editor and test with no crash, but upon subsequent reopens of the editor it might crash. Basically, it seems like upon opening the editor there is a coin flip and the editor ends up in one of two states until its next close/reopen: crash when tested, or no crash.

It may not be related, but it really seems like when this problem occurs, if it is going to occur, it has something to do with editor initialization.

Ok thanks I’ll give this a try. Just to clarify I’ve got this right, it’s essentially the call to setValueNotifyingHost that ends up causing the crash if a parameter is set to non-automatable?

I think I can see where this might be coming from…

Yes, it is a call to setValueNotifyingHost on a non-automatable parameter when running in the plugin sandbox that for sure triggers the crash. That appears to be the only/main offending call in this scenario.

Semi-unrelated to this Tracktion bug, but:

APVTS makes this slightly trickier than it should, but you can also create a dummy AudioProcessor object and add your non-automatable parameters to it.

That has the advantage of working correctly across hosts that don’t respect isAutomatable() like Ableton Live.

1 Like

Ok, I think I’ve found the issue with this. I wasn’t correctly handling a parameter index when it was coming directly from the plugin and writing to some out of bounds memory.

I think I’ve fixed this now so @Cymatic I’ll PM you a copy of a new build when its done to verify it has indeed fixed the issue.

Thanks a lot for the help on this, I’ve seem something that was hard to decipher from our online logs (due to the process separation) but I think this is actually that problem. Cheers!

1 Like

Just a quick note to anyone watching this thread for a related issue, the updates in 11.5.4 so far seem to fix this crash for me! dave96 coming correct! Thanks, Dave.