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.


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 publicincludes 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.
  • ProcessDoubleReplacingthis is the very core of the plugin. It is in it that the processing of incoming audio is carried out.


The part privatecontains only a type variable doublethat 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 = 0and installing kNumParamsafter it, kNumParamsit becomes the number of plug-in parameters (in this case 1).
The following enumuses 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 0to 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 SetShapesets 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. IKnobMultiControlIs 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). IMutexLockprovides 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 kGainchanges from 0 to 100. So after dividing the value by 100, we assign a value from 0 to 1 privateto 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 ProcessDoubleReplacingis this double** inputs. A sequence of type values doublecan 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];


in1indicates 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 mGainand write it to the output buffer. nFramestells 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

Also popular now: