Jassert (isPositiveAndBelow (index, numUsed)); when tweaking knobs

Hello,

I keep getting random asserts both on release and debug of running my project in VS 2019 or in a host.

It seems to be related to parameters (ValueTree) and listeners of knobs / sliders.

Error happens in juce_ArrayBase:

inline const ElementType& operator[] (const int index) const noexcept
{
    jassert (elements != nullptr);
    // Breakpoint hits below
    jassert (isPositiveAndBelow (index, numUsed));
    return elements[index];
}

Here’s the call stack:

Here’s how I’m making parameters / GUI code:

  • In AudioProcessor’s constructor:

    , m_treestate(*this, nullptr, “PARAMETERS”, CreateParameters())

and inside the AudioProcessor::CreateParameters() :

juce::AudioProcessorValueTreeState::ParameterLayout NewProjectAudioProcessor::CreateParameters()
{
    juce::AudioProcessorValueTreeState::ParameterLayout params;

    params.add(std::make_unique<AudioParameterFloat>(Ids::Gain::id, Ids::Gain::text, NormalisableRange<float>(0.0f, 1.0f), 0.5f));
    // The rest of the params.add
    return params;
}
  • in AudioProcessorEditor (PluginEditor.cpp):
    constructor:

    {
      // m_gainValue is a ScopedPointer<AudioProcessorValueTreeState::SliderAttachment> 
      m_gainValue = SetSlider(m_gainSlider, "gain", 0.0f, 1.0f, 0.5f, 10, 10, 75, 75);
      addAndMakeVisible(m_gainSlider);
    
      // Filter
      addAndMakeVisible(m_filter);
      m_filter.setBounds(0, 160, 220, 150);
    }
    

where SetSlider() is a static method that is either used directly from processor editor or called by the member components:

AudioProcessorValueTreeState::SliderAttachment* NewProjectAudioProcessorEditor::SetSlider(Slider& slider, const juce::String name, double min, double max, double default, double x, double y, double w, double h, Slider::SliderStyle style /*= Slider::RotaryVerticalDrag*/, Slider::TextEntryBoxPosition textPosition /*= juce::Slider::NoTextBox*/)
{
	slider.setName(name);
	slider.setNormalisableRange(NormalisableRange<double>(min, max));
    slider.setValue(default);
    slider.setSliderStyle(style);
    slider.setTextBoxStyle(textPosition, true, 80, 20);
    slider.setBounds(x, y, w, h);
    return new AudioProcessorValueTreeState::SliderAttachment(m_audioProcessor.m_treestate, name, slider);
}

Example for Filters (m_filter is a member in AudioProcessorEditor and its type is CFilter)

CFilter::CFilter (NewProjectAudioProcessor& p)
: m_processor(p)
{
    const float verticalOffset = 10;
    // m_cutoffValue is a ScopedPointer<AudioProcessorValueTreeState::SliderAttachment> 
    m_cutoffValue = NewProjectAudioProcessorEditor::SetSlider(m_cutOffSlider, p, Ids::Filter::Cutoff::id, 20.0f, 20000.0f, 10000.0f, 0, verticalOffset, 100, 100);
    addAndMakeVisible(m_cutOffSlider);
}

What do you think is the cause of this? race conditions? Cross Thread Access?

Thanks!

Just to provide an update, I converted all the scopedpointers to std::unique_ptr as I noticed that was deprecated.

I still have this jassert issue.

How are jasserts treated in JUCE? Are they a signal to let us know that we are about to crash? or is it just a “you should probably fix this but it’s ok to ship” ?

asserts are used by programmers to alert of them that something is wrong, as defined by the assertion failing. They do indicate something is wrong, and should be fixed. In this case you are being informed that the index being used to access the Array element is out of bounds.

The meaning of assertion may not always be set in the stone and it can differ from company to company. For me, asserts should be only reserved if a crash possibility is imminent. Sometime we also intentionally crash the software artificially to save the day. An example is when allowing to continue a behaviour may corrupt the save file but crashing will leave the save file alone. In that case we usually perform an intentional crash.

