- Python 51.1%
- CSS 18.2%
- HTML 14.4%
- JavaScript 11.7%
- Nix 4.6%
Notes, intervals, key signatures, and notes-in-a-key on a grand staff, introduced landmark-first and explained relative to alto clef. Answers come from MIDI, the on-screen piano, or letter keys; the page grades and times them and suggests a rating. Built on Hanzi Friend's stack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| static | ||
| templates | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| app.py | ||
| auth.py | ||
| curriculum.py | ||
| db.py | ||
| exercises.py | ||
| flake.lock | ||
| flake.nix | ||
| music.py | ||
| pow.py | ||
| quiz.py | ||
| README.md | ||
| score.py | ||
| serve.py | ||
| stats.py | ||
Stave Friend
A spaced repetition app for learning to read piano music, built on the same bones as Hanzi Friend (Flask, htmx, FSRS, SQLite). It's tuned for someone who already sight-reads alto clef and needs treble and bass.
What it teaches
Concepts are introduced in a fixed order (curriculum.py), one card each:
- Notes on the grand staff, starting from landmarks (treble G, bass F, middle C, the Cs an octave out) and filling in by step, treble and bass alternating. Ledger lines and black keys come later.
- Intervals (2nd to octave), read by shape: odd intervals stay line-to-line or space-to-space, even ones switch.
- Key signatures, then notes in a key, where the signature, not an accidental, makes the note sharp or flat.
Every note intro and answer explains the spot in terms of alto clef (treble reads one letter down from alto, bass one letter up). When a wrong answer is the alto reading of that spot, the app says so.
The Drill page plays short melodies from notes you know. Drills aren't scheduled; they're for fluency once the notes are in.
Answering
- Play the key on a MIDI keyboard (Web MIDI; Chromium-based browsers work out of the box, Firefox asks for a site permission).
- Click the on-screen keyboard.
- Type the letter, with Shift for the black key above. Typed answers check the letter, not the octave.
The page grades the answer and times it, then suggests an FSRS rating: wrong is Again, right letter in the wrong octave is Hard, and a right answer is Easy, Good, or Hard by speed (under 2.5s, under 6s, slower). Space accepts the suggestion; 1-4 picks a rating yourself. Interval and key questions answer by number key.
Configuration
Environment variables (a .env file works for development):
FLASK_SECRET-- session and proof-of-work signing keyINVITE_CODE-- enables registration; leave unset to keep it closedPOW_DIFFICULTY-- leading zero bits for the login proof-of-work (default 15)STAVE_DATA_DIR-- wherestave.dblives (default.)
Running
Development: nix develop, then python app.py (port 8090).
Tests: pytest tests, or nix flake check.
Production: nix run, or on NixOS import nixosModules.default and set:
services.stave-friend = {
enable = true;
port = 8090;
environmentFile = "/run/secrets/stave-friend.env";
};
The service listens on localhost; front it with your reverse proxy. Web MIDI needs a secure context, so serve it over HTTPS (localhost is fine for development).
Credits
Notation is drawn with Bravura
(SIL Open Font License, see static/fonts/Bravura-OFL.txt).