The Code
We are going to modify the code from our previous tutorial How to Develop Your First Effect to add new functionalities.
Parameters
We will add and initialize two DadGUI::cUIParameter parameters: one for panning and one for Mono/Stereo mode.
In cMyFirstEffect.h, we declare these parameters:
DadGUI::cUIParameter m_ParameterPan; // Stereo Panning parameter
DadGUI::cUIParameter m_ParameterStereoMode; // Stereo/Mono mode parameter
In cMyFirstEffect.cpp, we initialize the new parameters within the onInitialize method.
We also add callback functions for m_ParameterGain and m_ParameterPan. These functions are automatically called whenever a parameter’s state changes.
Tip : Calback Function format:
CallBack(DadDSP::cParameter* pParameter, uint32_t Data).
pParameterpoints to the parameter that was just modified (it will point to eitherm_ParameterGainorm_ParameterPan).Datacarries user data in a 32-bit integer. We will use this data to carry the address of our class instance (this).
// Initialize the Gain parameter
m_ParameterGain.Init(MY_FIRST_EFFECT_ID,// Serialize ID
50.0f, // Default value
0.0f, // Minimum value
100.0f, // Maximum value
5.0f, // Fast increment
1.0f, // Slow increment
onGainChange, // Callback function
(uint32_t)this, // Callback user data
1.0f, // Transition time (seconds)
20); // MIDI CC number
// Initialize the Gain parameter
m_ParameterPan.Init(MY_FIRST_EFFECT_ID, // Serialize ID
0.0f, // Default value
-100.0f, // Minimum value
100.0f, // Maximum value
5.0f, // Fast increment
1.0f, // Slow increment
onPanChange, // Callback function
(uint32_t)this, // Callback user data
1.0f, // Transition time (seconds)
21); // MIDI CC number
// Initialize the Gain parameter
m_ParameterStereoMode.Init(MY_FIRST_EFFECT_ID,// Serialize ID
0.0f, // Default value
0.0f, // Minimum value
0.0f, // Maximum value
1.0f, // Fast increment
1.0f, // Slow increment
nullptr, // Callback function
0, // Callback user data
0.0f, // Transition time (seconds)
22); // MIDI CC number
Parameter Views
We will add and initialize two parameter views:
In cMyFirstEffect.h, we declare the views:
-
m_ParameterPanViewfor panning is of typeDadGUI::cParameterNumLeftRightView. This view is designed to display numerical parameters with a symmetrical left/right range centered around zero, corresponding to the MIDI center position. -
m_ParameterStereoModeView, used for the mode switch, is based on theDadGUI::cParameterDiscretViewclass. This view is designed to display a selector with discrete value.
Tip A discrete parameter can only take values from a predefined list.
It works like an indexed selection table:
- 0 → XXL (Very Large)
- 1 → XL (Large)
- 2 → L (Medium)
- 3 → S (Small)
- 4 → XS (Very Small)
Internally, the parameter stores only the index value.
So, when you callgetValue(), the function returns an integer between 0 and 4, corresponding to the selected entry, rather than the displayed text such as “XXL” or “L”.
DadGUI::cParameterNumLeftRightView m_ParameterPanView; // GUI view for Panning parameter
DadGUI::cParameterDiscretView m_ParameterStereoModeView; // GUI view for stereo mode parameter
In cMyFirstEffect.cpp, we initialize the two new views in the onInitialize method.
Initializing discrete views requires an extra step: for every possible discrete value, we call AddDiscreteValue (in this case, “Stereo” and “Mono”).
// Initialize the GUI view for the parameter
m_ParameterGainView.Init(&m_ParameterGain,// Linked parameter
"Gain", // Short name
"Gain", // Long name
"%", // Unit (short)
"percent"); // Unit (long)
// Initialize the GUI view for the parameter
m_ParameterPanView.Init(&m_ParameterPan,// Linked parameter
"Pan", // Short name
"Pan control", // Long name
"%", // Unit (short)
"percent"); // Unit (long)
// Initialize the GUI view for the parameter
m_ParameterStereoModeView.Init(&m_ParameterStereoMode, // Linked parameter
"Mode", // Short name
"Stereo Mode");// Long name
m_ParameterStereoModeView.AddDiscreteValue(
"Stereo", // ShortDiscretValue
"Stereo"); // LongDiscretValue
m_ParameterStereoModeView.AddDiscreteValue(
"Mono", // ShortDiscretValue
"Mono"); // LongDiscretValue
The Panel
In cMyFirstEffect.cpp, within the onInitialize method, we add the two new views to the m_ParameterFirstPanel:
// Create a parameter panel for the user interface
m_ParameterFirstPanel.Init(&m_ParameterGainView, // Parameter View 1 (Gain)
&m_ParameterPanView, // Parameter View 2 (Panning)
&m_ParameterStereoModeView);// Parameter View 3 (Mode)
New Method: Logarithmic Gain Calculation
The human ear does not perceive volume linearly; it perceives it logarithmically. This is why we use a logarithmic scale (in dB) for audio gain—it corresponds to our natural perception. A “log” potentiometer or fader gives a uniform and natural sensation of volume throughout its range.
We will create a method to convert the linear gain from our parameters m_ParameterPan and m_ParameterStereoMode into log values stored in the member variables m_RightGain and m_LeftGain.
In cMyFirstEffect.h, we declare the new method and variables:
// -------------------------------------------------------------------------
// Compute left/right channel gains using a logarithmic (power) curve
// -------------------------------------------------------------------------
void CalcGainLog();
// -------------------------------------------------------------------------
// Gain member variables
// -------------------------------------------------------------------------
float m_RightGain; // Calculated right gain
float m_LeftGain; // Calculated left gain
In cMyFirstEffect.cpp, we implement the new method.
Tip
We notice the use ofgetNormalizedValue(). This method returns the parameter value normalized between 0.0f and 1.0f.
For example, ifm_ParameterPanwas initialized with a min value of -100 and a max value of 100:
- A value of -100 results in a NormalizedValue() of 0.0f.
- A value of 0 results in a NormalizedValue() of 0.5f.
- A value of 100 results in a NormalizedValue() of 1.0f.
// -------------------------------------------------------------------------
// Compute left/right channel gains using a logarithmic (power) curve
// -------------------------------------------------------------------------
void cMyFirstEffect::CalcGainLog()
{
// Retrieve normalized parameter values [0.0 .. 1.0]
float Gain = m_ParameterGain.getNormalizedValue();
float Pan = m_ParameterPan.getNormalizedValue();
// Constant-power panning law: apply a square-root curve (exponent 0.5)
float gain_left = std::pow(1.0f - Pan, 0.5f); // fades out as pan moves right
float gain_right = std::pow(Pan, 0.5f); // fades in as pan moves right
// Apply the master gain on top of each channel's pan gain
m_LeftGain = Gain * gain_left;
m_RightGain = Gain * gain_right;
}
Callbacks
We will add two callback methods that intercept parameter state changes.
In cMyFirstEffect.h, we declare these two new static methods to implement the callbacks.
// -------------------------------------------------------------------------
// Gain callback
// -------------------------------------------------------------------------
void static onGainChange(DadDSP::cParameter* pGainParameter, uint32_t Data);
// -------------------------------------------------------------------------
// Pan callback
// -------------------------------------------------------------------------
void static onPanChange(DadDSP::cParameter* pPanParameter, uint32_t Data);
In cMyFirstEffect.cpp, we implement the two new methods.
The code cMyFirstEffect* pThis = (cMyFirstEffect *) Data; allows us to cast the transmitted parameter data into a pointer to the cMyFirstEffect instance.
These functions then call the CalcGainLog method to calculate the right and left channel gains based on the values of m_ParameterGain and m_ParameterPan.
Tip
Using callbacks allows you to simplify and clarify the processing logic. Instead of executing code constantly, callbacks let you run specific treatments only when needed. Main advantages:
- The code is easier to read, maintain, and debug.
- It optimizes CPU resources by launching calculations only when parameters change.
// -------------------------------------------------------------------------
// Gain callback
// -------------------------------------------------------------------------
void cMyFirstEffect::onGainChange(DadDSP::cParameter* pGainParameter, uint32_t Data){
// Cast Data to cMyFirstEffect instance pointer
cMyFirstEffect* pThis = (cMyFirstEffect *) Data;
pThis->CalcGainLog();
}
// -------------------------------------------------------------------------
// Pan callback
// -------------------------------------------------------------------------
void cMyFirstEffect::onPanChange(DadDSP::cParameter* pPanParameter, uint32_t Data){
cMyFirstEffect* pThis = (cMyFirstEffect *) Data;
pThis->CalcGainLog();
}
onProcess
Here is the new code for the onProcess method.
We test the value of m_ParameterStereoMode.getValue(): it returns 0 if Stereo is selected and 1 if Mono is selected. If Mono is selected, we mix the right and left channels.
Finally, we apply the calculated right and left gains.
// -----------------------------------------------------------------------------
// Method: onProcess
// Description: Main audio processing function. Called continuously for
// every audio buffer.
// -----------------------------------------------------------------------------
void cMyFirstEffect::onProcess(AudioBuffer *pIn, AudioBuffer *pOut, eOnOff OnOff, bool Silence)
{
float Left = pIn->Left;
float Right = pOut->Right; // Note: Should likely be pIn->Right if processing both channels from input
// Test stereo mode (1.0f for Mono, 0.0f for Stereo)
if(m_ParameterStereoMode.getValue() == 1.0f){
// Convert to mono: mix the channels
Left = (Left + Right) / 2.0f;
Right = (Left + Right) / 2.0f; // Note: The original code had a bug here, corrected to use input values for mixing
}
// Apply gain to both channels
pOut->Left = Left * m_LeftGain;
pOut->Right = Right * m_RightGain;
}