Skip to content

bqst

bqst banner

bqst

An audio mastering plugin built in C++/JUCE, combining custom DSP, saturation design, oversampling, preset management, DAW validation, and a polished hardware-inspired interface.

download bqst for macOS buy me a coffee

why i built it

BQST is a working JUCE VST3/AU/Standalone audio plugin with a custom GUI, automatable parameters, presets, oversampling, pluginval validation, and a release build installed into Ableton. It sits right at the intersection of practically all the things I’m into: programming, music production, audio engineering, DSP, and product design.

Music production has never been a cheap hobby. DAWs already cost a decent amount, and the plugins that actually help with production and mixing can get expensive fast. At some point I started thinking: what if I could build one of the tools I kept reaching for? Or make one I felt would genuinely help me?

I’ve also always wanted to dip my toes into DSP. Not by watching plugin-development videos forever, or taking a course in school, but by building a real signal processor, and one that was useful enough to actually put on music.

what is it?

That’s how bqst came about, short for Baxandall EQ and Saturation. The idea was simple: a broad tone-shaping equalizer feeding a tasteful harmonic generator.

Final BQST plugin interface

An equalizer, or an EQ, boosts or cuts parts of the frequency spectrum of a signal. In music, that means adding an airy sheen to a vocal, reducing the harshness of a cymbal, or giving a kick drum more sub bass weight. The EQ module is the left half.

A harmonic generator is a little less straightforward. When you drive analog gear like tape machines, tubes, op-amps, or transformers, they don’t just make the signal louder; they add new frequencies related to the original sound. Those added frequencies are called harmonics. In the right amount, they can make audio feel thicker, warmer, brighter, or more expensive. Too much of it just becomes distortion. BQST’s saturation stage is my attempt at toeing that line, creating two algorithms that make signals sound full and dense, or a little aggressive and edgy. The saturation module is the right half.

The signed and notarized universal macOS installer supports Apple Silicon and Intel Macs and includes VST3 and Audio Unit formats. The source code is available at github.com/rohanz/bqst.

BQST is free to download. If it’s useful to you and you want to support future development, you can buy me a coffee.

Before getting into the sound design, there was one engineering constraint that shaped the whole project: this had to run safely inside a DAW.

realtime constraints

A plugin’s processBlock runs on a high-priority audio thread that the DAW calls every few milliseconds with a buffer to fill. Miss that deadline and the user hears a click or a dropout; there is no retry button. That makes the audio callback a real-time context: no locks that might stall the thread, no file or network I/O, and no per-sample allocations in the hot path. BQST reads host parameters through JUCE’s AudioProcessorValueTreeState, smooths gain, mix, and bypass changes, and gates filter coefficient updates behind smoothers so automation does not zipper. It also uses juce::ScopedNoDenormals in processBlock to avoid CPU slowdowns from subnormal floating-point values. The GUI thread handles presets, file access, and drawing; the audio thread only does the work needed to produce the next block of samples. The same discipline shows up in game engines, embedded firmware, and low-latency networking: anywhere a callback has a deadline it cannot miss.

Let’s start with the left half: the EQ module.

bax eq design

A Baxandall-style EQ is built for broad tone moves: making a mix feel brighter, darker, heavier, or lighter without sounding obviously “EQ’d.”

Each side has a low shelf and high shelf with +/-6 dB of gain. The shelf frequency selectors are stepped:

  • Low shelf: 74, 84, 98, 116, 131, 166, 230, 361 Hz
  • High shelf: 1.6, 1.8, 2.1, 2.5, 3.4, 4.8, 7.1, 18 kHz

The graph below shows all of those stepped shelf positions across the audible range.

The filters are JUCE IIR low/high shelves with a deliberately low Q around 0.38, closer to classic tone-control behavior than a modern parametric shelf.

Under the hood, each shelf is a biquad coefficient set. During a gain move, the gain value is smoothed sample-by-sample, and the coefficients are recalculated only while that smoother is still moving toward its target. Once the knob has settled, the filter reuses the same coefficient set. That keeps automation from zippering without rebuilding filter coefficients forever when nothing is changing.

The right half of BQST is trickier, because saturation is less about a perfect-looking curve and more about how the algorithm behaves when real audio hits it.

saturation design

The saturation side was the most subjective part of the plugin. EQ curves can be judged fairly visually, but saturation has to be judged by how it reacts to real audio. A curve that looks smooth on a graph can still sound brittle on a kick drum, dull on a vocal, or too obvious across a full mix.

I started by designing the two modes around different use cases. Cream was meant to be the smoother, denser mode: the kind of saturation that makes a signal feel thicker without immediately announcing itself as distortion. Grit was meant to be firmer and more transformer-like, with more edge and forwardness, but still usable outside of special-effect settings. The names are based on character rather than topology, because that is how I actually think when reaching for a processor during a mix.

