Your effect sounds great on your favourite drum loop. Then someone runs a bass through it, or a quiet piano piece, or nothing at all between songs, and it hums, clicks or blows up. Which test sound would have warned you? Predict before you play: a busy drum loop, a sine sweep, a single click, or a burst of noise followed by silence?
Switch between the four sources. Which one tells you most about the effect itself, and what does the scope show after the noise burst stops?
Ask the effect a question
Watch the scope right after the noise burst ends. On this healthy drive the line goes flat, instantly. That flat line is a test result, and it's one a drum loop can never give you, because a drum loop never stops long enough to show what the effect does on its own.
A test sound is a question. Busy music asks "does it sound nice?", which matters, but it hides almost every bug under the music. The most revealing test sound is often the plainest one.
Silence asks about the effect itself
Feed silence into an ordinary effect and the right answer is silence. A reverb or a delay may ring on after real sound, but that tail should fade to zero. Anything else in the silence came from your code, because there was nothing in the input to explain it.
Here's an imaginary bug: an effect that adds a tiny constant, output = input + 0.1. On a drum loop you might never notice. On silence, it's unmistakable.
What silence reveals
That steady offset is called DC, and it's not harmless just because you can't hear a constant. It eats headroom, so the waveform clips sooner on one side. An edit that jumps to or from that offset makes a click when someone cuts or fades the track. And on some speakers it pushes the cone off-centre for as long as it lasts.
Test silence twice: on a fresh instance, and right after real sound. Some bugs only appear once the effect's memory (a filter's state, a delay line) has something in it.
Every test sound asks something different
- A sine or a sweep asks about frequencies. One pure tone makes added harmonics, gain changes and noise easy to spot. A sweep from low to high shows where things change: where a drive starts adding harsh partials, where a filter rings.
- A single click (an impulse) asks about time. Everything after the click is what the effect remembers: echoes, reverb, ringing filters, and how long before it's truly quiet.
- Full scale and both ends of every control ask about the edges. Turn each knob to its minimum and maximum, then try the combinations that matter: maximum feedback, then silence; lowest cutoff with highest resonance. Some extremes are meant to be silent or wild. None should produce NaN, a stuck output or an endless squeal.
- Moving controls ask about transitions. Move knobs the way a host does, with separate edits, not a smooth browser ramp that skips the code path that clicks.
- Real music asks the question only ears can answer: do you like it?
Switch the lab to the impulse and look at the scope. A drive has no memory worth speaking of, so one click comes out as one (slightly reshaped) click. Put a reverb there and the same test shows you its whole tail.
Three bugs, three test sounds
Step through three faults that often slip past a quick listen. They're illustrations, not recordings, but each is a bug real effects have shipped with.
For each fault, which test sound makes it impossible to miss?
Which test catches it?
The fault
A stage adds a small constant to every sample.
1block.output[index] = shaped + 0.1None of these is caught by "it sounds fine on my loop". Each is caught in seconds by the right question.
Matching yesterday is not the same as being right
A powerful kind of test compares today's output with a saved one. When Maru moves a module to a new implementation, it freezes the old renderer and checks the new one against it sample by sample, with a maximum difference of zero, across presets, notes, both sample rates, every control's endpoints, odd block sizes and resets. That's how you prove a rewrite changed nothing.
But notice what it proves: nothing changed. If yesterday's output had a DC offset, a test that demands today's output match it will faithfully protect the offset. So pair every "same as before" check with checks that know what right means: silence stays silent, every sample is a real number, nothing clips, notes end.
And when a test fails after an intentional change, explain the change before you update the expected output. Updating a saved result or widening a tolerance until the test goes green, without understanding why it failed, turns your test into decoration.
What Maru checks for you, and what it doesn't
Every Maru module can run the same gates the packager runs, cheapest first:
pnpm --dir modules/<slug> check # every gate, in the packager's order pnpm --dir modules/<slug> check -- --fast # skip the slow compiler and browser pair
The light gates check that the declaration is valid, the Processor compiles, the face only uses what the host provides, and the parameters still match the module's recorded contract. The heavy gates build the JavaScript and WebAssembly versions and require their output to match exactly, then run the module in a real browser to check reset, event capacity and the realtime deadline.
What no gate knows is your promise. For that you write tests with the shared @maruaudio/audio-qc package, whose checkAudioQuality and analyzeAudio helpers check renders for non-finite samples, peaks, clipping and sudden jumps. For instruments, render a repeated-note phrase and a sweep of each control as well as a held note. For anything that saves state, the round trip into a fresh instance has its own check, covered in the next guide.
Listening is a test too
None of this replaces ears. A machine can confirm every sample is finite while a harsh resonance ruins the phrase. So when you compare two versions by ear, test like you mean it: same source, same starting point, matched loudness (louder almost always sounds better), and if you can, hide which is which until you've chosen.
Same idea, different place: Listening and comparing turns those habits into a fair A/B you can trust. A meter and an honest listening test answer different questions about the same output.
Write what you hear so you can find it again: "a tick when Drive jumps at 1.2 s" is useful; "feels less pro" isn't, yet.
Make it yours
Build a checklist for your own module. Use the sheet below; it stays in this browser and copies out as Markdown when you're done.
For each test sound, what should happen, and what actually happened?
My module's test sounds
0 of 6 done · saved in this browser
Fill one row each for fresh silence, silence after sound, a sweep, a click, every control at both ends, moving controls, and a reset. Then pick the failure you'd hate most for someone else to discover and turn it into an automated test that names it: "silence stays silent after a phrase", "reset sounds like new". If that bug ever comes back, the test will say so in words you'll recognise.
Quick check
- A render matches its saved reference exactly, but has a DC offset. Is the module correct? It's unchanged, not correct. The "same as before" check passed; a silence check would fail. They answer different questions, and the first one is easy to mistake for the second.
- A reverb keeps making sound for three seconds after the input stops. Has it failed the silence test? Not necessarily. A tail is the point of a reverb. Check that it decays all the way to zero and stays there, and that reset clears it.
- You test a knob by ramping it smoothly in a browser preview and hear no clicks. A DAW user hears clicks when automating it. Why? The DAW sends separate edits, which go through the parameter path your smooth ramp skipped. Test with the same kind of edits the module will receive.
Next steps
- Use it: Presets, state and automation. Your module passes every test, until someone saves a session, closes the DAW and reopens it.
- Go deeper: Realtime and numerical safety. The timing and number problems that hide inside the callback.