As for the array elements being oob. I have no idea what that could mean in the context of using the juce framework. I haven’t created any arrays and that is clearly from internal classes of valuetreestate and its dependent code so what I’m hoping for is to see if anyone is familiar with that actual incident.

Fair enough, that the ‘meaning’ could be different. And, in that case, I guess it’s up to you to decide. :slight_smile:

I use them in a way that I never expect them to fail. ie. giving me runtime checks that are removed in release builds.

Back to the specifics, my gut says asserts in JUCE indicate something is wrong, and needs to be fixed. I am not familiar enough with the code here to comment on where it is going wrong.

What’s the value of index at the assert breakpoint?

If you want members of the editor to be creating sliders, etc, you will want to use components.

If you haven’t already, I’d recommend first setting up your sliders and attachments in a very straightforward way (as in the tutorial) and get that working before extracting out into components.

So I didn’t post the entire constructor. Other than gain slider everything else is a component: Oscillator, LFO, Filter, Enveloped, Midi Keyboard etc.

As for the setup, I think it is recommended to use AudioProcessorValueTreeState and not directly getting a callback for each slider / buttons which is the way I have set up.

and sadly my old memory dumps are now outdated and won’t open with my current code. So I no longer have this and somehow the crashes are also no longer!

I will update this thread if I see it again but if I remember correctly both numbers index and numUsed were 0, hence the assert.

Ok I got it again accidentally after moving some knobs:

index = 0
numUsed = definitely invalid memory.

not just invalid memory, but it looks like the object this numUsed belongs to has been deleted (as indicated by the 0xdddddddd value. Not sure other compilers use that same value, but it is what you get with a debug build under VS.

Whups, reading the code again, not sure why I jumped to conclusions there.

Is everything happy if you relocate the slider logic out of the static method and manually place it where you need it?

I’m a bit unclear on what the goal is with the static method — is it to keep code tidy/DRY?

Sorry for the late response,

I was trying to get it to occur again. So this time I moved the slider attachment back into each component that has the slider rather than passing it to that “SetSlider” function. I also removed the AudioProcessor that was passed in.

I still get the assert.
This time both sides of the assert are 0.

jassert (isPositiveAndBelow (index, numUsed)); // both index and numUsed are zero

and the array that is trying to index is actual destroyed and the object isn’t valid.

I’m hitting a blocker with this issue. It’s not good for the synth to crash on a production, I have followed the tutorials and I believe I did as it was recommended so at this point I’m starting to think that this may be an actual bug.

The purpose of the SetSlider function is to save repetitive code specially for setting simple things such as position, name and default values etc.

Can you confirm that all of your sliders and attachments are setup in a straightforward way, such as:

// Private members of a Component subclass
Slider gainSlider;
std::unique_ptr<SliderAttachment> gainAttachment;

// In a constructor or method of the component
gainSlider.setSliderStyle (Slider::Rotary);
addAndMakeVisible (&gainSlider);
gainAttachment = std::make_unique<SliderAttachment> (parameters, "gain", gainSlider);

If not, can you post example code of a component setting up a slider/attachment?

Can you show us how CreateParameters is being used?

Do you have any AudioProcessorValueTreeState::Listeners setup at this point?

You had a few non-traditional things going on originally, so I’m thinking there might still be something off. What version of JUCE are you on?

Thanks Sudara for the quick response!

Here’s a code example from LFO.cpp in the constructor (LFO is a component):

m_speedSlider = std::make_unique<juce::Slider>();

NewProjectAudioProcessorEditor::SetSlider(*m_speedSlider, Ids::LFO::Speed::id, Ids::LFO::Speed::min, Ids::LFO::Speed::max, Ids::LFO::Speed::min, 0, verticalOffset, 100, 100);

m_speedSlider->setDoubleClickReturnValue(true, 0.0f);

m_lfoSpeedValue = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(m_processor.m_treestate, Ids::LFO::Speed::id, *m_speedSlider);

addAndMakeVisible(m_speedSlider.get());

and destructor:

CLFO::~CLFO()
{
    m_lfoSpeedValue = nullptr;
    m_lfoAmountValue = nullptr;

    m_speedSlider = nullptr;
    m_amountSlider = nullptr;
}

including the updated SetSlider static function here (no more passing of audio processor or making attachment):

void SetSlider(Slider& slider, const juce::String name, double min, double max, double default, int x, int y, int w, int h, Slider::SliderStyle style /*= Slider::RotaryVerticalDrag*/, Slider::TextEntryBoxPosition textPosition /*= juce::Slider::NoTextBox*/, double interval /* = 0 */)
{
    slider.setName(name);
    if (interval == 0)
    {
        slider.setNormalisableRange(NormalisableRange<double>(min, max));
    }
    else
    {
        slider.setRange(min, max, interval);
    }
    slider.setValue(default);
    slider.setSliderStyle(style);
    slider.setTextBoxStyle(textPosition, true, 80, 20);
    slider.setBounds(x, y, w, h);
}

Then in PluginEditor.cpp’s constructor:
initialized like this:

testProjectAudioProcessorEditor::testProjectAudioProcessorEditor(testProjectAudioProcessor& p)
: AudioProcessorEditor(&p), m_audioProcessor(p),
m_lfo(p, "") // not using the string parameter right now so please ignore it
{
    // LFO
    addAndMakeVisible(m_lfo);
    m_lfo.setBounds(0, 400, 220, 150);
}

Then in PluginProcessor.cpp’s constructor:

testProjectAudioProcessor::testProjectAudioProcessor()
#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
                    )
    , m_treestate(*this, nullptr, "PARAMETERS", CreateParameters())
