In Part 2, we created a fully functioning VST2/VST3 plugin whose sole action was to remove 3dB of headroom to the input signal (no UI). Part 3 focuses on adding a UI with a control to actually do the primary job of the A/B switch: switching between input A and input B.
Setup
This project is available on github: vst-ab-switch. Make sure you checkout the blog-part3-369
tag in order to follow along with this part (or browse the code directly on github)
cmake
directly as explained in Part 1.All the source files are located under src/cpp
Creating the UI
As described in Part 2, using the editorhost
app (or deploying the plugin in an actual DAW), you can right click on the background to open the editor. The documentation that comes with the SDK describes the editor a little bit.
Changing the background color
The top right area shows the parameters that you can edit for the “view” that is selected in the top left area (called Editor). To change the background color (of the only current view), simply click on the background-color
selection box. I selected ~BlueCColor~
to make it different from the default.
Adding simple text
The bottom right section has multiple tabs. The first one is called Views
and lists all the views that the editor knows about. Each view corresponds to a C++ class (feel free to look at the source code of the SDK to see what each class does, although the comments/documentation is quite limited). You then drag and drop a view from this list into the editor. I dragged the one called Label
(C++ class being CTextLabel
) and changed some properties like the title, background color, etc… to create the A/B Switch title.
If you want to change the font to one that is not listed, you need to go into the Fonts
tab in the bottom right section and add the font there. It will then appear in the dropdown selection.
Adding the switch/toggle
To add the toggle, I used the OnOff Button
view (COnOffButton
) and resized it to 50×50. An on/off button is a view that has 2 states which can get toggled by clicking on it.
In order for the button to have an image that will change when the toggle is clicked, you need to:
- create one image that contains the 2 frames on top of each other. Or in other words, the image is twice as tall (see the file
resource/Control_AudioSwitch.png
).
- on the
Bitmaps
tab of the bottom right panel, click the+
and select the image - now in the parameters area, you can now select the image you just added, in the dropdown of the
bitmap
entry.
Control_AudioSwitch_50x100.png
ended up being treated as a 50x scale.If you exit the Editing mode (which you can do by clicking on the x
next to Editing
in the menu bar), you can now click on the image and it changes to say In A
or In B
.
Saving
Once you are done, you save the new UI by clicking File/Save As...
. The file needs to be saved under the resource
folder and to match what the C++ code expects (see ABSwitchController
constructor): it should be called ABSwitch.uidesc
. The image(s) also need to be under the resource
folder.
ABSwitch.uidesc
) checked in.The file contains a <custom>...</custom>
section which represents the state of the editor and can be safely removed (although it is probably good to keep it while you are working on the UI).
Bonus: automatic scaling
Based on the helloworld sample that comes with the SDK (and the bug I found), I realized that if you generate an image called Control_AudioSwitch_2x.png
and add it to the Bitmaps
section, the UI will automatically use it on HiDPI screen like retina displays!
Adding a parameter to the code
Now that we have a UI, we need to tie the UI to the code so that toggling the switch actually does something.
Declaring the parameter in the controller
The controller (ABSwitchController
) declares the parameter we just created in the initialize
method:
parameters
is a field that comes from theEditController
class that the controller inherits from and is used to declare/register a parameter- a
stepCount
of 1 indicates it is a toggle - the
defaultNormalizedValue
specifies the original value of the parameter, which we set to 0 (which represents Input A) - the
tag
(defined inABSwitchCIDs.h
) is very important as it is the value that ties all the pieces together (the value comes from an enum and is set to1000
). Note that it is unclear to me what the actual allowed range for this tag is. Can it start at 0 for example? The helloworld sample code uses 500 and 1000… so I used a similar value. - I have no idea how short the short title is supposed to be (could not find an obvious explanation)
In order for the controller to be able to restore its state (for example after loading a project which contains this plugin), the setComponentState
method needs to be implemented:
The state is provided as an IBStream
and using the helper class IBStreamer
you can read what was previously saved (see processor). Note how the value read from the stream is assigned to the proper parameter by using the setParamNormalized
and tag previously used during registration!
Adding the parameter to the UI
In the UI editor, you now add a tag in the Tags
tab of the bottom right area. I called it Param_AudioSwitch
with a value of 1000 (which is the tag defined in the controller!).
Then the on/off button view needs to be edited to select this tag in the control-tag
entry.
Don’t forget to save the UI.
Handling the parameter in the processor
Quite surprisingly, you don’t register/declare the parameter in the processor. You just use it. That being said, the parameter is usually represented by an actual concept in the processor code. In this case, the field fSwitchState
(whose type is backed up by an enum class for clarity) represents which input is currently used.
Persistence
Similarly to the controller, you need to implement a couple of methods to read and write the parameter to handle persistence (for example, loading/saving a project).
This method is very similar to the one implemented in the controller with the difference that it saves the parameter value in the local fSwitchState
field.
This method is the one that generates the stream in the first place. In this case the streamer is used to write the content of the local fSwitchState
field to the stream.
Updates
Finally, the last remaining piece of the puzzle is how the processor gets notified of changes in the UI (or through automation). Every time the process
method is called, the ProcessData
argument contains an entry referencing all the parameters that have changed since the last call to process
(for clarity I have extracted this step into a separate method).
- the outer loop iterates over every change (in this case there should be at most 1)
- the code is a little bit complicated because you do not get just a single value but potentially multiple values: every call to
process
handles several samples and the parameter may actually change at different points in time during this window. The “Parameters and Automation” section in the VST SDK documentation is actually pretty good at describing what could happen (check the diagram at the bottom of the page!). - for our case, we are simplifying and simply taking the last known value in the interval for our value
- note how the
switch
statement determines which parameter we are talking about
Use
Now that we know how the parameter gets persisted and updated, we can simply use it. After all this, the actual value is stored in the fSwitchState
field, ready to be used in the rest of the code.
In the initialize
method we register another stereo audio input (B):
The logic is now almost trivial!
The fSwitchState
field is used to determine which stereo input we should use. The rest of the logic simply copies the input samples to the output stereo pair (removed the gain section from Part 2) using memcpy
for each channel (left and right).
Conclusion
At this stage we have a fully working A/B switch. It’s not pretty, it doesn’t have all the bells and whistles of the Rack Extension but the core part of the plugin is doing what it is supposed to do. The final version should be very similar to the Rack Extension (minus CV handling which is not available in the VST world :( ).
Next
Check out Part 4 for some more notes and comments regarding the remainder of the implementation.
Last edited: 2018/03/24