Label Attachment with AudioProcessorValueTreeState

(New to JUCE). I have a plugin with a mix of combo boxes, editable labels and a toggle button. The AudioProcessorValueTreeState class seems to have attachment classes for buttons, sliders and combo boxes, but not (editable) labels.
I want to use APVTS, particularly for its convenience with storing/retrieving parameters, so I’m wondering what the best approach here is.
Should I just not use APVTS for any of my parameters, or should I use it for the combo boxes and buttons, then just have normal parameters for the labels?
My editable labels are only going to be reading numbers if that makes a difference.

Check out the existing attachments… you can always add your own attachment classes for custom controls.

Rail

The attachment classes are used to connect AudioProcessorParameters with the GUI.
There is no string AudioProcessorParameter for good reason: strings are not threadsafe without a locking or other mechanics, which brings serious drawbacks in audio realtime processing. Then they can easily allocate which is also a no go in realtime programming (unless you use preallocated strings everywhere).
But most importantly: computers deal with numbers, they don’t read. Textual representations are most times for the users, so there is no need to access strings in the DSP code.

Thanks guys. I had imagined there was a good reason for the attachment classes being the way they are.
My labels are only for entering numbers between 0 and 127, so I ended up using the slider attachment for an invisible slider which listens to the label values.

This seems to work, but I wonder if there was a better way?

This is a valid approach that others used as well.
Alternatively have a look at Slider::IncDecButtons as SliderStyle, which gets close to what you want.

This is a Attachment class I created for a TempoLabel class (derived from Label):

Header:

class JUCE_API TempoLabelParameterAttachment : private Label::Listener
{
public:
    TempoLabelParameterAttachment( RangedAudioParameter& parameter,
                                   TempoLabel& label,
                                   UndoManager* undoManager = nullptr );

    ~ TempoLabelParameterAttachment() override;

    /** Call this after setting up your TempoLabel in the case where you need to do
        extra setup after constructing this attachment.
    */
    void sendInitialUpdate (RangedAudioParameter& parameter);
    
private:

    void setValue (float newValue);

    virtual void labelTextChanged (Label* labelThatHasChanged) override;

    TempoLabel& m_Label;
    ParameterAttachment attachment;
    bool ignoreCallbacks = false;
};

Implementation file:

#include "TempoLabelAttachment.h"

//-----------------------------------------------------------------------------------

TempoLabelParameterAttachment::TempoLabelParameterAttachment (RangedAudioParameter& parameter,
                                                              TempoLabel& label,
                                                              UndoManager* undoManager)
    : m_Label (label),
      attachment (parameter, [this] (float f) { setValue (f); }, undoManager)
{
    sendInitialUpdate (parameter);
    m_Label.addListener (this);
}

TempoLabelParameterAttachment::~TempoLabelParameterAttachment()
{
    m_Label.removeListener (this);
}

void TempoLabelParameterAttachment::sendInitialUpdate (RangedAudioParameter& parameter)
{
    attachment.sendInitialUpdate();
}

void TempoLabelParameterAttachment::setValue (float newValue)
{
    const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
    
    m_Label.setValue (newValue);
}

void TempoLabelParameterAttachment::labelTextChanged (Label* labelThatHasChanged)
{
    if (labelThatHasChanged == &m_Label)
        {
        if (ignoreCallbacks)
            return;
        
        m_Label.setTempo (labelThatHasChanged->getText());
        
        attachment.setValueAsCompleteGesture (m_Label.getValue());
        }
}

In my TempoLabel class I have a public member method:

void TempoLabel::setValue (float fNewTempo)
{
    m_fTempo = fNewTempo;
    
    const String szTempo = String (fNewTempo, 1) + " BPM";
    
    setText (szTempo, NotificationType::sendNotification);
}

and

void TempoLabel::setTempo (String szNewTempo)
{
    auto bpm = szNewTempo.getFloatValue();
    
    setValue (bpm);
}

Rail