[Solved] Uncommented exception in juce_AudioProcessorValueTreeState.cpp when migrating code

Solution Here

Hello,
I am working on merging reverb code into a compressor. The project is an amalgamation of different code bases my boss has. We are encountering some problems with unifying the GUIs and DSP of both code bases into a single AudioProcessorValueTreeState. I have been watching tutorials and reading the forums (very helpful), but have a question specifically about this exception in juce_AudioProcessorValueTreeState.cpp on lines 476-486, which does not have the helpful comments the JUCE team usually leaves in these exceptions. Can anyone point me in the right direction?

// Line 476 starts below
template <typename Attachment, typename Control>
std::unique_ptr<Attachment> makeAttachment (const AudioProcessorValueTreeState& stateToUse,
                                            const String& parameterID,
                                            Control& control)
{
    if (auto* parameter = stateToUse.getParameter (parameterID))
        return std::make_unique<Attachment> (*parameter, control, stateToUse.undoManager);

    jassertfalse;
    return nullptr;
}

The exception triggers on line 484 (jassertfalse;) with the message “A breakpoint instruction (__debugbreak() statement or a similar call) was executed in SMPLComp.exe.”

The call stack is,

>	SMPLComp.exe!juce::makeAttachment<juce::SliderParameterAttachment,juce::Slider>(const juce::AudioProcessorValueTreeState & stateToUse, const juce::String & parameterID, juce::Slider & control) Line 484	C++
 	SMPLComp.exe!juce::AudioProcessorValueTreeState::SliderAttachment::SliderAttachment(juce::AudioProcessorValueTreeState & stateToUse, const juce::String & parameterID, juce::Slider & slider) Line 492	C++
 	SMPLComp.exe!LabeledSlider::reset(juce::AudioProcessorValueTreeState & state, const juce::String & paramID) Line 52	C++
 	SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() Line 156	C++
 	SMPLComp.exe!SmplcompAudioProcessorEditor::SmplcompAudioProcessorEditor(SmplcompAudioProcessor & p, juce::AudioProcessorValueTreeState & vts) Line 24	C++
 	SMPLComp.exe!SmplcompAudioProcessor::createEditor() Line 224	C++
 	SMPLComp.exe!juce::AudioProcessor::createEditorIfNeeded() Line 891	C++
 	SMPLComp.exe!juce::StandaloneFilterWindow::MainContentComponent::MainContentComponent(juce::StandaloneFilterWindow & filterWindow) Line 890	C++
 	SMPLComp.exe!juce::StandaloneFilterWindow::updateContent() Line 855	C++
 	SMPLComp.exe!juce::StandaloneFilterWindow::StandaloneFilterWindow(const juce::String & title, juce::Colour backgroundColour, juce::PropertySet * settingsToUse, bool takeOwnershipOfSettings, const juce::String & preferredDefaultDeviceName, const juce::AudioDeviceManager::AudioDeviceSetup * preferredSetupOptions, const juce::Array<juce::StandalonePluginHolder::PluginInOuts,juce::DummyCriticalSection,0> & constrainToConfiguration, bool autoOpenMidiDevices) Line 772	C++
 	SMPLComp.exe!juce::StandaloneFilterApp::createWindow() Line 78	C++
 	SMPLComp.exe!juce::StandaloneFilterApp::initialise(const juce::String & __formal) Line 96	C++
 	SMPLComp.exe!juce::JUCEApplicationBase::initialiseApp() Line 300	C++
 	SMPLComp.exe!juce::JUCEApplication::initialiseApp() Line 92	C++
 	SMPLComp.exe!juce::JUCEApplicationBase::main() Line 259	C++
 	SMPLComp.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 48	C++
 	[External Code]	

I can provide additional code snippets if needed but can’t provide the entire project file as it is closed-source.

Developing in VS2022 17.6.5 on Windows 11.

Thanks,
Kris

It means it couldn’t find the parameter you wanted to attach to.

1 Like

Thank you!