The early versions exposed the usual problems with saturation quickly. Loud kick drums and 808s could push the algorithm into a harsh high-frequency buzz, and the more aggressive mode could make the upper mids feel too crunchy and forward. That was useful feedback: the issue wasn’t just the waveshaper curve, but the whole signal path around it.

The fix was to treat saturation as a small system rather than a single function. The transfer curve still matters, but it sits inside a chain: low-end control before the nonlinear stage, a carefully shaped soft-clipping function, tone shaping after saturation, and static autogain calibrated against different kinds of material. That made the algorithms behave more like a usable audio tool and less like a distortion demo.

The graph below isolates the waveshaping part of that saturation system. Click and drag upward on the Drive knob to see how the algorithms change as they are pushed harder.

As the drive increases, the straight dry signal starts to bend. That bend is the whole point: the peaks are rounded instead of chopped flat, which is what creates the extra harmonic content without immediately sounding like hard clipping.

The two algorithms, cream and grit, both use soft nonlinear transfer curves, but the surrounding tone network is different.

saturation algorithms

cream

Cream is the smoother mode. It is meant to feel thick and polished rather than obviously distorted. The algorithm combines:

  • a soft asymmetric tanh curve
  • a small even-harmonic bias term
  • cubic weighting for odd harmonics at higher drive
  • pre/de-emphasis around the upper treble
  • a low-end guard before and after the nonlinear stage

The pre/de-emphasis matters because full-band saturation can make treble harsh very quickly. Cream shapes what enters and exits the saturator, so the result feels more like analog density than a clipper. The core waveshaper uses drive-dependent terms for the knee, asymmetry, odd-harmonic push, and wet blend. Here, drive01 is the Drive knob normalized to a 0-1 range:

const auto push = drive01 * drive01;
const auto maxPush = push * drive01;
const auto asymmetry = drive01 * (0.016f + drive01 * 0.045f + push * 0.040f);
const auto oddWeight = drive01 * (0.032f + drive01 * 0.095f + push * 0.115f + maxPush * 0.135f);
const auto softKnee = 0.80f + drive01 * 0.42f + push * 0.36f + maxPush * 0.60f;

const auto driven = sample * softKnee + oddWeight * sample * sample * sample + asymmetry;
const auto shaped = (std::tanh(driven) - std::tanh(asymmetry)) * (1.0f + 0.07f * drive01 + 0.13f * maxPush);
const auto blend = drive01 * 0.39f + push * 0.16f + maxPush * 0.15f;
return sample * (1.0f - blend) + shaped * blend;

grit

Grit is transformer-inspired. It is a little firmer and more forward, but I still wanted it to be more than just a hyped special effect. It uses:

  • a firmer tanh transfer curve
  • a small bias term
  • low and low-mid weighting before saturation
  • partial low-end restore and mild top rounding after saturation
  • the same low-end guard structure

That pre/post tone path is what makes Grit feel less like a generic clipper. The low and low-mid content pushes into the nonlinear stage a little harder, then some of that tonal tilt is restored afterward, leaving more transformer-like weight without simply EQ-boosting the final signal. Those design choices show up more clearly in the harmonic fingerprint graph below, which uses a simple 1 kHz tone. Lower harmonics tend to read as thickness or warmth; stronger upper harmonics can read as edge or bite.

This one is interactive too. Turn the Drive knob to see how the harmonic balance shifts between gentle density and more obvious saturation.

Humans often perceive louder music as better music, which makes drive controls easy to misjudge. If a saturation stage gets louder as it gets pushed, it can feel like an improvement even when the main change is just extra volume. BQST has autogain enabled by default to make that comparison fairer. Instead of chasing the signal level live, it uses a static compensation curve calibrated offline against sine tones, bass, drums, and full mixes near commercial loudness. I rendered each source through both saturation modes at fixed drive values, measured the output level against the dry input, and fit one compensation curve per algorithm. That compensation is applied to the wet path before the Mix control, so turning up Drive changes the tone more than it changes the loudness.

The table shows the result in dB of gain compensation. “Compensation” is the curve baked into the plugin; “target” is the average level reduction suggested by the calibration material. Negative numbers mean the plugin turns the wet signal down by that amount. The goal was not to perfectly match every possible source, but to land within about half a dB on average without adding a live level detector into the audio path.

DriveCream CompensationCream TargetGrit CompensationGrit Target
3 dB-0.8 dB-1.3 dB-1.5 dB-1.7 dB
6 dB-2.4 dB-2.5 dB-3.7 dB-3.5 dB
12 dB-6.2 dB-5.8 dB-7.9 dB-7.8 dB
18 dB-9.7 dB-10.1 dB-11.4 dB-11.5 dB

That brings up another problem with nonlinear audio processing: once a plugin creates new harmonic content, it also has to decide what to do with harmonics that land above the host’s normal sample-rate limit. The fix is a process known as oversampling.

oversampling

Oversampling is one of those words that sounds more complicated than the basic idea. BQST temporarily processes audio at a higher internal sample rate, up to 8x, using JUCE’s dsp::Oversampling with half-band polyphase IIR filters, then brings it back down to the host’s normal rate.

