2025–2026 · experiment · web audio api · web midi

Reverse Piano

an instrument that sounds on release
playing Reverse Piano on a laptop keyboard
live · press and let go enter →

Most pianos work the same way: press a key, hear a note. Reverse Piano inverts that. Pressing a key produces nothing; the instrument records the press and waits. Sound is synthesized at the moment of release, shaped by three measurements taken during the hold — how long the key was held, the velocity it was struck with, and how many other keys were held at the same time. A short hold produces a fast, percussive tone; a hold of several seconds produces a slow attack and long sustain.

how to play

Two ways in — your laptop keyboard, or a MIDI controller.

black:   w e   t y u   o p   ]
white:  a s d f g h j k l ; ' \
laptop keyboard, faked velocity
MIDI controller, real velocity

how it works

Nothing is scheduled while a key is held. On keydown (or MIDI note-on), the note goes into a single map — that's the entire cost of a press:

held: Map<note, {startTime, velocity}>

on release:
  duration  = now - startTime        // seconds of silence
  velocity  = midiVelocity / 127     // 0..1
  density   = heldKeys.size + 1      // simultaneous notes
  held.delete(note)

Those three scalars are the only inputs to synthesis — each drives a different part of the sound:

duration →  attack   0.005s → 0.125s
            sustain  0.3s   → 4.3s
            release  0.2s   → 3.2s
velocity →  gain     0.08 + v · 0.25
            cutoff   400 + v · 4500 Hz
density  →  reverb   send, clamped at 0.8

Duration is normalized against a 3-second window before it interpolates the envelope. Density does one thing beyond the reverb send: past a richness threshold, a third partial appears in the tone. And the UI names each release by where its hold duration lands — gasp / breath / sigh / exhale.

Each release builds a throwaway Web Audio graph: a triangle fundamental, sine partials at 2·f₀ and 3·f₀, and a 100ms noise burst — boosted on very short holds to produce the percussive gasp transient — routed through a shared low-pass, an envelope gain, and a convolution reverb with a procedurally generated impulse response. No samples, no voice pool; polyphony is bounded only by how many keys are actually held.

filter direction

The low-pass cutoff sweeps downward as each note sounds — from its velocity-derived value to 0.15× over the note's lifetime — so the tone softens and rounds as it decays, matching the pressure curve of an exhalation: hold as inhale, release as exhale.

what I learned

The hardest part wasn't the code — it was learning how to play it. The first time I sat down with the finished version, I had to unlearn my instinct to press and sustain; this instrument only responds to release.

I think about this often when writing code. The best architectures are usually the ones where you notice what isn't there — the coupling that was never added, the abstraction layer that wasn't needed, the state that was never introduced. Systems, like this instrument, are as much about what you leave out as what you build.

· · ·