So the exception actually occurs four times. The fourth line in the call stack changes as follows with each error:

 	SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() Line 156	C++
 	SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() Line 160	C++
 	SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() Line 164	C++
 	SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() Line 168	C++

These correspond to the following calls in my PluginEditor.cpp:

    sizeLSlider.reset(valueTreeState, "size");
    dampLSlider.reset(valueTreeState, "damp");
    widthLSlider.reset(valueTreeState, "width");
    rmixLSlider.reset(valueTreeState, "rmix");

So, thank you, that definitely pointed me in the right direction. I will look more into the parameter value tree functionality and determine where we’re going wrong.

One follow up note for anyone, the call stack refers to SMPLComp.exe!SmplcompAudioProcessorEditor::initWidgets() because my PluginEditor class inherits SmplcompAudioProcessorEditor. That confused me at first because I expected my class/file names, not JUCE’s.

After your comment I realized I could just right-click on the error and “Go to source code”. Ah well, it’s a learning moment.

These are the changes I have made which eliminated the exceptions. Hopefully all the new parameters are properly linked into the GUI/editor and the ParameterValueTree. We are still working on the DSP code so I am not certain that everything is linked properly. Next steps are to dump the ParameterValueTree XML file to verify and wire the reverb into the chain after the compressor.

  1. Adding parameter listeners in PluginProcessor.cpp (“PP.cpp”):
    parameters.addParameterListener("size", this);
    parameters.addParameterListener("damp", this);
    parameters.addParameterListener("width", this);
    parameters.addParameterListener("rmix", this);
  1. Adding setter functionality to parameterChanged in PP.cpp:
    else  if (parameterID == "size") compressor.setSize(newValue);
    else  if (parameterID == "damp") compressor.setDamp(newValue);
    else  if (parameterID == "width") compressor.setWidth(newValue);
    else  if (parameterID == "rmix") { /* TODO: implement rmix -KGK */ };
  1. Setting parameter editor range and default values in PP.cpp:
	params.push_back(std::make_unique<AudioParameterFloat>	("rmix", "RMix",
																NormalisableRange<float>(
                                                               Constants::Parameter::rmixStart,
                                                               Constants::Parameter::rmixEnd,
                                                               Constants::Parameter::rmixInterval),
                                                           1.0f, "%", AudioProcessorParameter::genericParameter,
                                                           [](float value, float)
                                                           {
                                                               return String(value * 100.0f, 1) + " %";
                                                           }));
    // (three similar calls folllow... -KGK)
  1. Initializing custom parameters (*) in Compressor.cpp:
Compressor::Compressor() : AudioProcessor( /* Call base constructor -KGK */ )
{
    addParameter(feedback = new CustomParameter());
    feedback->setName("feedback");
    feedback->setValue(defaultFeedback, pluginSettingsState);

    addParameter(numberOfEchoes = new CustomParameter());
    numberOfEchoes->setName("numberOfEchoes");
    numberOfEchoes->setValue(defaultNumberOfEchoes, pluginSettingsState);

    addParameter(echoTimeDelay = new CustomParameter());
    echoTimeDelay->setName("echoTimeDelay");
    echoTimeDelay->setValue(defaultEchoTimeDelay, pluginSettingsState);
}
  1. Setters in Compressor.cpp for PP.cpp to call on.

  2. Constants for the parameter editor values.

(*): The compressor and the reverb use two different internal schemes for parameter variables. Compressor uses simple floats, reverb uses CustomParameters which include getters, setters, names, labels, etc. I’m advocating that we convert everything to CustomParameters as it seems like the best practice. It is a bit overkill as the DSP module already has getters/setters which PP.cpp relies on. Thoughts?

Thank you for coming to my devlog :^)

a) what would be custom with your parameters? I suggest to use the existing ones, but save them as pointer with the right type, i.e.

juce::AudioParameterInt* thing { nullptr };
// in the constructor
thing = dynamic_cast<juce::AudioParameterInt*>(getParameter("thing");
jassert (thing); // this one would have made you aware of the original problem

// using it
auto value = thing->get(); // this returns an actual int in the correct range, no implicit conversion, no warnings

b) I am not a fan of the parameterListener(). You introduce an avoidable string comparison. Depending on how many parameters you have this could pile up

