PROJECT 01 · AUDIO PLUGIN
I've been using VST plug-ins since 2009 when I first started using a computer to make music and I continued to use them throughout my career in audio. However, last year I decided to challenge myself: how I would I keep growing as an audio artist? Why not try to build my own tools for it? What kind of vocabulary can we develop from creating our own tools? What kind of friction can we introduce into our own process? I’m still working through all of these and here I wanted to share some of my journey with Lyre, my first VST Plugin.
I started by reading "The Complete Beginner's Guide to Audio Plugin Development" cover to cover because I was starting from zero here. I have developed games before, but this was a different beast. The book guided me through the basics of how to work with JUCE: what the editor and processor are, how to implement a circular buffer, how to style my editor. It also introduced taught me how to use one pole filtering for smoothing and to why understanding threads is important for plugin development.
LISTEN
Dry / granulated
Two different examples of the granular mode, same source, different settings
HOW IT GOT HERE
From tutorial to granular engine
FEATURE SPOTLIGHT
The grain engine
Hollemans' book is truly a great beginners guide. After going through it I had a great starting point for my project and I started developing the granular mode. My first task was the grain itself and then develop an engine to spawn these grains and process them.
A grain is a small fragment of audio near the threshold of what we can perceive that contains time-domain information (starting time, duration, envelope shape) and frequency-domain information (the pitch of the waveform within the grain and the spectrum of the grain). (ROADS, 2002). The grains in this system are objects and we'll store them in a grain pool: this will allow us to spawn many grains, sum their output, and reuse them over time and that is important for performance since audio shouldn't have interruptions.
Because grains are small fragments of audio, that means they will likely have discontinuities at their boundaries and those will be perceived as small crackles in audio that are created by a sudden jump in amplitude. That issue is solved by giving each grain a window: you multiply it by a window function and that will smooth the boundaries instead of cutting them off.
WINDOWING
C++float window;
if (grain.grainDuration <= 1) {
window = 1.0f;
}
else
{
float phase = static_cast<float>(grain.samplesPlayed) / static_cast<float>(grain.grainDuration - 1);
window = 0.5f * (1.0f - std::cos(juce::MathConstants<float>::twoPi * phase));
}
outL = sampleL * window;
outR = sampleR * window;
However, something that happens when you apply a window is that you lose some energy, each grain gets quieter and the final result is a noticeably lower volume. I corrected this later with a makeup gain:
MAKEUP GAIN
C++if (params.granularisActive) {
constexpr float granularMakeupGain = 2.5f;
wetL = grainL * granularMakeupGain;
wetR = grainR * granularMakeupGain;
}
FEATURE SPOTLIGHT
Parameters
Three parameters drive the grain engine directly.
DENSITY
Density determines the number of grains per second, independent of how long each one lasts.
GRAIN SIZE
Grain size controls how long an individual grain lives, in milliseconds. Shorter grains will sound more textured and granular with sharp transients, whereas larger sizes will sound smoother.
PITCH SHIFTING
Pitch comes from stepping each grain's read position through the buffer at a fractional rate set by the pitch ratio.
Texture: modulate it all
Texture is a macro control, not a single parameter. It is a randomness controller that modulates grain timing, grain size, and playback position at once, each with its own independent randomization range.
For every grain, the engine draws a random value between -1 and +1, so we'll have symmetric jitter around the base value. That random value is scaled by how far texture is turned up, then added to 1 to produce a factor: at texture = 0, the factor is always exactly 1, so nothing changes; as texture increases, the factor can drift further above or below 1. This factor is then multiplied by the base value to get the jittered result.
- Density: the interval between grains is multiplied by a random factor, up to ±50% at full texture.
- Size: each grain's length is multiplied by its own random factor, up to ±100% at full texture.
- Position: the grain's start point in the buffer is shifted by a random offset, scaled to the grain's own size.
FEATURE SPOTLIGHT
Preset manager
I learned the basis of this preset manager from Akash Murthy on The Audio Programmer Youtube channel and adapted it to Lyre's environment. Presets are split into two banks: factory presets, bundled with the plugin (read-only), and user presets, saved locally: anything you saved stays on that machine, separate from the factory bank.
- Anime 🔒
- Answers 🔒
- Bouncy Eight 🔒
- Brick 🔒
- myPreset 1
- Guitar 1
- Guitar 2
NOTES ALONG THE WAY
What this project actually taught me
New territory
Coming from game development, programming wasn't entirely new to me, though C++ specifically was. However, this felt like different territory. Some DSP concepts were review, things I'd touched before as an audio designer with an interest in the subject and some were concepts that I was looking in depth for the first time.
My C++ also moved forward: multithreading was new to me, and my debugging improved a lot just from understanding compiler behavior better and learning to actually read what the IDE's output was telling me.
As a sound designer, I can have total control over how far I can push each parameter in this plugin, how I want them to interact with each other (the texture macro is one example), and how the signal flows between them. I can use my sound design skills to shape the plugin and my developer skills to help shape my sounds.
RESOURCES & READING
Useful resources
BOOKS
- HOLLEMANS, Matthijs. The complete beginner's guide to audio plugin development. 2024.
- ROADS, Curtis. Microsound. The MIT Press, 2004.
- REISS, Joshua D, MCPHERSON, Andrew P. Audio Effects - Theory, implementation and application. CRC Press, 2015.
- STROUSTRUP, Bjarne. The C++ programming language. Addison-Wesley, 2013.
DOCUMENTATION, REPOSITORIES & WEBSITES
- JUCE official docs JUCE official documentation, learn the framework, explore the classes, etc.
- Learn CPP This is honestly a great companion resource
- C++ reference C++ documentation
- Level meter by Akash Murthy This is where I learned how to make the bulb style meter
YOUTUBE CHANNELS
- The Cherno C++ course — Great resource to learn C++ fundamentals with practical demonstrations, understand how the compiler works, how linker works and there's a lot of more advanced topics to explore there
- Digital Filter Basics course by Akash Murthy — Understand digital filters, easy to follow, demonstrations with images
- WolfSound — Great for DSP and audio programming specific content
- The Audio Programmer — JUCE and DSP tutorials, meetups with experienced audio developers