carillon — keystroke rhythm sonifier

No typing rhythm can produce a wrong note.

Pitch comes from the gap between your keys, loudness from how long you hold one. Both are then snapped to the scale — which you can watch happen, below.

The instrument

demo trace — a recorded rhythm, real mapping

last keystroke

interval
ms
pitch
Hz
dwell
ms
cutoff
Hz
notes 0 corrections 0 avg ms Sound is off. Typing works either way.

Interval sets pitch

log-mapped, then snapped to the same 10 lines · 40–900ms

Dwell sets loudness & brightness

30–250ms → gain 0.35–1.00, cutoff 900–4200Hz

Provenance

0 network requests after load — checked, not asserted
63 tests, all passing
10×3 quantization targets across three keys
7 cents of detune between the two voices — chorus, not a beep
How the zero-request claim is enforced
$ node verify.mjs . verify: OK — no network calls, external resources, or analytics identifiers found in .

That checks the source. This checks the running page, live in your browser right now, by reading its own Performance timeline:

measuring…

verify.mjs scans what ships — index.html plus everything under src/ — and exits non-zero on any of the three request-issuing browser APIs, an http(s) resource, a CDN script tag, or a telemetry-shaped identifier. It runs on every npm test. That is also why this page carries no webfont: one would be a request, and the check would fail. It is strict enough to have caught this very paragraph naming one of those APIs out loud, which is the correct behaviour for a checker whose subject is the page's own bytes.

What happens to your keystrokes

Two numbers per key — time since the last key, and how long you held this one — are read with performance.now() in this tab's memory, turned into a pitch and a tone, and thrown away. Reload and every timing is gone. There is no server, no storage, and no copy of what you typed anywhere but your own screen.

What this does not claim
  • It is an instrument, not a biometric: nothing here identifies who is typing.
  • Tuned by ear against one person on one keyboard. No claim it suits everyone.
  • Physical keyboards only — on-screen keyboards do not produce comparable timing.
  • performance.now() inside the event loop is not a real-time clock; under load, timing jitters by a few milliseconds.

Full method, limitations and the mapping's reasoning: README.md.