#endif
{
    //... other stuff not related to tree state or parameter creation.
}

And here’s the CreateParameters() function:

juce::AudioProcessorValueTreeState::ParameterLayout testProjectAudioProcessor::CreateParameters()
{
    juce::AudioProcessorValueTreeState::ParameterLayout params;

    // LFO
    params.add(std::make_unique<AudioParameterFloat>(Ids::LFO::Speed::id, Ids::LFO::Speed::text, NormalisableRange<float>(Ids::LFO::Speed::min, Ids::LFO::Speed::max), Ids::LFO::Speed::min));
    params.add(std::make_unique<AudioParameterFloat>(Ids::LFO::Amount::id, Ids::LFO::Amount::text, NormalisableRange<float>(Ids::LFO::Amount::min, Ids::LFO::Amount::max), Ids::LFO::Amount::min));
    
    return params;
}

Including get and set state information just in case:

void testProjectAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    auto state = m_treestate.copyState();
    std::unique_ptr<juce::XmlElement> xml(state.createXml());
    copyXmlToBinary(*xml, destData);
}

void testProjectAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
    
    if (xmlState.get() != nullptr && xmlState->hasTagName(m_treestate.state.getType()))
    {
        m_treestate.replaceState(juce::ValueTree::fromXml(*xmlState));
    }
}

and the values are read in the ProcessBlock of PluginProcessor

void testProjectAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
//...
        const float lfoSpeed = m_treestate.getParameterAsValue(Ids::LFO::Speed::id).getValue();
        const float lfoAmount = m_treestate.getParameterAsValue(Ids::LFO::Amount::id).getValue();
//...
}

Do you have any AudioProcessorValueTreeState::Listener s setup at this point?

All the usage / access of the treestate is in the functions I listed in this post. There are no manual listeners added, nor any of the components are inheriting any listeners.

You had a few non-traditional things going on originally, so I’m thinking there might still be something off. What version of JUCE are you on?

I’m on Juce 6.0.1 which is the latest official release. I’m not sure what’s considered traditional.

I hope there is something in the way i set the parameters up or reading them that gives it away. Most of the parameter reads are using the getParameterAsValue() function. I have one that is getRawParameterValue() and I thought I should mention it.

Bad word, sorry! I just meant there were a few ways things were deviating from suggested use in the tutorials, etc. The only deviations I see now are that your m_speedSlider is a unique_ptr instead of a Slider and your destructor is manually managing things like the smart pointers.

