About this virtual piano

A small, dependency-free piano: a two-octave keyboard, correct equal-temperament pitch, and real polyphony, playable by mouse, touch or computer keyboard. No account, no upload, and nothing you play here is recorded.

Why it exists

Sometimes you want to check a melody, work out a fingering, or hear an interval, and there is no real piano within reach — just a laptop. This page gives that a home: a keyboard with correct pitches and enough polyphony for real chords, reachable from any browser with no install and no sign-up.

How the audio is built

Each note gets its own small Web Audio graph the moment it starts sounding: an OscillatorNode tuned to that note's exact frequency, feeding a dedicated GainNode that ramps its attack and release, feeding a shared master gain node that the volume slider controls, connected to your device's audio output.

  • The audio engine starts on your first note, not on page load. Building an AudioContext before a user gesture — or inside an asynchronous callback that runs after one — risks the browser leaving it suspended under its autoplay policy, which would make the very first note silently do nothing. It is created (and resumed, if needed) synchronously inside the first note-on's own event handler here, whether that event is a pointerdown, a keydown, or Enter/Space on a focused key.
  • Every note ramps its own gain instead of stepping it. Snapping a note's volume straight from 0 to its target (or back) almost always cuts the waveform off mid-cycle, heard as a click or pop. Each note ramps its gain up over about eight milliseconds on note-on and back down over roughly fifty milliseconds on note-off.
  • A fresh oscillator plays every note. The Web Audio spec makes OscillatorNode one-shot — once stop() is called it can never play again. A new one is created on every note-on and discarded once its release finishes, rather than trying to reuse a single node across notes.
  • A polyphony cap protects the tab. Up to 24 notes can sound at once; asking for a 25th stops the oldest note first, rather than letting oscillators accumulate without bound.

How the pitch math was verified

Every key's frequency is f = 440 × 2^((n − 69) / 12), where n is the note's MIDI number and 440 Hz / MIDI 69 is A4. This was checked against known reference values before shipping:

Verification test vectors and computed results
MIDI noteExpectedComputed
69 (A4)440.00 Hz440.00 Hz
60 (C4)≈261.63 Hz261.63 Hz
81 (A5)880.00 Hz880.00 Hz

Beyond those fixed vectors, the white/black key pattern was checked across a full octave against the textbook rule — a black key after C, D, F, G and A, none after E or B — and the computer-keyboard mapping was checked to be one-to-one: no physical key maps to two notes, no note is reachable by two different keys, and every on-screen key that shows a letter is reachable by exactly that key. All of this runs as plain assertions against the pure pitch/layout module under Node, with no browser or audio hardware required.

Scope and honesty about limits

This produces correct pitches from simple oscillator waveforms, not a sampled or modelled piano sound — it has none of a real piano's velocity sensitivity, string resonance, or hammer-strike character. It is not recording software, has no MIDI export, and does not save anything between visits. It is a keyboard for checking a melody or practicing away from an instrument, not a substitute for one.