Skip to content

Silders and Buttons

passivist edited this page Jan 20, 2017 · 4 revisions

Parameters

To add a new parameter we must follow a few steps: We have to declare a pointer to the parameter in "PluginProcessor.h":

AudioParameterFloat* positionParam;

We then have to add the parameter to the PluginAudioProcessor initializer list and create an AudioParameterFloat object in the constructor:

GrrnlrrAudioProcessor::GrrnlrrAudioProcessor() : Thread("scheduling thread"),
                                                 positionParam(nullptr)
{
    addParameter(positionParam = new AudioParameterFloat("pos", "Position", 0.0f, 1.0f, 0.5f));

    time = 0;
    startThread();
}

The constructor of AudioParameterFloat takes these arguments: a parameter ID String, a String for the name, the lowest and the highest value and a default value. We could also use a NormalisableRange<type>(start, end, step, skew) object. This allows us to also set the mid point and step size of our parameter. If we would for example want a slider that had its mid point at 0.25 our code would look like this:

addParameter(positionParam = new AudioParameterFloat("pos", "Position", NormalisableRange<float>(0.0f, 1.0f, 0.01f, 0.25f), 0.5f));

Note that I moved the call to startThread() after the addParameter call. This is necessary because otherwise the parameters would be undefined in the run function, crashing the program.

We can then point to the AudioParameterFloat object when we want to use the value it contains, as we can see in the run() function of the PluginAudioProcessor where we define the variable int startPosition as current value of the AudioParameterFloat object positionParam multiplied by the int numSamples variable:

// [...]
int numSamples = fileBuffer->getAudioSampleBuffer()->getNumSamples();

int onset = 1000;
int length = 44100;

int startPosition = *positionParam * numSamples;
startPosition = wrap(startPosition, 0, numSamples);

float rate = 1;

float envMid = 0.5;
float envSus = 0.5;
float envCurve = -3;

grainStack.add( Grain(time + onset, length, startPosition, envMid, envSus, envCurve, rate) );
// [...]

To test if this is working correctly we can use a handy feature of the JUCE Plugin Host. The host has an option to generate a simple GUI for all the plugins parameters:

show parameters

If we click on 'show all parameters' the GUI should look like this:

parameter display

Now you should have control over the position parameter.

Sliders and Buttons

But we also want to implement our own sliders. The sliders and buttons should be connected to the plugin parameters. It is possible to use the Slider and Button classes provided by JUCE and connect them to the parameters manually, but for the large number of parameters this would be quite tedious. A better way is to write a class that encapsulates both the parameter and the GUI element. We will be defining two classes a button and a slider. These classes will live in a new file "ParameterGUIclasses.h" that we will add to the project. We also have to include the file from the "PluginProcessor.h" file. The classes are adaptations of a ParameterSlider class found in the 'audio plugin demo' example from the JUCE project.

Here are the contents of "ParameterGUIclasses.h":

// [1]
class ParameterSlider : public Slider,
                        private Timer
{
public:

  ParameterSlider (AudioProcessorParameter& p) : Slider (p.getName (256)), param (p)
  {
      setRange (0.0, 1.0, 0.0);
      startTimerHz (60);
      updateSliderPos();
  }

  bool isDragging = false;

  // [2]
  void startedDragging() override     { param.beginChangeGesture(); isDragging = true;  }
  void stoppedDragging() override     { param.endChangeGesture();   isDragging = false; }

  // [3]
  void timerCallback() override       { updateSliderPos(); }

  // [4]
  void updateSliderPos()
  {
    const float newValue = param.getValue();

    if (newValue != (float) Slider::getValue())
        Slider::setValue (newValue);
  }
  // [5]
  void valueChanged() override
  {
      if(isDragging){
          param.setValueNotifyingHost((float) Slider::getValue());
      } else {
          param.setValue((float) Slider::getValue());
      }

  }

  double getValueFromText (const String& text) override   { return param.getValueForText (text); }
    String getTextFromValue (double value) override         { return param.getText ((float) value, 1024); }

  AudioProcessorParameter& param;

  JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterSlider)
};
  1. The ParameterSlider class inherits from both Slider and Timer. The constructor takes a reference to an AudioProcessorParameter as an argument and initializes a new Slider Object with the name of the parameter, as well as the param field of the class. The number 256 in the call p.getName(256) means that the string holding the name cannot be longer than 256 characters. In the body of the constructor the default range of the slider is set. Also the timer is started with a frequency of 60hz that means the function timerCallback will be called once every 60th of a second. Lastly there is a call to updateSliderPos which sets the initial position of the slider.

  2. These two functions are methods of Slider used to determine if the user has started or stopped dragging the slider with the mouse. We use it here to tell the host program that we are currently changing the value and it can't change it at this moment. We also set a flag in our object whether or not we are dragging.

  3. This is the callback of the Timer object. It will call updateSliderPos each time so in effect the position of the slider is updated 60 times a second.

  4. In this function we update the position of the slider. If the value of the parameter is different than the value of the slider we set the value of the slider so that it is the value of the parameter. This allows us to change the position of the slider without dragging it and will enable visual feedback when automating parameters in our host program or restoring values from presets.

  5. This is the callback from the Slider class. It handles the changing of values by the user. It's the function we first check if the user is dragging the slider. If this is the case we change the value of the parameter to that of the slider and also send a message to the host program that we just changed that value. If the user isn't changing the value that means that the host program must be changing the value in this case we set the value of the parameter without notifying the host to avoid an endless loop.

