Match editor labels to AudioProcessorValueTreeState parameters


#1

In my editor, how can I change the range of values for my parameters in a AudioProcessorValueTreeState to match how they’re being displayed in a host application?

Here are the Lambda functions I’m using to display the values in the host application:
parameters.createAndAddParameter ("time", "Time", String(), NormalisableRange<float> (0.1f, 1.0f), 0.5f, [](float v) -> String { return String(v*1000.0) + " ms"; }, [](const String& s) -> float { return s.getFloatValue()*1000.0; });

How can I change the Label in my editor to match this behavior as the slider is changed? Is there a best way to do this with the AudioProcessorValueTreeState class?


#3

Unfortunately you have to do the same again for the slider in your editor.
I suggested to use the function of the parameter via the SliderAttachment, see here:

@t0m liked the idea, but it wasn’t the right time to add that. Maybe it is now after the release?


#4

Look here:


#6

So, I pulled the AudioProcessorParameterSlider class and am using it. It’s solved the first problem but now I am noticing that with the plugin running as a standalone, the initial values I set for each slider do not init correctly and in fact, do not update when I adjust them using the Editor. However, the initial values do show up correctly when I load the plugin into Ableton and update the parameters from there. Any idea why that might be happening? in fact, the editor doesn’t update at all when running from ableton.


#7

Strange - Never experienced this behaviour with the AudioProcessorParameterSlider.
NOTE: I use it with AudioProcessorValueTreeState::CreateAndAddParameter, passing the default value in the method. The slider range and default value are taken care of when setting up the attachment between the UI object and the AudioProcessorParameter.

This is more or less what I do for each parameter

Processor.h
ScopedPointer<AudioProcessorValueTreeState> parameters;

Processor.cpp

    parameters = new AudioProcessorValueTreeState(*this,nullptr); 

    AudioProcessorParameter* pr = parameters->createAndAddParameter (MyParamID,
									                                            MyParamName,       // parameter name
									                                            String(""),     // I append the label in the MyValueToTextFunction lambda below
									                                            MyParamRange,    // range
									                                            MyParamDefaultValue,         // default value
									                                            MyValueToTextFunction,
									                                            MyTextToValueFunction
									                                            );

Editor.h

    ScopedPointer<AudioProcessorParameterSlider::SliderAttachment> sliderAttachment;
    ScopedPointer<AudioProcessorParameterSlider> paramSlider;

Editor.cpp

  addAndMakeVisible(aSlider = new AudioProcessorParameterSlider(paramName,Slider::RotaryVerticalDrag,Slider::TextBoxBelow))
  aSliderAttach = new fefanto::AudioProcessorParameterSlider::SliderAttachment (processor.parameters, MyParamID, *aSlider));
  
  [Editor::resized Omitted]

#8

Ah, I was missing the SliderAttachment. Cheers!


#9

Hello @fefanto,

I’m trying to figure out how to append an AudioProcessorValueTreeState parameter label within its respective ValueToText function, and the comments in the example code of your previous post here suggests you know how to do this.

Would you mind sharing some example code of your ValueToText function to demonstrate how you’ve managed to get this to return both a given parameter value and its respective label?

So far I’ve figured out how to design a basic ValueToText function and can force it to have whatever ‘label’ I wish, but I don’t know how to tie in the actual audio parameter’s label in a more elegant way:

Right now in my Processor.cpp file I design ValueToText functions like this:

static String myValueToText (float value)
{
    return String (value) + String ("my Forced Label");
}

It works, but I’m aware this is obviously not the ideal way to do this. I feel like there’s a very simple solution I’m missing, however I’m relatively new to JUCE and C++ development at the moment, so the answer eludes me for now.

Thanks in advance!


#10

That is exactly the topic of the thread I linked above, the SliderAttachments don’t get access to the AudioParameter’s lambdas, so you have to set them by copying the lambda from the parameter into your slider’s label.