Re: getters/setters
That is a topic where even seasoned developers will discuss for ages if you let them.

  • It really makes no difference. In performance they are inlined and won’t have any effect after the optimiser, maybe even without
  • They come from the strict OO world, where encapsulation was achieved by making everything private and only expose via getters and setters. It is still the safest way and has some nice side effects for debugging, like being able to set a breakpoint whenever the value is changed from outside. And that makes it easier to write unit tests too.
    But I only do it now for library code. In some cases I even allow public variables, when the object has little to no own logic.

Here is the custom parameter class used by the reverb code:

Header:

#pragma once

#include <JuceHeader.h>

using namespace std;
using namespace juce;

class CustomParameter : public AudioProcessorParameter {
public:
	// Inherited via AudioProcessorParameter
	float getValue() const;
	void setValue(float newValue);
	void setValue(float newValue, ValueTree& state);
	void setValueNotifyingHost(float newValue, ValueTree& state);
	float getDefaultValue() const;
	String getName(int maximumStringLength) const;
	void setName(String name);
	String getLabel() const;
	float getValueForText(const String& text) const;
private:
	float value = 0;
	String name;
};

Pretty standard stuff in the header - getters, setters, utility functions.

Class:

#include "CustomParameter.h"

float CustomParameter::getValue() const
{
    return value;
}

void CustomParameter::setValue(float newValue)
{
    value = newValue;
}

void CustomParameter::setValue(float newValue, ValueTree& state)
{
    state.setProperty(getName(999), newValue, nullptr);
    setValue(newValue);
}

void CustomParameter::setValueNotifyingHost(float newValue, ValueTree& state)
{
    state.setProperty(getName(999), newValue, nullptr);
    this->AudioProcessorParameter::setValueNotifyingHost(newValue);
}

float CustomParameter::getDefaultValue() const
{
    return 0.0f;
}

String CustomParameter::getName(int maximumStringLength) const
{
    return name.substring(0, maximumStringLength - 1);
}

void CustomParameter::setName(String name)
{
    this->name = name;
}

String CustomParameter::getLabel() const
{
    return String();
}

float CustomParameter::getValueForText(const String& text) const
{
    return 0.0f;
}

Only noteworthy thing here is the setValueNotifyingHost. From my read of the documentation, that function seems to be meant for plugins. This is a standalone application, so that isn’t necessary. We can probably refactor to existing parameter types as you suggested.


Ok, thank you for sharing this. I asked ChatGPT for a comparison of that code with our current method (see point #3 in my previous post). It highlighted the use of jassert in your snippet as a powerful runtime check; I will start incorporating that to catch these errors earlier. Otherwise, it seems like both methods are valid. The current code has the advantage of utilizing the constants my client is adjusting to dial in the editor–DSP functionality, so we’ll probably stick with that.

I have seen more memory/pointer schemes in Juce than I can count, from unique pointers…

    std::unique_ptr<MainWindow> mainWindow;

to safe pointers…

Component::SafePointer <AudioSettingsWindow> settingsWindow;

to shared pointers (only in library code). And then there are the plain, straightforward float/int/bool variable declarations, sometimes protected by getters/setters. Thank you for showing me another method to implement this. I’m starting to understand the finer points of when and where to use each method.


Thanks for raising this point - the string comparison was definitely on my mind for performance reasons. Afaik, every microscopic adjustment to a slider will trigger a parameter callback. Those comparisons could add up and affect DSP performance.

The tutorial for adding plug-in parameters (link->) does not even mention parameter listeners. So the listener-observer pattern shouldn’t be necessary for straightforward programs? Even ones with complex DSP?
I.e. you would only need a listener when there are cascading changes - something like, when this parameter goes above/below this value, add/remove/modify another editor component?


Re getters/setters - I did not know their performance was optimized to be equal. I do appreciate them for their debugging potential. We’ll see if there is time in the budget to implement them or just do away with them.


Thank you, once again, for all your helpful insight!