Home Lyre delay - A Learning Journey
Post
Cancel

Lyre delay - A Learning Journey

PROJECT 01 · AUDIO PLUGIN

C++ / JUCE VST3, Standalone Solo project ~12 months, first hello world → v1.0

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.

Lyre Delay v1.0 — current build Windows · VST3

LISTEN

Dry / granulated

Dry source
Example 1 - 62% Wet
Example 2 - 100% Wet

Two different examples of the granular mode, same source, different settings

HOW IT GOT HERE

From tutorial to granular engine

EARLY
I started by following "The Complete Beginner's Guide to Audio Plugin Development" step by step, changing things here and there as I started to get more confortable
MID
Basic granular engine was already there, I also had already added some new elements such as the audio visualizer and the bulb-style meter
LATE
Fully developed granular engine and preset system and an improved GUI

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;
A Hann window (source)

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.

TEXTURE grain timing density jitter grain size duration jitter grain position start-point jitter
  • 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.

Preset browser — Lyre v1.0
FACTORY bundled
  • Anime 🔒
  • Answers 🔒
  • Bouncy Eight 🔒
  • Brick 🔒
USER local
  • 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.

complex numbers Fourier transform delay & filter design multithreading pointers debugging / compiler behavior

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

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
This post is licensed under CC BY 4.0 by the author.