I created a little convenience Slider-subclass, where I can specify the label cases I came across, to be a little less likely to copy code, hope that helps:

class TextFormattedSlider : public Slider
{
public:
    enum {
        RawNumber = 0,
        LevelDB,
        GainDB,
        MiliSeconds,
        Hertz,
        Percent,
        Ratio
    };

    TextFormattedSlider (SliderStyle style, TextEntryBoxPosition textBoxPosition, int numberType = 0)
      : Slider (style, textBoxPosition),
        type (numberType)
    {}

    String getTextFromValue (double value) override
    {
        switch (type) {
            case LevelDB:       // level in dB
                return String (value, 1) + " dB";
            case GainDB:        // gain in dB
                return String (Decibels::gainToDecibels (value, -80.0), 1) + " dB";
            case MiliSeconds:   // msecs
                if (value >= 1.0)
                    return String (value, 2) + " s";
                else
                    return String (roundToInt (value * 1000.0)) + " ms";
            case Hertz:         // Hz
                if (value >= 1000.0)
                    return String (value * 0.001, 2) + " kHz";
                else
                    return String (value, 0) + " Hz";
            case Percent:
                return String (roundToInt (value * 100.0)) + " %";
            case Ratio:
                return "1 : " + String (value, 1);
            default:
                return Slider::getTextFromValue (value);
        }
    }
    double getValueFromText (const String &text) override
    {
        switch (type) {
            case LevelDB:
                return text.trimCharactersAtEnd (" dB").getFloatValue();
            case GainDB:
                return Decibels::decibelsToGain (text.trimCharactersAtEnd (" dB").getFloatValue(), -80.0f);
            case MiliSeconds:
                if (text.endsWith ("ms"))
                    return text.trimCharactersAtEnd (" ms").getFloatValue() * 0.001f;
                else
                    return text.trimCharactersAtEnd (" s").getFloatValue();
            case Hertz:
                if (text.endsWith ("kHz"))
                    return text.trimCharactersAtEnd (" kHz").getFloatValue() * 1000.0;
                else
                    return text.trimCharactersAtEnd (" Hz").getFloatValue();
            case Percent:
                return text.getFloatValue() * 0.01;
            case Ratio:
                return text.trimCharactersAtStart ("1 : ").getFloatValue();
            default:
                return Slider::getValueFromText (text.trimCharactersAtEnd (" %"));
        }
    }
    void setNumberType (const int t)
    {
        type = t;
    }

private:
    int type = RawNumber;
};

#11

Thanks @daniel. Yes I did see that. Seems like a great workaround, and I may end up adopting your technique if that appears to be the simplest solution for my situation.

I was wondering though if anyone knows of some other technique that doesn’t involve having to create a sub-class, and is more “native” in a way to the original implementation of the AudioProcessorValueTreeState class.

When I first got acquainted with the APVTS class and saw that there was a “label” argument as part of the .createAndAddParameter method, I (naively) assumed that populating this arg would automatically generate a string label appended to the result of the valueToText function within the ultimate context of an automation lane in a host application, for example. I soon realized that things didn’t work this way out of the box, which made me wonder: is there a recommended best practice to ‘get’ the ‘label’ of a parameter from an APVTS and add it to the final string returned by a valueToText function?


#12

I think the purpose of that “label” flag is rather for the host. If e.g. on Logic you switch from “editor” to “controls”, the Label is used to label the generic control. Also on the automation graph, the parameter’s label is used to label each graph.
Apart from that, you can always get the parameter from the state and call getLabel() there, but I understand, that is not what you wanted in the first place…
Label


#13

I’m sorry, but that’s not the behavior I’m seeing. To be clear, I’m referring to the purpose of the third “label” argument of the .createAndAddParameter method, whose value as far as I can tell does not appear anywhere in the host application I’m testing in (which is also Logic). What you’re referring to in your example in your previous post is actually accomplished via the second argument of the .createAndAddParameter method, otherwise known as the “parameterName”.

