LabelAttachment for AudioProcessorValueTreeState parameter

I need to to attach a label to an AudioProcessorValueTreeState.
text input has of course be interpreted using the parameters textToValueFunction.
text output should be generated using the valueToTextFunction.

Has anybody already solved that riddle? if not where would start to look for hints? Thanks!

1 Like

How do you want to implement your valueToText? Is there a mapping from a free text to a numerical value, that can be normalised to float numbers between 0 and 1? If yes, then you can do that straight forward.

But because hosts have no possibility to add free text into a parameter, it makes no sense to send that data over the host’s automation interface, afaik.

If you simply want to store a text inside your AudioProcessorValueTreeState (without automation) you can add a normal ValueTree node under the public state member of the AudioProcessorValueTreeState.

And to connect the ValueTree to a Label, I created the ffGuiAttachments on github.

Hope that makes sense…

I have again struggeled with the problem and I am more puzzled than before.

The AudioProcessorValueTreeState::createAndAddParameter () function allows to set
both a valueToTextFunction and a corresponding textToValueFunction.
Naively I exepted to be able to call these functions to set a parameter value and to update the label text upon parameter value change for example like this:

    void setValue(float newValue) override
    {
        const ScopedLock selfCallbackLock(selfCallbackMutex);
        {
            ScopedValueSetter<bool> svs(ignoreCallbacks, true);
            if (AudioProcessorParameter* p = getParameter())
                label.setText(p->getText(newValue, 64), sendNotificationSync);
        }
    }

Unfortunately the getText function expects the value to be normalized to the 0…1 range.
So well lets call p->range.convertTo0to1(newValue) to fix that.
Oh wait - that range is not a member of the AudioProcessorParameter class but instead belongs to the AudioProcessorValueTreeState::Parameter struct.
How then about casting the pointer to access the hidden member? No, that won’t work because AudioProcessorValueTreeState::Parameter is declared as private and of course it is defined in the cpp file and not in the header. That means it is completely unaccessible.

Now here I ran out of Ideas. Since the interface can not be extended because of the restrictive access rulings I have now three options left: (please correct me if I am wrong)

  • implement my own classes as a kind of parallel universe and use these where required.
  • directly access my own valueToTextFunction and textToValueFunction and forget about the encapsulation that the framework is meant to provide.
  • create my own fork of the framework and make AudioProcessorValueTreeState::Parameter
    public.

What are you guys doing in those situations? Thanks for any thoughts and Ideas.

1 Like

I am still not sure, if I understood, what you are up to. As I said, the host will not store texts, it will store floats, and even worse, only floats between 0 and 1. There might be platform specific additions to that, but this one is the one JUCE supports.

However, I created a mapping from text to float and use it as textToFloat methods. It should work for around 20 letters, until the accuracy of float values is reached. However, when I saved the state and reloaded it, I already had a huge loss of accuracy.

I published the code for a complete working plugin as gist

That’s the core:

void TextPluginAudioProcessor::setTextValue (const StringRef paramID, const StringRef text)
{
    if (auto parameter = parms.getParameter(paramID)) {
        float value = 0.0;
        for (int i=0; i<text.length(); ++i) {
            value += (text [i] - 32) / pow (100.0, i+1);
        }
        parameter->setValueNotifyingHost (value);
    }
}
String TextPluginAudioProcessor::getTextValue (const StringRef paramID)
{
    if (auto parameter = parms.getParameter(paramID)) {
        float value = parameter->getValue();
        String text;
        for (int i=0; i<32; ++i) {
            long column = static_cast<long> (value * pow (100.0, i)) % 100;
            if (column > 0 && column < 95)
                text.append (String::charToString (column + 32), 1);
        }
        return text;
    }
    return "";
}

Does that help?

N.B. automating a ramp over the parameter gives a funny matrix like effect :wink:

First: Thanks for coping with me!

