The hardest test for a knob is a steady sound. Below, two plain tones hold perfectly still while you move Drive. Grab the Amount control and sweep it fast, back and forth. Listen to the moment of movement, not the settings at either end.
Sweep Amount quickly while the tones hold. Do you hear only the drive changing, or little ticks and steps as the knob moves?
Move the knob, not the sound
On a well-made effect the sound simply changes character as the knob moves. On a careless one you hear a zipper: a fine buzz or a stream of ticks riding on top of the tones, gone the moment your hand stops. Both effects can use exactly the same drive curve. The difference is a few lines that decide how a value travels from the old setting to the new one.
Two lines of DSP make a drive. A dozen small decisions around them make it an effect someone wants to play.
Start with a promise, not an algorithm
Before writing code, write one sentence about what your effect does to music. "Warms up a clean guitar without getting fizzy." "Keeps the pick attack, darkens the tail." "Adds grit to drums that stays the same as you automate it."
A promise like that is a design tool. It tells you which source to test with, which controls matter, and what failure sounds like. "A professional saturation engine" tells you none of those things.
The starter effect makes a quiet promise of its own: drive adds harmonics, Tone decides how many survive. Everything that follows is about keeping a promise like that while someone is actually playing with the knobs.
Order is the first decision
The starter runs drive first, then a low-pass tone filter. If you swapped them, would the sound change, or only the code?
It changes the sound. Drive is a curve, and a curve creates new frequencies from whatever reaches it. With the filter after it, Tone trims the new fizz the drive made, which is the same reason a guitar amp's speaker cabinet sits after its distortion. With the filter first, Tone only dulls the input, and the drive grows fresh fizz from what's left. Both orders are legitimate; they keep different promises. (Signals and systems explains why drive and filters don't commute.)
How cabinet impulse responses shape your guitar sound lets you compare the cabinet stage with the drive settings held steady.
Our take: pick the order you can explain in one sentence, and change it only to answer a listening question.
Ranges and defaults are part of the sound
Each control's range, default and feel live in the schema, so every way of moving it (the face, a DAW lane, a MIDI knob) agrees. That makes the schema a sound-design file.
Look at the starter's Drive. The curve is (1 + k) * x / (1 + k * |x|) with k = drive * 40, and for quiet input it behaves like a gain of 1 + k. Work that out across the fader:
| Drive | Gain on quiet input |
|---|---|
| 0.05 | 3 (about 10 dB) |
| 0.25 | 11 (about 21 dB) |
| 0.45 (default) | 19 (about 26 dB) |
| 1.0 | 41 (about 32 dB) |
The first quarter of the fader covers about two thirds of the change. That's where most of the character lives, and it's crowded into a small strip of travel. If your promise is "easy to dial in gentle warmth", that's a reason to give Drive a taper in the schema so the gentle settings get more room.
The default deserves the same care. It's the first thing anyone hears, so it should demonstrate the promise on the sound you designed for, at a comfortable level, with room to move both ways. Save the dramatic extreme for a preset.
Why a moving knob can click
Now the crackle. In the starter, setParameter stores the new Drive value, and the very next sample uses it. That's correct for a fixed setting. While the knob moves, it means the wave's shape changes instantly partway through a cycle.
A jump you can hear
A sudden step in a waveform contains energy at every frequency at once, and your ear hears it as a click. A fader or automation lane sends a stream of small steps, so you get a stream of small clicks: the zipper.
The fix is to keep two values instead of one. The target is where the knob says to go. The current value is what the sound uses right now, and it moves toward the target a little every sample. Step through both versions:
How far has the sound moved toward the new Drive value after each block of 128 samples?
Target and current, block by block
block 0Before the move
Drive sits at 0.2. Target and current are the same.
- target
- 0.2
- current
- 0.2
Add smoothing to the starter
Maru's DSP package already has the two pieces you need:
import {
onePoleSmoothingCoefficient,
onePoleSmoothingStep,
} from "@maruaudio/dsp/modulation/slew"
onePoleSmoothingCoefficient(timeSeconds, sampleRate) turns a time into a per-sample step size, and onePoleSmoothingStep(current, target, coefficient) moves a value one sample closer. Here is one way to fit them into your copy of the starter. It's a sketch to adapt, not code the starter ships:
// state: add driveTarget and driveCoefficient next to drive
// prepare(): the coefficient depends on the sample rate, so compute it here
state.driveCoefficient = onePoleSmoothingCoefficient(0.005, spec.sampleRate)
// setParameter(): move the target, not the sound
if (path === "drive") {
state.driveTarget = clampUnit(value)
return true
}
// process(): step once per frame, then use the same value on every channel
for (let offset = 0; offset < frames; offset = offset + 1) {
state.drive = onePoleSmoothingStep(state.drive, state.driveTarget, state.driveCoefficient)
for (let channel = 0; channel < 2; channel = channel + 1) {
if (channel >= channels) break
const index = channel * stride + start + offset
const shaped = driveTransfer(block.input[index], state.drive)
state.filterState[channel] = onePoleLowpassTick(state.filterState[channel], shaped, coefficient)
block.output[index] = Math.fround(state.filterState[channel])
}
}
Notice the loops swapped places. The starter walks one channel at a time. If you stepped the smoother inside that channel loop, a stereo signal would advance it twice per frame, so the left and right channels would hear different Drive values and the glide would run at double speed. Step once per frame; share the result.
Choose a time you can defend
The 0.005 above is a time constant: after 5 ms the value has covered about 63% of the gap, and after 25 ms it's essentially there.
Fast glide or slow glide
There's no single right answer, only a musical one. Too short and fast moves still buzz. Too long and the knob feels like it's wading through syrup: a quick automation flick on a drum fill arrives late and smeared, and a performer stops trusting the control. Somewhere between a few milliseconds and a few tens is usually where a drive feels both smooth and alive.
Test it the way you'll use it. A steady tone exposes clicks best, so start there. Then play the real part, a guitar riff or a drum loop, and flick the knob in time. The tone tells you whether it's clean; the riff tells you whether it's fast enough.
Where smoothing still goes wrong
The first note. If a fresh instance starts with drive at 0 and the default target is 0.45, the very first note glides in from no drive at all. Decide where the current value starts. Usually you want it to land on the first target it's given, so a new instance sounds like its setting immediately, and do the same after reset.
Every control, not just one. Smoothing Drive doesn't smooth Tone. Listen to every knob on the steady tone, including both ends of each range and two knobs moving at once.
The wrong test. Moving a knob in a browser preview isn't the same as automation in a DAW, which sends discrete edits. Test with the kind of edits the effect will actually receive.
Make it yours
Write a one-line promise for an effect of your own. Then create it from the Processor starter:
npm create maru@latest my-drive -- --kind processor-effect
Change only what your promise needs: maybe the order of the stages, a range and a taper, a smoothing time. Run the project's dev preview, put a steady tone through it, and move every control fast. Then play the music it's meant for.
When it keeps its promise, set the schema default to the setting that shows it off best, so it's the first thing anyone hears. And keep one test that renders a sudden jump in your smoothed control and checks the output for a step between neighbouring samples. If someone (including you, in six months) removes the smoothing, that test will say so before a listener does.
Quick check
- Your effect sounds great at Drive 0.2 and at Drive 0.8, but sweeping between them crackles. Is the drive curve wrong? No. Both settings are fine; the crackle is the value jumping between them. Look at how the parameter travels: smoothing, and how edits arrive.
- You smooth Drive inside the per-channel loop and the stereo version glides twice as fast as the mono one. Why? The smoother steps once per channel instead of once per frame, so two channels mean two steps per sample. Step once per frame and share the value.
- A 100 ms smoothing time makes the zipper vanish, but a drummer complains that automated grit arrives late on fills. What do you change? Shorten the time constant. Long smoothing hides clicks by blurring every move, including the ones that need to land on the beat.
Next steps
- Use it: Build an instrument. An effect answers a signal. An instrument has to answer notes that start and stop. What new problems arrive with note-off?
- Go deeper: Distortion and saturation. More about the curves, the harmonics they make and why oversampling keeps high notes clean.
- Sideways: Realtime and numerical safety. Your glide counts samples. What breaks when code counts blocks instead?