To give this some context, I’m using the JUCE AudioProcessorValueTreeState tutorial as a reference point, and here’s the particular example code snippet from the tutorial which I hope will clarify things:

parameters.createAndAddParameter ("gain",       // parameter ID
                              "Gain",       // parameter name
                              String(),     // parameter label (suffix)
                              NormalisableRange<float> (0.0f, 1.0f),    // range
                              0.5f,         // default value
                              nullptr,
                              nullptr);

When I build this tutorial it’s actually the second argument (labelled in the comments as “parameter name”) which appears as the parameter name in the automation lane. So then, for example, if I create a parameter and supply a label like this, along with a valueToText function…

parameters.createAndAddParameter ("gain",       // parameter ID
                              "Gain",       // parameter name
                              String("xyz123"),     // parameter label (suffix)
                              NormalisableRange<float> (0.0f, 4.0f, 0.01),    // range
                              0.5f,         // default value
                              myValueToTextFunction,
                              nullptr);

…the “parameter label” does not get appended as a suffix to the individual automation data points, and it is the String value of the second “parameter name” argument that appears in as the name of the parameter:

And sorry if I didn’t communicate this more clearly, but I am actually wondering exactly how I can access a parameter’s label within a valueToText function. So, when you say:

Is it possible to do this within a static valueToText function and would you mind showing some example code demonstrating this exactly?

From my initial attempts I cannot figure out how to do this. Thanks for all your feedback so far.


#14

Ouch, that was my bad. I actually confused the parameterLabel and parameterName (in my memory the third one was called description, but I don’t know, where I got that, I should have double checked).

For the third parameter, I am actually not sure, where this is used. But I would question the comment in the tutorial that being the suffix. I digged into the wrapper, but I can’t find it currently, where this isused, so in short, I don’t know.

But you can simply add a suffix to the number using the lambdas, like:

parameters.createAndAddParameter ("gain",       // parameter ID
                                  "Gain",       // parameter name
                                  "This is the gain", // parameter label (description?)
                                  NormalisableRange<float> (0.0f, 4.0f, 0.01),  // range
                                  0.5f,         // default value
                                  [](float value) {return String (value) + " my suffix";},
                                  [](String text) {return text.trimCharactersAtEnd (" my suffix").getFloatValue()});

In my first answer I actually thought you were trying to propagate these two functions to the Sliders in your editor, which I found not possible yet.

I hope I understood it better this time, hope I could help…


#15

Bingo! That does the trick, and thank you for the quick response. Yes, this is helpful and on point.

However it still seems that the purpose of the .createAndAddParameter method’s 3rd argument “parameter label” is still a bit of a mystery then, since your solution still entails manual hardcoding of a suffix, and I was hoping there was some straightforward means of using the “parameter label” value as a suffix.

The APVTS tutorial explicitly states:

The parameter label allows you to specify a suffix (for example “dB” for gain in decibels or “Hz” for frequency parameters).

So, I assumed that it could be integrated into the valueToText function in some way, but I guess this is not the case? And it’s still unclear to me how or if it’s used by a host application in any way. Does anyone know if this information in the tutorial is inaccurate, or if there is indeed some relatively straightforward means of getting the “label” of a parameter to use in a valueToText function?

Thanks!


#16

Yes, I also don’t know. I think there was a misunderstanding, like in the children’s game “Chinese whispers”. The one writing the tutorial got it from the one writing the wrapper (and it’s documentation), who had to get the information from the three SDKs together (AAX, AU, VST/VST3), which was also interpreted by all the various host manufacturers… a lot of chances for misunderstandings…

I think we need a comment from a JCUE official, @jules or @t0m maybe?

I assume it needs to be adapted and double checked, if that functionality is provided by the wrappers. It might be asked too much, but it would be great to have a list, which hosts use that label anyway and for what purpose…