-
Notifications
You must be signed in to change notification settings - Fork 12
Silders and Buttons
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:
If we click on 'show all parameters' the GUI should look like this:
Now you should have control over the position parameter.
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)
};
-
The
ParameterSlider
class inherits from bothSlider
andTimer
. The constructor takes a reference to anAudioProcessorParameter
as an argument and initializes a newSlider
Object with the name of the parameter, as well as theparam
field of the class. The number 256 in the callp.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 functiontimerCallback
will be called once every 60th of a second. Lastly there is a call toupdateSliderPos
which sets the initial position of the slider. -
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. -
This is the callback of the
Timer
object. It will callupdateSliderPos
each time so in effect the position of the slider is updated 60 times a second. -
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.
-
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)
};
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:
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:
Before you progress to the final chapter you should add controls for the remaining parameters.