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)