I don’t think what I want is too outlandish.
I want to create a LabelAttachment for the AudioProcessorValueTreeState that works very much in line with the existing framework. The Label would show the text that corresponds to the parameter value as defined in AudioProcessorValueTreeState::createAndAddParameter.

here is an example from my current project:

    static const String& tuneChoices(int idx) {
        static const StringArray tuneChoices = { "D/D", "A/E", "C/G" };
        idx = jmin(2, jmax(0, idx));
        return tuneChoices[idx];
    }
    (...)
        createAndAddParameter("instrumentTuning", "instrumentTuning", "Tuning",
            NormalisableRange<float>(0.0f, 2.0f, 1.f), 0.f,
            [](float value) {            // value to text function
            return tuneChoices((int)value);
            },
            [](const String& text) {    // text to value function
            for (int i = 0; i < 3; ++i) {
                if (text == tuneChoices(i)) {
                    return (float)i;
                }
            }
            return 0.f;
            }
        );

This would also be the same text that is published to the plugin host.
like here: (from juce_VST3_Wrapper.cpp)

        void toString (Vst::ParamValue value, Vst::String128 result) const override
        {
            if (AudioProcessorParameter* p = owner.getParameters()[paramIndex])
                toString128 (result, p->getText ((float) value, 128));
            else
                // remain backward-compatible with old JUCE code
                toString128 (result, owner.getParameterText (paramIndex, 128));
        }

        bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override
        {
            if (AudioProcessorParameter* p = owner.getParameters()[paramIndex])
            {
                outValueNormalized = p->getValueForText (getStringFromVstTChars (text));
                return true;
            }

            return false;
        }

To get the desired results I need to access the Parameter::range member as I have pointed out in my last post.
While the getText and getValueForText member functions are not inaccessible they are also not quite usable when the AudioProcessorValueTreeState is used. (because there is no way to access the Parameter::range member)

I simply want to display the string that represents the parameter value as the label text. The user should also be able to insert the a label text to change the parameter value.
Solving this problem will also allow some further nifty additions that I already have in the pipeline:

  • change togglebutton label text according to toggle status.
  • populate a combobox with values produced by the getText function,

I am now going to create a fork of the juce_audio_processors module that will provide the described functionality. (to be released soon)

I would also second a LabelAttachment class. With the valueToText and textToValue functions being available in AudioProcessorValueTreeState::Parameter, there is no problem with converting Strings to Float and vice versa. Additionally, the conversions used by the Slider class could be used as a fallback conversion method, if no lambda’s were specified.

A dirty way of creating a Label with an Attachment would be a Label with a synced, hidden Slider, or even removing the Slider by cropping the component. But that’s only a hack…

I tried to create my own LabelAttachment, however, without access to AttachedControlBase (well, copying the class would dirty-fix this) and especially with AudioProcessorValueTreeState::Parameter being private, it’s impossible without changing JUCE code.

I wonder if this is possible in the meantime and does the Slider Label show the value tree parameters valueToText String?

Yes, that’s absolutely possible thanks to the ParameterAttachment:

juce::Label label;
juce::RangedAudioParameter& parameter;
std::unique_ptr<juce::ParameterAttachment> attachment;

// set up in constructor:
attachment = std::make_unique<juce::ParameterAttachment>
(
    parameter,
    [label](float value)
    {
        auto normalised = parameter.convertTo0to1 (value);
        label.setText (parameter.getText (normalised, 0), 
                       juce::dontSendNotification);
    }
);
label.onTextChange = [&]
{
    auto denormalised = parameter.convertFrom0to1 (parameter.getValueForText (label.getText()));
    attachment.setValueAsCompleteGesture (denormalised);
};
attachment.sendInitialUpdate();

It’s up to you if you wrap this into a reusable struct or write it each time.
But that paradigm allows to connect any unknown class to an AudioParameter.

Haven’t tested that snippet but used it in a similar fashion in my code.

EDIT: just copied it into a project and fixed a few typos

1 Like

Thanks. It looks like this is what i need. I will try it.

And it now also takes the valueToText string from the parameter tree if attached :slight_smile: