Skip to content
TerraByte-DevPublic

About

A MIDI piano practice workspace for Windows. 3 ms from key to sound, 190 instruments, splits and layers, a loop station, sheet music, and stats that quietly learn what you actually play.

Topics

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Keys

Keys

A MIDI piano that answers in three milliseconds — without taking your speakers hostage.
A local-first workspace for the instrument you already own — plug in, run one command, play.

License: MIT Latest release 3.00 ms latency, measured No account, no telemetry Python 3.11 · FluidSynth · zero-build frontend

Get started · Features · Screenshots · Architecture · Troubleshooting · Contributing · License

brought to you by
TerraByte Solutions LLC


Keys is a workspace for a USB piano — a room you open and play in, at your own pace, with nothing to enrol in and nobody grading you. MIDI comes in, FluidSynth renders it through WASAPI in exclusive mode at a measured 3.00 ms, and a browser UI hangs off the side showing what you played — never in the path of what you hear. It's local-first: no account, no telemetry, fonts already on your machine, and a practice history that lives in a SQLite file you own. Nothing leaves the machine and nothing is fetched — with exactly one exception, opened only when you ask for it: a backing track loads YouTube's player. The frontend has no build step and no dependencies — vanilla ES modules served straight off disk. There is no package.json.

It isn't a tutor and it isn't Synthesia. There's no curriculum, no unlock sequence, no streak-shaming. What there is: hundreds of instruments a click away, splits and layers you can build in seconds, a metronome that can't drift, sight-reading when you want it — and, underneath all of it, an analytics layer that quietly records everything so you can look back and see which keys, chords and scales you actually reach for.

Get started

Install the release — Windows, no Python and nothing else to install.

Download Keys-<version>-win-x64.zip from Releases, extract it into a folder of its own, and run Keys.exe. The folder matters: the zip's root is the application — Keys.exe and _internal/ with no wrapper folder above them — so "Extract Here" empties it into whatever folder you were standing in, and that folder is the one an in-app update rewrites. The build is unsigned, so SmartScreen says "Windows protected your PC" the first time: More info → Run anyway.

After that, Keys updates itself in place: Settings → About → Check for updates, then Download, then Restart and install. Three separate presses, nothing on launch and nothing on a timer, and your practice history, settings and recordings — which live in %LOCALAPPDATA%\Keys, not in the application folder — are not touched by any of it.

Prerequisites — for running from source instead.

  • Python 3.11 or 3.12 — python-rtmidi publishes no wheel for 3.13+.
  • FluidSynth 2.x — download the -cpp11 zip, extract it, and put its bin/ on PATH. (It is not in winget, and pip install fluidsynth is an abandoned 2012 package — the binding you want is pyfluidsynth, which bundles no native library.)
  • A SoundFont — GeneralUser GS 2.0.3 saved to soundfonts/GeneralUser-GS.sf2.
  • A class-compliant USB MIDI keyboard. Developed and measured against a Yamaha P-71B, and 88 keys (MIDI 21–108) is what Keys assumes until you say otherwise. Any size works: tell it what you have in Sound → Your keyboard — pick 25/32/37/49/61/73/76/88, set a custom range, or press your lowest and highest key and let it work them out. Everything follows from that answer: what gets drawn, how far a scale runs, where sight reading puts its notes, and what happens when you open a piece written for more keys than you own.

Run

git clone https://github.com/TerraByte-Dev/Keys.git
cd Keys
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
.venv\Scripts\python keys.py        # opens http://127.0.0.1:8770

That's the whole setup. The browser opens on its own, the MIDI port binds itself, and unplugging the piano and plugging it back in needs no restart. 1–6 switch views, M toggles the metronome, Esc is panic.

FluidSynth somewhere else? Set KEYS_FLUIDSYNTH_BIN to its bin directory. Everything else you'd want to change lives in Settings, and persists to a gitignored config.local.json.