Beyond that I’m not seeing anything obvious anymore. In these situations my strategy is usually to rip code out of the project until things work and then add code back in again :slight_smile:

Good luck!!

Thanks, fun fact: Sliders used to be just normal object instances, no pointers, no scoped or any other smart pointers. This was still happening.

I am strongly convinced this is a bug.

What is the right way to report bugs for juce?

Is there any sort of direct support linked to licensing (ie, paid vs free)? I’m willing to pay for the subscription to have this get looked at.

Cheers!

While it is perfectly possible that it is a juce bug, the chances that you are doing something different from what others do, who have not that problem, is quite likely.

AFAIK the JUCE team reads all those posts. As soon as you have a way to reproduce it in a minimal/small program, they will look at it, regardless if you have a subscription or not. They want to get rid of the bugs, since they will bite paying customers also…

It would be great if you could strip out the relevant parts so the bug still occurs. That would give a starting point for investigation.

Hi!

I have some update as I was further trying to figure out what’s going on. I get some very unusual behaviors that I think could be related and may as well be the cause of it:

1- My Oscillator component steals the focus completely once interacted with:

Once I click and drag anything in Osc1 (which is the Oscillator component) the key on the keyboard no longer releases even if I let go of the physical button on my keyboard.

The oscillator component has slider attachments and sliders both declared as unique pointers.

m_octaveOffsetSlider = std::make_unique<juce::Slider>();
NewProjectAudioProcessorEditor::SetSlider(*m_octaveOffsetSlider, Ids::Frequency::Octave::id + m_index, -5.0f, 5.0f, 0.0f, 250, verticalOffset, 50 + textboxWidthOffset, 50, Slider::RotaryVerticalDrag, juce::Slider::TextEntryBoxPosition::NoTextBox);
m_octaveOffsetSlider->setDoubleClickReturnValue(true, 0);
m_octaveOffsetValue = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(m_processor.m_treestate, Ids::Frequency::Octave::id + m_index, *m_octaveOffsetSlider);
addAndMakeVisible(m_octaveOffsetSlider.get());

2- Today I added a combo box to Filter component and the weird thing is now I get the same input problem as with the Oscillator component!

ComponentFilter

Also suddenly I got a crash upon closing the standalone software “cannot delete incomplete component”. Note: the crash goes away if I do not set m_groupFilter = nullptr in destructor!

Filter component header:

std::unique_ptr<AudioProcessorValueTreeState::SliderAttachment> m_cutoffValue;
std::unique_ptr<AudioProcessorValueTreeState::SliderAttachment> m_resonanceValue;
std::unique_ptr<AudioProcessorValueTreeState::ComboBoxAttachment> m_filterTypeValue;

std::unique_ptr<Slider> m_cutOffSlider;
std::unique_ptr<Label> m_cutOffLabel;

std::unique_ptr<Slider> m_resonanceSlider;
std::unique_ptr<Label> m_resonanceLabel;
// Added this before m_groupFilter
std::unique_ptr<juce::ComboBox> m_comboBoxFilterType;

std::unique_ptr<juce::GroupComponent> m_groupFilter;

Filter component cpp:

CFilter::CFilter (NewProjectAudioProcessor& p)
    : m_processor(p)
{
    const float verticalOffset = 10;
    m_cutOffSlider = std::make_unique<juce::Slider>();
    NewProjectAudioProcessorEditor::SetSlider(*m_cutOffSlider, Ids::Filter::Cutoff::id, 20.0f, 20000.0f, 10000.0f, 0, verticalOffset, 100, 100);
    m_cutOffSlider->setSkewFactorFromMidPoint(500.0f);
    m_cutOffSlider->setDoubleClickReturnValue(true, 20000.0f);
    m_cutoffValue = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(m_processor.m_treestate, Ids::Filter::Cutoff::id, *m_cutOffSlider);
    addAndMakeVisible(m_cutOffSlider.get());

    m_cutOffLabel = std::make_unique<juce::Label>();
    NewProjectAudioProcessorEditor::SetLabel(*m_cutOffLabel, "labelCutoff", Ids::Filter::Cutoff::text, 24, 96+verticalOffset, 56, 32);
    addAndMakeVisible(m_cutOffLabel.get());

    m_resonanceSlider = std::make_unique<juce::Slider>();
    NewProjectAudioProcessorEditor::SetSlider(*m_resonanceSlider, Ids::Filter::Resonance::id, 1.0f, 10.0f, 1.0f, 100, verticalOffset, 70, 70);
    m_resonanceSlider->setDoubleClickReturnValue(true, 1.0f);
    m_resonanceValue = std::make_unique<AudioProcessorValueTreeState::SliderAttachment>(m_processor.m_treestate, Ids::Filter::Resonance::id, *m_resonanceSlider);
    addAndMakeVisible(m_resonanceSlider.get());

    m_resonanceLabel = std::make_unique<juce::Label>();
    NewProjectAudioProcessorEditor::SetLabel(*m_resonanceLabel, "labelResonance", Ids::Filter::Resonance::text, 100, m_resonanceSlider->getHeight() + verticalOffset - 10, 77, 32);
    addAndMakeVisible(m_resonanceLabel.get());
    
    // Added this comboxbox
    m_comboBoxFilterType = std::make_unique<juce::ComboBox>();
    m_comboBoxFilterType->setName(Ids::Filter::Type::id);
    m_comboBoxFilterType->addItemList(Ids::Filter::Type::Choices, 1);
    m_comboBoxFilterType->setSelectedId(1);
    m_comboBoxFilterType->setBounds(100, 95 + verticalOffset, 80, 35);
    m_filterTypeValue = std::make_unique<AudioProcessorValueTreeState::ComboBoxAttachment>(m_processor.m_treestate, Ids::Filter::Type::id, *m_comboBoxFilterType);
    addAndMakeVisible(m_comboBoxFilterType.get());

    m_groupFilter.reset(new juce::GroupComponent ("groupFilter", TRANS("Filter")));
    addAndMakeVisible (m_groupFilter.get());
    m_groupFilter->setBounds (0, 0, 210, 150);

    setSize (220, 150);
}

CFilter::~CFilter()
{
    m_cutoffValue = nullptr;
    m_resonanceValue = nullptr;
    m_filterTypeValue = nullptr;

    m_cutOffSlider = nullptr;
    m_cutOffLabel = nullptr;
    m_resonanceSlider = nullptr;
    m_resonanceLabel = nullptr;
    m_comboBoxFilterType = nullptr;
    // If I remove the following line, I will no longer get a crash as "cannot delete incomplete component". 
    m_groupFilter = nullptr;
}

Here’s how I use the filter component in PluginEditor.cpp

// Oscillators
addAndMakeVisible(m_oscillator1);
m_oscillator1.setBounds(220, 170, 350, 200);

// Filter
addAndMakeVisible(m_filter);
m_filter.setBounds(0, 160, 220, 150);

Note that only the oscillator and filter components have the combo box.
I can move the filter combox box to another component and see if I get the same issue or not.

UPDATE: Confirmed, adding that combobox to any other component causes both of the issues: the crash and the input focus lock on that component.

The crash can easily be explained: The members are destroyed in the reverse order that they are declared. By declaring the Attachments before the Sliders and ComboBoxes, they will be deleted AFTER the controls they are controlling are already destroyed.

That can be easily fixed by reversing the order to always declare the attachment AFTER the control they are connecting.

I thought nothing is destroyed until you decrease the ref count to 0 by setting = nullptr in the desctructor.

If the issue is the order then I should be able to reverse the destruction order by just assigning the =nullptr for attachments after the sliders = nullptr.

My thinking was that if attachments require the sliders, the sliders cannot be set to nullptr before attachment.

Thanks

I just moved all the slider / combo box attachment declarations bellow the sliders/combox boxes in the header.

Still getting the same crash

If it makes any sense, if I remove the m_groupFilter from the component the crash goes away…
but the input issue remains.