Play a quick trill on a one-voice synth: hold one key, press the next before letting go, release the first. It's the most natural thing a keyboard player does. Before you try it below, predict: when you let go of the first key, should the sound stop?
Hold one key, press another, then let go of the first. Does the newer note keep singing? Now let go of it while still holding the first: what comes back?
Overlap two keys
Voice Mode
Use your computer keyboard so you can hold two keys at once. The newer note keeps singing when you release the old one. Release the newer note while still holding the first, and the first one comes back. It feels obvious, which is exactly why it's easy to get wrong.
A naive synth does something else. It hears "note-off" and stops whatever is sounding. Your trill gets a hole in the middle and the lead line drops out on fast passages, even though the oscillator and envelope are working perfectly. The hard part of an instrument isn't the tone. It's the bookkeeping.
Who owns a note-off?
A voice is everything it takes to make one sounding note: the oscillator's position in its wave, the envelope's stage, the pitch. A note-on starts or redirects a voice. A note-off sends it into release. The question every instrument must answer is: which voice does this note-off belong to?
One trill, two synths
Step through the same four events in three instruments. The middle one is the real code in Maru's instrument starter.
At the third event, which instruments keep D sounding?
Press C, press D, release C, release D
event 1C on
The voice tunes to C and starts its envelope.
- sounding
- C
The starter's fix is one line: if (a !== state.lastNote) return true. A release for a note that a newer note already replaced is accepted (so the host knows it was handled) and does nothing. That one comparison is the difference between a synth that drops out on fast passages and one that doesn't.
The starter stops there on purpose. It doesn't remember that C is still held, so releasing D while C is down leaves silence instead of bringing C back. The synth in the lab goes one step further with a held-note stack: a list of the keys still down, so the voice can fall back to the most recent one. That's how most mono synths behave, and it's what makes fast lead lines feel connected. Maru's DSP package has that stack ready to use as @maruaudio/dsp/voices/note-stack.
Decide what a new note does to the envelope
Bookkeeping says which note sounds. You still have to decide what a new note does to the sound already playing. Two classic answers:
- Retrigger. Every new note restarts the envelope. Each note of a trill gets its own attack: punchy, articulate, great for basslines that need every note to bite.
- Legato. An overlapping note changes pitch but keeps the envelope going. The line flows like one breath, which is the classic mono-synth lead trick, and pairs with glide for those sliding acid and funk lines.
Switch the lab's Voice Mode between Monophonic and Mono Legato and play the same overlapping trill. With a slow Attack (try 0.3 s), the difference is dramatic: retriggered notes each swell up from nothing and a fast trill never reaches full level, while legato notes ride one continuous swell.
Neither is correct. They're promises about how the instrument is played. Write yours down: "every repeated note gets a clear new attack" leads to different code and different tests than "overlapping notes slide without restarting".
Same idea, different place: Envelopes explains why a restarted attack changes a note's shape. Here, your event code decides when that restart happens.
Small rules with big consequences
Velocity zero means release. Many keyboards and sequencers send a note-on with velocity zero instead of a note-off. The starter routes that straight to its release logic: if (b <= 0) return handleEvent(state, PROCESSOR_EVENT_NOTE_OFF, a, 0). Forget it and some controllers will leave notes hanging forever.
Release isn't silence. A note-off starts the release stage; the sound fades over the Release time. That's musical. A stuck note is different: a sound that never reaches its end. When you test, wait for the release to finish and then check that the output really is silent.
Notes are sample-accurate. The host splits each audio block at the exact frame where a note arrives and calls handleEvent there, so a note lands on its sample, not on the next block. If a parameter change and a note arrive on the same frame, the parameter is applied first. Write your instrument to that rule and fast passages stay tight.
More notes need more voices
To play chords, an instrument needs several voices and a policy for handing them out. Maru's Graph instruments get this for free: you describe one voice, and the engine runs eight copies of it. A new note reuses a voice already playing the same note, then takes the lowest free voice, then steals the oldest one still sounding.
Stealing is where polyphony gets musical. Hold a low bass note, then play a big chord on top with all voices busy. Something has to give, and with oldest-first stealing, it's the bass note you started with, which is often the one you care about most. A hard steal can also click, while a quick fade needs a moment of overlap. These are design decisions, not plumbing.
A Processor instrument declares its own polyphony (maxVoices in its implementation) and handles allocation itself. You don't have to write a pool from scratch: @maruaudio/dsp/voices/polyphonic-pool does allocation and release, and you choose and test the stealing behaviour that suits your sound.
Try to break it before you add another oscillator
Before you spend an afternoon on a richer tone, play these phrases and know what should happen in each:
- one note, held and released
- the same note repeated fast
- the C and D overlap from the top of this guide, and its reverse
- a chord bigger than your voice count, over a held bass note
- a reset while notes are sounding, then the first phrase again
Listening catches ugly attacks and steals. An offline render catches what ears miss: render a repeated-note phrase and a sweep of each control, then check that every sample is a real number, find the peak, count clipped samples, and look for sudden jumps outside the short moments where notes start and stop. Maru's @maruaudio/audio-qc package has checkAudioQuality and analyzeAudio for exactly these checks.
Our take: finish note ownership before adding tone options. A plain voice that always responds is more fun to play than a gorgeous one that sometimes disappears.
Make it yours
Start from the instrument starter:
npm create maru@latest my-synth -- --kind processor-instrument
Pick one behaviour to grow: a held-note stack so releases fall back to held keys, a velocity response, or a small polyphonic pool. Make a preset that makes the behaviour easy to hear: a long enough release shows stealing, a crisp attack shows retriggering. Keep it dry so reverb doesn't hide what you're listening for.
Then write a short test phrase next to it: the overlap that should not cut, and the point after which everything must be silent. Run it every time you change the voice code. Your instrument will keep its promise to every keyboard player who trills on it.
Quick check
- On the starter, C is replaced by D, then C's note-off arrives. Should D stop? No. C's release doesn't own the voice any more. It's tempting to think "a key went up, so something should end", but the key that went up isn't the one you're hearing.
- On the starter, you hold C, play D, release D, and get silence even though C is still held. Is the guard broken? No, the guard did its job. Returning to a still-held note needs a held-note stack, which the starter leaves out to stay short. Add one if your instrument should behave that way.
- A slow-attack pad sounds great on chords but a fast melody on it barely gets louder than a whisper. What's going on? Each note retriggers the slow attack and the next note arrives before it finishes. Try legato for melodic playing, or a shorter attack.
Next steps
- Use it: Build a MIDI tool. An instrument turns notes into sound. What about a tool that turns notes into other notes, and might change them while you hold a key?
- Sideways: Realtime and numerical safety. Keep envelopes and voices behaving the same whatever block size the host hands you.