This matters especially in saturation. A nonlinear curve creates new harmonics above the original signal. If those harmonics are generated too close to the host sample rate’s Nyquist limit, they can fold back into the audible range as aliasing. That foldback doesn’t sound like nice analog saturation; it sounds like unrelated high-frequency dirt.

Oversampling gives those new harmonics more room to exist before the anti-alias filter removes them. It also helps the high EQ shelf behave better near the top of the audible range, because the filter is no longer being squeezed against the original Nyquist limit as aggressively.

demo

Finally, let’s actually listen to it. The demo below shows the same drum loop: one clean, with no processing, and the other processed through BQST. Headphones or monitors are ideal here, because the changes are more about weight, transient shape, and tone. The processed version uses a +2.2 dB high-shelf boost at 2.1 kHz, a +1.7 dB low-shelf boost at 116 Hz, and Cream Drive around 14 dB. Press play, then flip between Clean and BQST while it is playing. Listen for added thickness and a touch more edge on the transients, even though the peak level is lower after processing: roughly -1.7 to -2.5 dBFS peak.

taste matters

The first working version of BQST already had the core idea: EQ, saturation, oversampling controls, meters, and left/right processing. But it looked like a prototype. A lot of the final work wasn’t adding more DSP; it was making the thing feel like a real audio tool.

Early BQST prototype interface

It worked, but visually, it left a lot to be desired.

I started designing all the assets myself. I wanted the plugin to borrow from the timeless, familiar language of analog hardware: big cream knobs, physical markings, screws, VU meters, and textured anodized faceplates, but I also wanted it to feel modern. So I kept the shapes simple, the shadows restrained, and the layout minimal. Those choices make the interface inviting but readable. VU meters aren’t just decoration, either; they give a slower, more musical sense of level than a twitchy digital peak meter.

Large BQST cream gain knob large gain knob
Small BQST cream selector knob selector knob
BQST VU meter frame asset vu meter

I didn’t want to copy analog design just for the sake of it, though. The design philosophy became: keep the familiarity of analog hardware, but use digital where it genuinely improves the workflow. Mid/side processing is rare in analog gear, and having it independently available per module is even rarer. In BQST, the EQ can run in M/S while the saturation stays in L/R, or the other way around, because digital routing makes that flexibility practical.

The workflow was designed around user convenience in the same way. Realtime and render oversampling are separated because those moments have different priorities: while tracking or writing, low latency matters more, so the plugin can run lighter; during export, latency no longer matters, so it can switch to higher-quality oversampling. Presets, undo, tooltips, and linked left/right controls work the same way. They aren’t flashy features, but they make the plugin feel less like a machine you have to manage and more like a tool that stays out of the way.

Designing BQST became a bigger lesson about software in general. A product can be technically functional and still feel unfinished. The details are what make people trust it: how it looks, how it responds, how predictable it feels, and whether it disappears into the workflow. BQST is vintage in its references, modern in its minimalism, and direct in use: it presents familiar controls, handles levels, latency, and routing quietly in the background, and still leaves the important decisions easy to tweak.

what stuck with me

Domain knowledge is a superpower. I hadn’t built an audio plugin before, but I’ve been using plugins for years. I knew what a useful plugin should feel like: the controls it needed, the pitfalls to avoid, the workflow problems to solve, and the difference between a cool demo and something I would actually put on a mix. That made the engineering loop much sharper, because I could listen to a bug, describe it precisely, and decide whether a code change actually fixed the musical problem.

DSP is both math and ergonomics. A saturation curve can look reasonable in isolation and still feel wrong on a kick drum. The final sound came from combining nonlinear transfer functions with filtering, autogain, oversampling, and careful parameter ranges.

Using AI well is an engineering skill. Codex was useful because I could give it precise constraints, inspect the result, test it in context, and redirect it when the output was wrong. I used it to move quickly through unfamiliar JUCE scaffolding, parameter wiring, and editor plumbing, but the important part was steering the loop: describing audio bugs clearly, asking for architectural changes, validating behavior in Ableton, checking the code paths, and deciding whether a change actually solved the musical problem. I didn’t take a backseat and hoping the prompts worked; I was using AI inside a disciplined loop of implementation, listening, measurement, correction, and validation.

Taste is part of engineering. The final plugin isn’t better only because the algorithms improved. It’s better because the interface became clearer, more trustworthy, and more intentional. The useful product was somewhere between analog-inspired familiarity and digital workflow improvements.

Production readiness is a system problem. The plugin wasn’t “done” when it made sound. It needed automation names, undo behavior, presets, AU/VST3 validation, signing, install paths, latency handling, and DAW testing. I validated the VST3 with pluginval at strictness level 10, checked the AU/VST3 builds in Ableton, and tested state recall, automation, bypass behavior, sample-rate changes, buffer-size changes, fixed UI sizes, and offline render settings.

download bqst for macOS buy me a coffee