Features

  • Three milliseconds, measured — and off by default — WASAPI exclusive mode at a 144-sample buffer, verified on real hardware rather than estimated. But exclusive mode takes the output device from every other application while Keys runs, so it is one click in Settings rather than the default. Out of the box Keys shares your speakers (~10 ms, inside the range a piano action already spans between a soft and a hard keystroke) and coexists with Spotify, Discord and a browser. Either way, audio is rendered in FluidSynth's own C thread and never passes through Python; the MIDI callback does one thing and stops, and everything else runs off a bounded queue that drops frames instead of blocking a note.
  • The note roll, and a full-screen mode to leave it in — the falling-notes videos, upside down: notes rise out of the keys as you play. A bar's length is how long you held the note and the gap above it is how long you waited, so legato, staccato and a chord you rolled by accident become things you can see. Press F and everything but the notes and the keyboard goes away — vignette, a wash of light at the keybed that swells under a run, drifting motes. In a split each zone gets its own colour, which is the thing a general-purpose piano visualiser cannot do.
  • Chords & scales that teach the gaps — pick any root and any of 13 modes or 24 chord qualities, and the biggest numbers on screen are the distances between the notes, because that is the part you can carry to a key you have not memorised. A major scale is W W H W W W H anywhere; a major chord is four keys then three, minor is three then four. Standard fingering sits on the notes with thumb crossings marked, and the seven chords that fit the key are underneath. Spelling is by letter, not lookup, so A harmonic minor comes back with a G♯ and not an A♭.
  • Sheet music you already own — import MusicXML (.musicxml or .mxl) and Keys engraves it with Verovio. Every notation program exports it, and a .mid works too, converted on the way in — so every piece in your library has both readings. The printed page is a mode of the full-screen roll rather than a separate screen: each song has a Roll button and a Sheet button, and once you are in there one control swaps between the notes falling and the engraved page. Both are play-along — the app makes no sound of its own on that screen, on purpose, which is why it still works with the audio engine off.
  • Ghost mode — learn a song by following it — point it at a .mid and the piece falls down the roll, silently, for you to play. It waits at each chord until you have actually played it, so you cannot fall behind and end up watching a video; one hand at a time if you want, with the other scrolling past as a shadow so you keep your place. Tempo and roll speed are separate knobs on purpose — one decides how fast the music goes, the other how far ahead you can see. And because your own playing falls at the same rate as the target, the gap between the two is your timing error: dead on and the edges are flush, a hair late and your bar sits above by exactly that much, all the way down. No sound of its own, no backend call, so it works with the audio engine off entirely.
  • Eleven themes, and the whole instrument follows — the keyboard, the lamps, the charts and the note roll all read one palette, so a theme changes the instrument rather than the chrome around it. Includes a real daylight theme, not a dark one with the lights turned up.
  • Layers: splits and layers on one keybed — a split puts bass in your left hand and piano in your right; a layer puts piano and strings on the same keys. Both are one idea: a zone is a range of keys pointed at a sound, and overlapping two zones is the layer. One click each, with a full editor underneath for transpose, gain, pan, sends and velocity curves.
  • A loop station — be your own band — record a few bars, they start looping, then play over the top and record that too. Bass, chords, melody, up to five layers, each keeping the instrument you played it with. Takes are locked to the bar and end themselves on the bar line, so a loop never drifts the way a hand-stopped one does; the pedal is captured as note length, so a sustained part sustains.
  • Backing tracks — paste a YouTube link, set loop points, and grind eight bars of a solo without hunting the scrubber. Slow it down to learn it, and keep the key and tempo written next to it. (It tells you when exclusive mode has the speakers, instead of playing silently and looking broken.)
  • 62 presets, ~190 instruments — presets are plain JSON you can edit by hand, and the ones you save get their own group at the top of the shelf rather than being filed alphabetically among the shipped ones. The instrument browser enumerates every preset the SoundFont actually contains — including all 13 drum kits — rather than trusting the GM chart.
  • An effects panel, not a shelf of opinions — reverb and chorus, and how much of the instrument you are playing goes into each, on knobs that move while you play rather than baked into a preset somebody named Cathedral. Deliberately only the controls that are real: MIDI’s tone CCs — 71 resonance, 72 release, 73 attack, 74 brightness, 75 decay — do nothing in FluidSynth, rendering byte-identical output at 0, 64 and 127, so a brightness knob here would be a placebo with a number beside it. See ARCHITECTURE.md.
  • Jam — the band plays, you solo — Keys plays drums, bass and chords (Ballad, Pop, Lo-fi or Rock, at any tempo) through the progressions songs are actually built on: the pop four, the fifties, ii–V–I, Pachelbel, the anime "royal road", the Andalusian walk. Each is played twice, sometimes with one chord swapped for a neighbour that shares two notes, and the next is chosen by how well it follows, so it never loops and never lurches. A one-octave strip, mirrored on the keyboard in every octave, lights what sounds good over the chord playing now: the chord bright, the notes that add colour dim, nothing lit for a note that rubs (the one a half step above a chord note). The set holds still for the whole chord. Two beats before a change it shows the way across: the next chord outlined, the notes both chords share marked, and one key pulsing where to land, chosen once from where your hand is. The band runs on FluidSynth's sequencer clock, on its own channels, so it never drifts and never fights your notes.
  • Four more ways to jam — Modal: a two-chord vamp in Dorian, Mixolydian, Lydian, Aeolian or Phrygian, all on the white keys, with the one note that gives the mode its colour ringed. Blues: the 12-bar form (and the quick change, and a minor blues) on a shuffle, judged against the blues rather than the key, so the blue note is colour instead of "outside". Answer: the band plays a two-bar question that ends up in the air, and you answer it, ending on a home note. Echo: the band plays a one-bar lick on vibraphone and you play it back, in any octave; the licks grow a note after two clean echoes and shrink after two misses. The band's own melody lights the keys as it plays, with the guide up.
  • Exercises you can pick up — scales in any key and mode, one hand or both, parallel or contrary; arpeggios with inversions; sight reading. Hands-together steps carry both notes, so the spread between your hands is measured directly — the thing most people plateau on and nobody else surfaces.
  • A practice clock that doesn't flatter you — time is credited between consecutive notes, capped at a grace window, so "34 minutes" means minutes with your hands on the keys and not minutes with the app open. Streaks, a 90-day calendar, and a per-key heatmap of what you actually use.
  • A metronome on the audio clock — clicks are scheduled on FluidSynth's sequencer, driven by the render thread, so they cannot drift against the sound. Tempo ramp with a one-key setback when you miss. It measures drift, not just per-beat error — players hold even spacing while the whole tempo slides, and that's the failure mode worth seeing.
  • Sight reading that adapts — a hand-rolled SVG grand staff (no notation engine), enharmonically spelled by key signature, weighting each new measure toward your worst notes from your own attempt history.
  • Chords, named properly — inversions, slash chords and extensions, spelled to the key you're in: in E♭ major it reads Eb, not D#. Detection runs in ~19 µs, sixty times a second.
  • Honest instrumentation — the latency panel reports the one number software can actually see and says plainly what it excludes. Nothing in software can measure MIDI-to-ear latency, and this app doesn't pretend otherwise.

