AudioProcessorValueTreeState::ComboBoxAttachment consistency

Using the AudioProcessorValueTreeState class I’m building some lists of options to be controlled by ComboBoxAttachment objects. One issue is that since this stores the values in the plugin state as integers it is easy to break compatibility between plug-in versions if the lists change. In fact it is OK if you add items to the end of a list but not reorder them, since the integer values need to retain their “meaning”.

My use-case is that I’m listing some FFT sizes and want initially to expose only (say) 512, 1024, 4096. But later I might want to add 256 and 2048. In this case it doesn’t make sense to add items to the end of the list since the list 512, 102, 4096, 256, 2048 is just weird!

Anyway, while the big problem is storing the values as integers it could be worked around using item IDs and just omitting some values from the combo box.

For example I could put placeholders in my FFT lists for the values I don’t yet want to use, but still assign them an ID. Other lists could have built in space by using (say) increments of 10 for the itemID (just like old skool line numbering in basic programming!).

The thing that stops this working is an inconsistency between ComboBoxAttachment::Pimpl::comboBoxChanged() and ComboBoxAttachment::Pimpl::setValue():

    void comboBoxChanged (ComboBox* comboBox) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        if (! ignoreCallbacks)
        {
            beginParameterChange();
            setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f);
            endParameterChange();
        }
    }

This uses the item ID. But ComboBoxAttachment::Pimpl::setValue() uses the index:

    void setValue (float newValue) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);

        {
            ScopedValueSetter<bool> svs (ignoreCallbacks, true);
            combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync);
        }
    }

This would be solved using the item ID:

    void setValue (float newValue) override
    {
        const ScopedLock selfCallbackLock (selfCallbackMutex);
        
        {
            ScopedValueSetter<bool> svs (ignoreCallbacks, true);
            combo.setSelectedId (roundToInt (newValue) + 1, sendNotificationSync);
        }
    }

This makes the integer value “meaning” in the plugin state to be “item ID minus 1” in all cases.

1 Like