Creating Audio Plugins, Part 2
- Tutorial
All posts in the series:
Part 1. Introduction and setup
Part 2. Learning code
Part 3. VST and AU
Part 4. Digital distortion
Part 5. Presets and GUI
Part 6. Signal synthesis
Part 7. Receiving MIDI messages
Part 8. Virtual keyboard
Part 9 Envelopes
Part 10. Refinement of the GUI
Part 11. Filter
Part 12. Low-frequency oscillator
Part 13. Redesign
Part 14. Polyphony 1
Part 15. Polyphony 2
Part 16. Antialiasing
Let's take a closer look at our test project. The most important files are resource.h , MyFirstPlugin.h and MyFirstPlugin.cpp . At the moment, the plugin is a simple volume control.
Open resource.h in the navigator . This file contains constants such as the name of the plugin, its version, a unique ID and links to image sources for the GUI. In lines 23-26, you can specify a unique ID:
ID is important for general cataloging of plugins. You can register it here . Lines 56 and further determine the ID and path to the image for the volume knob that you see when you start the plugin:
Find in the navigator and open Resources → img → knob.png . This is a sprite with sixty different pen positions, each 48 by 48 pixels in size. When you start the plugin and turn the knob, the image of the handle does not rotate. The program only shows the corresponding part of this sprite.
Why doesn't it spin? Imagine that the pen has some notches and casts a shadow. Then, during rotation, the shadow will also spin. This does not happen in reality, as you (hopefully) know.
Below in resource.h you can set the size of the plugin window:
Play around with the values and run the plugin.
Open MyFirstPlugin.h . It contains the interface for the plugin class. The part
The part
We pass to the interesting part! Open MyFirstPlugin.cpp . Immediately we see an interesting receiver with the type
By installing
The following
It also determines the number of frames in knob.png equal to 60.
Next, the implementation of the constructor begins. The plugin attributes are set:
In the GUI so far, neither a value nor a percent sign is visible, but the value can vary from
Next, the designer creates a graphic context of the desired size and sets the background red color:
After that, knob.png is loaded , a new IKnobMultiControl with an image is created and attached to the GUI.
Notice how the kKnobFrames parameter is passed to indicate the number of frames in the sprite:
Finally, the designer binds the graphics context and creates a default preset for the plugin:
Take a look at
As we remember, it
So, we have a little disassembled the process of creating a GUI and binding parameters such as
The first parameter passed to the function
For each sample, we take an input value, multiply it by
You may have noticed that when you run the plugin as a standalone application, you can hear yourself from the speakers if the computer has a built-in microphone. This is because, by default, the application uses this microphone as an input source. To change this (and something else), go to preferences :
Set the breakpoint in the function
Please note that you can also call any function from the debugger and see the correct values. Click Stop in the upper left when you look at it and decide to continue.
Now it's time to create a plugin from the code and upload it to the host. This will be the topic of the next post.
In the meantime,
To fill in some of the gaps, I highly recommend reading these slides by the inventive Mr. Oli Larkin. They contain some of the key clarifications about WDL-OL .
Original article:
martin-finke.de/blog/articles/audio-plugins-003-examining-the-code
Part 1. Introduction and setup
Part 2. Learning code
Part 3. VST and AU
Part 4. Digital distortion
Part 5. Presets and GUI
Part 6. Signal synthesis
Part 7. Receiving MIDI messages
Part 8. Virtual keyboard
Part 9 Envelopes
Part 10. Refinement of the GUI
Part 11. Filter
Part 12. Low-frequency oscillator
Part 13. Redesign
Part 14. Polyphony 1
Part 15. Polyphony 2
Part 16. Antialiasing
Let's take a closer look at our test project. The most important files are resource.h , MyFirstPlugin.h and MyFirstPlugin.cpp . At the moment, the plugin is a simple volume control.
Learning code
Constants, flags, and image sources
Open resource.h in the navigator . This file contains constants such as the name of the plugin, its version, a unique ID and links to image sources for the GUI. In lines 23-26, you can specify a unique ID:
// 4 chars, single quotes. At least one capital letter
#define PLUG_UNIQUE_ID 'Ipef'
// make sure this is not the same as BUNDLE_MFR
#define PLUG_MFR_ID 'Acme'
ID is important for general cataloging of plugins. You can register it here . Lines 56 and further determine the ID and path to the image for the volume knob that you see when you start the plugin:
// Unique IDs for each image resource.
#define KNOB_ID 101
// Image resource locations for this plug.
#define KNOB_FN "resources/img/knob.png"
Find in the navigator and open Resources → img → knob.png . This is a sprite with sixty different pen positions, each 48 by 48 pixels in size. When you start the plugin and turn the knob, the image of the handle does not rotate. The program only shows the corresponding part of this sprite.
Why doesn't it spin? Imagine that the pen has some notches and casts a shadow. Then, during rotation, the shadow will also spin. This does not happen in reality, as you (hopefully) know.
Below in resource.h you can set the size of the plugin window:
#define GUI_WIDTH 300
#define GUI_HEIGHT 300
Play around with the values and run the plugin.
Plugin class interface
Open MyFirstPlugin.h . It contains the interface for the plugin class. The part
public
includes a constructor, a destructor, and three member functions of the class:Reset
called when the sampling rate changes.OnParamChange
called when the plugin’s parameters are changed, for example, when we turn the knob.ProcessDoubleReplacing
this is the very core of the plugin. It is in it that the processing of incoming audio is carried out.
The part
private
contains only a type variable double
that stores the current volume value.Implementation
We pass to the interesting part! Open MyFirstPlugin.cpp . Immediately we see an interesting receiver with the type
enum
:enum EParams
{
kGain = 0,
kNumParams
};
By installing
kGain = 0
and installing kNumParams
after it, kNumParams
it becomes the number of plug-in parameters (in this case 1). The following
enum
uses the constants described in resource.h and sets the handle coordinates in the plugin window:enum ELayout
{
kWidth = GUI_WIDTH,
kHeight = GUI_HEIGHT,
kGainX = 100,
kGainY = 100,
kKnobFrames = 60
};
It also determines the number of frames in knob.png equal to 60.
Next, the implementation of the constructor begins. The plugin attributes are set:
//arguments are: name, defaultVal, minVal, maxVal, step, label
GetParam(kGain)->InitDouble("Gain", 50., 0., 100.0, 0.01, "%");
In the GUI so far, neither a value nor a percent sign is visible, but the value can vary from
0
to 100
. The default value is 50
. You may notice that the gradation of values is not uniform on the circle. This is because of SetShape(2.)
. If you replace this with SetShape(1.)
, then the distribution of values will be linear. It SetShape
sets the non-linear behavior. Next, the designer creates a graphic context of the desired size and sets the background red color:
IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight);
pGraphics->AttachPanelBackground(&COLOR_RED);
After that, knob.png is loaded , a new IKnobMultiControl with an image is created and attached to the GUI.
IKnobMultiControl
Is a class for interface handles. Notice how the kKnobFrames parameter is passed to indicate the number of frames in the sprite:
IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, kKnobFrames);
pGraphics->AttachControl(new IKnobMultiControl(this, kGainX, kGainY, kGain, &knob));
Finally, the designer binds the graphics context and creates a default preset for the plugin:
MakeDefaultPreset((char *) "-", kNumPrograms);
Take a look at
OnParamChange
(at the end of the file). IMutexLock
provides streaming security - a concept that we will discuss later. Everything else is just a set of options depending on which parameter changes:case kGain:
mGain = GetParam(kGain)->Value() / 100.;
break;
As we remember, it
kGain
changes from 0 to 100. So after dividing the value by 100, we assign a value from 0 to 1 private
to the class member mGain
. So, we have a little disassembled the process of creating a GUI and binding parameters such as
mGain
. Let's now take a look at how the plugin handles incoming audio. In our case, an audio stream is a sequence of samples represented by a data type double
, each of which contains the signal amplitude at a given point in time. The first parameter passed to the function
ProcessDoubleReplacing
is this double** inputs
. A sequence of type values double
can be passed usingdouble*
. But the plugin processes two channels (stereo) or even more, so we need several sequences of samples, and we communicate this with double**
. The first two lines in the function illustrate this:double* in1 = inputs[0];
double* in2 = inputs[1];
in1
indicates the first sequence of samples (left channel), in2
- samples of the right channel. After performing similar actions for the output buffer, we can iterate over the elements of the input and output buffers:for (int s = 0; s < nFrames; ++s, ++in1, ++in2, ++out1, ++out2)
{
*out1 = *in1 * mGain;
*out2 = *in2 * mGain;
}
For each sample, we take an input value, multiply it by
mGain
and write it to the output buffer. nFrames
tells us how many samples per channel are available so that we know the length of the buffers. You may have noticed that when you run the plugin as a standalone application, you can hear yourself from the speakers if the computer has a built-in microphone. This is because, by default, the application uses this microphone as an input source. To change this (and something else), go to preferences :
Set the breakpoint in the function
Reset
. Change the Sampling Rate on the right and apply the changes. The debugger will interrupt the execution of the code in the function Reset
. At the bottom right, where it works lldb
, Enter print GetSampleRate()
.Please note that you can also call any function from the debugger and see the correct values. Click Stop in the upper left when you look at it and decide to continue.
Now it's time to create a plugin from the code and upload it to the host. This will be the topic of the next post.
In the meantime,
Additional reading
To fill in some of the gaps, I highly recommend reading these slides by the inventive Mr. Oli Larkin. They contain some of the key clarifications about WDL-OL .
Original article:
martin-finke.de/blog/articles/audio-plugins-003-examining-the-code