Screenshots

The Play view — presets, instrument browser and the docked keyboard

The keyboard never leaves. It's docked to the bottom edge in every view and lights amber under your fingers, scaled by velocity.

The Practice tab: a session clock and a shelf of exercises
Practice — a session clock and a shelf: scales, arpeggios and sight reading.
Sight reading on a hand-rolled SVG grand staff
Sight reading — a real grand staff with key signatures and correct ledger lines, weighted toward your weakest notes.
The Layers tab: one-click split and layer builders
Layers — a split or a layer in one click, with the full editor underneath.
The Stats tab: activity calendar and key heatmap
Stats — a year of activity, a keyboard heatmap, and the keys you actually play in.

Architecture

One process, four threads, and a strict rule about which of them may be slow.

  piano ──USB──> rtmidi callback thread ──> FluidSynth (C render thread) ──> WASAPI ──> speakers
                          │                                     ▲
                          │ bounded deque                       │ sequencer (metronome)
                          ▼                                     │
                  asyncio drain @60Hz ──> websocket ──> browser UI

The MIDI callback routes a note and appends a tuple. It never logs, locks, allocates a dict, or touches a socket. Zone changes build a whole new routing table and swap it in one atomic assignment, which is why there is no lock anywhere near the hot path. If the UI stalls, the queue sheds its oldest events and a 1 Hz heartbeat carrying the engine's own held-note set puts the display back in sync.

  • keys.py — the launcher.
  • backend/__init__.py — load-bearing: sets the GIL switch interval and PATH before anything can import FluidSynth.
  • backend/engine.py — the Synth, the zone routing table, the hot path. backend/midi_in.py — rtmidi + hotplug.
  • backend/hub.py — the bounded queue between the callback thread and everything else.
  • backend/metronome.py — sequencer scheduling. backend/music.py — spelling, intervals, chord detection.
  • backend/store.py / practice.py — SQLite practice log and the idle-aware clock.
  • frontend/ — app.js shell + websocket, keyboard.js the keyboard component, instrument.js the key-count picker, views/*.js, style.css.

Deeper notes: docs/ARCHITECTURE.md (the invariants and why they exist), docs/HARDWARE.md (every measured number, and the settings that fail silently), docs/ROADMAP.md (what's built and what's deliberately skipped), docs/PACKAGING.md (building the Windows app, the three ways PyInstaller lies to you, and how an update replaces it without touching your data).

Troubleshooting

Run these in order — each is more specific than the last, and the first needs no virtualenv at all.

python tools\midi_probe.py                        # zero dependencies. Is the piano reaching Windows?
.venv\Scripts\python tools\audio_check.py         # is the audio path good?
.venv\Scripts\python tools\engine_check.py        # zones, presets, drum kits, metronome timing
.venv\Scripts\python tools\pipeline_check.py      # the whole app, no piano required
.venv\Scripts\python tools\frontend_check.py      # ES module syntax + asset wiring

"Everything else went silent." You are in exclusive mode. It gives Keys sole ownership of the output device, which is exactly where the 3 ms comes from — while it runs, nothing else can play through that device, and a browser will report an audio rendering error rather than a volume problem. This is not the default; Settings → Audio output → Shared hands it back. To keep 3 ms and your music, pin Keys to an output Windows is not using. Closing Keys always releases the device.

Every note has the same velocity. That's the instrument, not the app: most digital pianos ship with a Touch Sensitivity setting that transmits a constant velocity regardless of how hard you play. The Play → Touch response meter tells you live, and on a Yamaha P-45/P-71 the fix is to hold [GRAND PIANO/FUNCTION] and press the white key immediately left of middle C.

engine_check and pipeline_check open the audio device and make noise on purpose, so they refuse to run while Keys is up — close it first, or pass --force if you know what you're doing.

Development

.venv\Scripts\python tools\pipeline_check.py    # end-to-end: engine, hub, websocket, practice, sight reading
.venv\Scripts\python tools\frontend_check.py    # every ES module parses; needs Node for the syntax pass

The full suite is eighteen checks; the thirteen that need nothing plugged in and no browser carry 947 assertions between them (ui_check also wants Playwright). See CONTRIBUTING.md. There is no bundler, no transpiler and no node_modules — editing frontend/ and reloading the page is the whole loop.

License

MIT © TerraByte Solutions LLC. That covers the code in this repository.

A release build also ships work that is not ours, under its own terms:

Bundled Licence Notes
FluidSynth LGPL-2.1 Shipped as loose .dlls beside the executable, which is why the build is --onedir and not --onefile — the licence requires the libraries stay replaceable.
Verovio LGPL-3.0 frontend/vendor/, with COPYING.txt and COPYING.LESSER.txt beside it. Engraves the sheet music.
GeneralUser GS by S. Christian Collins free to distribute, including inside an application; the SoundFont itself may not be sold The 31 MB of sound.

Everything else the app depends on — FastAPI, uvicorn, pyfluidsynth, python-rtmidi, pywebview — is installed from PyPI under its own permissive licence and is not redistributed here in source form.

TerraByte Solutions LLC
An open-source project by TerraByte Solutions LLC

About

A MIDI piano practice workspace for Windows. 3 ms from key to sound, 190 instruments, splits and layers, a loop station, sheet music, and stats that quietly learn what you actually play.

Topics

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages