📜 ⬆️ ⬇️

Creating audio plug-ins, part 2

All posts 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. Receive MIDI Messages
Part 8. Virtual Keyboard
Part 9. Envelopes
Part 10. Refinement 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 in resource.h navigator. This file contains such constants as the name of the plug-in, its version, unique ID, and links to image sources for the GUI. In lines 23-26, you can register 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 plug-in cataloging. You can register it here . Lines 56 and further define the ID and the path to the image for the volume knob that you see when launching the plug-in:

 // 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 handle positions, each 48 by 48 pixels in size. When you launch the plugin and twist the knob, the knob image does not rotate. The program only shows the corresponding part of this sprite.
Why doesn't it rotate? Imagine that the pen has some notches and casts a shadow. Then, while rotating, the shadow will also spin. This does not really happen, 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 public part includes a constructor, a destructor, and three member functions of the class:



In the private part there is only a double variable that stores the current volume value.

Implementation



Go to the interesting part! Open MyFirstPlugin.cpp . We immediately see an interesting trick with the enum type:

 enum EParams { kGain = 0, kNumParams }; 


By setting kGain = 0 and putting kNumParams after it, kNumParams becomes the number of parameters for the plugin (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 defines the number of frames in knob.png equal to 60.
Next begins the implementation of the constructor. The attributes of the plugin are set:

 //arguments are: name, defaultVal, minVal, maxVal, step, label GetParam(kGain)->InitDouble("Gain", 50., 0., 100.0, 0.01, "%"); 


In the GUI, neither the value nor the percent icon is visible yet, 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 this is replaced with SetShape(1.) , Then the distribution of values ​​will be linear. SetShape sets non-linear behavior.
Next, the designer creates the graphic context of the desired size and sets the background color to red:

 IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachPanelBackground(&COLOR_RED); 


After that, the knob.png is loaded, a new IKnobMultiControl is created with the image and tied 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 constructor binds the graphics context and creates a default preset for the plugin:

 MakeDefaultPreset((char *) "-", kNumPrograms); 


OnParamChange look at OnParamChange (at the end of the file). IMutexLock provides streaming security - a concept that we will examine later. Everything else is just a set of options depending on which parameter is changed:

 case kGain: mGain = GetParam(kGain)->Value() / 100.; break; 


As we remember, kGain changes from 0 to 100. So after dividing the value by 100, we assign the value from 0 to 1 to a private member of the mGain class.

So, we have a little dismantled the process of creating a GUI and binding to it parameters such as mGain . Let's now take a look at how the plugin handles incoming audio. In our case, the audio stream is a sequence of samples represented by a double data type, each of which contains the signal amplitude value at a given point in time.
The first parameter passed to the ProcessDoubleReplacing function is double** inputs . You can pass a sequence of double using double* . But the plugin processes two channels (stereo) or even more, so we need several sequences of samples, and we report 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 indicates the 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 the input value, multiply it by mGain and write it to the output buffer. nFrames tells us how many samples per channel there are, 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 due to the fact that by default the application uses this microphone as an input source. To change this (and something else), go to the preferences :





Set breakpoint in Reset function. Change the Sampling Rate on the right and apply the changes. The debugger will abort the execution of the code in the Reset function. Bottom right where lldb works. Enter print GetSampleRate() .



Note that you can also call any function from the debugger and see the correct values. Click Stop at the top left when you look at it and decide to continue.
Now it's time to create a plugin from code and upload it to the host. This will be the topic of the next post.
For now

Additional reading



To fill in some of the gaps, I highly recommend reading these slides by the ingenious Mr. Oli Larkin. They contain some of the key explanations about the WDL-OL .

Original article:
martin-finke.de/blog/articles/audio-plugins-003-examining-the-code

Source: https://habr.com/ru/post/225019/


All Articles