Why can’t a Normalizable Range have min == max momentarily? Hitting assert…

I have a TwoValueHorizontal slider (aka range bar) that goes from 0 to 100, to set the range for a parameter. It is valid to have the min and max at the same value. In such a case, the parameter would be limited to a fixed value.

I have implemented two other Sliders with TextBoxes to display the min and max values, and allow you to set them. They should basically update each other. If you drag on the range bar to change the min/max, it updates the TextBoxes to show the min/max. Concurrently, it must set the allowable range on the TextBoxes. If you change the min with the slider, the range on the max Text Box should be newMin to 100, etc.

As currently implemented, if you drag on the range bar such that the min and max both become the same value, it hits an assert in juce_NormalisableRange.h line 240:

    void checkInvariants() const
    {
        jassert (end > start);	// HITTING ASSERT HERE
        jassert (interval >= ValueType());
        jassert (skew > ValueType());
    }

Here is a simple project that demonstrates the issue:
SliderTester.zip (23.9 KB)

And the relevant code:

class MainComponent  : public juce::Component
{
public:
    //==============================================================================
    MainComponent();
    ~MainComponent() override;

    //==============================================================================
    void paint (juce::Graphics&) override;
    void resized() override;

private:
    //==============================================================================
    // Your private member variables go here...

    juce::Slider slider;
    juce::Slider incDec;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
#include "MainComponent.h"

//==============================================================================
MainComponent::MainComponent()
{
    setSize (600, 400);
    
    addAndMakeVisible(slider);
    slider.setSliderStyle(juce::Slider::TwoValueHorizontal);
    slider.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
    slider.setRange(0.0, 100.0, 1.0);
    slider.setMinValue(0.0);
    slider.setMaxValue(100.0);
    
    // move the min/max pointers on the slider, update the range
    // of the display slider
    slider.onValueChange = [this] {
        // set new range for incDec
        incDec.setRange(slider.getMinValue(), slider.getMaxValue(), 1.0);
    };

    addAndMakeVisible(incDec);
    incDec.setSliderStyle(juce::Slider::IncDecButtons);
    incDec.setRange(0.0, 100.0, 1.0);
}

void MainComponent::resized()
{
    // This is called when the MainComponent is resized.
    // If you add any child components, this is where you should
    // update their positions.
    
    slider.setBounds(20, 20, 300, 40);
    incDec.setBounds(340, 20, 100, 40);
}

Just drag the min/max pointers on the twoValue slider to the same value, and it asserts.

Why does it do this? Why can’t the range be squashed so that the min == max? I can see that jassert (end >= start); might make sense…

I can avoid this by using adding/subtracting a tiny decimal portion when the min/max are the same, even though I am using integers but this seems remarkably klugey. Thanks.

The problem is that the 0-to-1 value, which is the whole point of a NormalisableRange, is taken as (v - start) / (end - start), which is meaningless for a range of zero. It might be resolved conventionally, but then from0to1 (to0to1 (v)) would not equal v.

1 Like

I’m wondering if, instead of calling setRange() on incDec, you could create your own NormalisableRange, and call setNormalisableRange() with that, instead? Then, you could implement your own convertFrom0To1Func, convertTo0To1Func, and snapToLegalValueFunc callbacks (as lambdas?), which would allow you to specify a range of at least 1, but only allow one of the values (0 or 1, your choice, I guess) to actually be set. Haven’t tried that, but looks like it might be workable to me.