For the parameterButton class we basically do the same thing but using the Button class instead of Slider:

class ParameterButton : public TextButton,
                        private Timer
{
public:
    ParameterButton (AudioProcessorParameter& p) : TextButton (p.getName (256)), param (p)
    {
        updateButton();
        startTimerHz (60);
    }

    void timerCallback() override { updateButton(); }

    void updateButton()
    {
        const bool newValue = param.getValue();

        if (newValue != TextButton::getToggleState())
            TextButton::setToggleState(newValue, sendNotification);
    }

    void clicked() override
    {
        param.setValueNotifyingHost(TextButton::getToggleState());
    }

    AudioProcessorParameter& param;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterButton)

};

Using the GUI elements

Similar to parameters themselves we must follow a number of steps to add the sliders to our plugin. We need to add an inheritance to Slider::Listener to our AudioPluginEditor class and override its SliderValueChanged function. Afterwards the plugin should compile and we can add our slider to the constructor of AudioPluginEditor:

addAndMakeVisible(positionSlider = new ParameterSlider (*p.positionParam));
positionSlider->setSliderStyle(Slider::LinearBar);
positionSlider->setTextBoxStyle(Slider::NoTextBox, false, 0, 0);

We also need to set a position for the slider in the resize function of AudioPluginEditor:

void GrrnlrrAudioProcessorEditor::resized()
{
    const int width = getWidth();

    openButton.setBounds(10, 10, 120, 20);
    positionSlider->setBounds(10, 40, width - 30, 20);
}

If everything is set up correctly your plugin should look like this and the position should be controllable with the slider and from your host application:

position slider

Randomizaton

Next we want to add a parameter and GUI element for adding a random element to the grain position. For this we must first add another parameter to the plugin:

addParameter(randPosParam = new AudioParameterFloat("randPos", "Random Position", NormalisableRange<float>(0.0, 1.0, 0.01, 0.5), 0.0f));

We then add the parameter to the calculation of our position in the run function:

// [...]
float randPosition = *randPosParam * (Random::getSystemRandom().nextFloat() - 0.5);
int startPosition = (*positionParam + randPosition) * numSamples;
startPosition = wrap(startPosition, 0, numSamples);
// [...]

Here we first get a random floating point value between 0 and 1 from a JUCE function. We then subtract 0.5 so that the random value will now be between -0.5 and 0.5. Lastly we scale the value with the randPosParam so we can control the amount of randomness added to the position.

After we have checked if everything is working we will add a knob to the GUI to control the parameter. We will also provide a label this time:

// [...]
// Position:
addAndMakeVisible(positionSlider = new ParameterSlider (*p.positionParam));
positionSlider->setSliderStyle(Slider::LinearBar);
positionSlider->setTextBoxStyle(Slider::NoTextBox, false, 0, 0);

addAndMakeVisible(randPosLabel);
randPosLabel.setText("Rand Pos", dontSendNotification);

addAndMakeVisible(randPosSlider = new ParameterSlider (*p.randPosParam));
randPosSlider->setSliderStyle(Slider::RotaryHorizontalVerticalDrag);
randPosSlider->setTextBoxStyle(Slider::TextBoxBelow, false, 80, 20);
// [...]

Here we can see some differences in how we can define GUI elements. The Slider class has a number of functions for setting its look and behavior. Where we set positionSlider to have the look LinearBar we set randPosSlider to be a rotary control that responds to both horizontal and vertical dragging. The Label class has a set of different functions for defining the look. I recommend having a look at the source of these classes to find our what settings are available.

Here is how the plugin should look now:

randPosSlider

Before you progress to the final chapter you should add controls for the remaining parameters.

<<< last Chapter next Chapter >>>

Clone this wiki locally