A bright, offline piano‑learning game for kids — plug in a MIDI keyboard and learn by playing. Falling notes, color‑coded keys, and honest, measured progress. No account, no tracking, nothing leaves your device.
"Doinu" means melody in Basque.
Live: https://endika.github.io/doinu/
Notes fall toward the keyboard; play the right key as each one lands. Every key has its own color (the Boomwhacker method), so a 6‑year‑old can follow along before they can read music.
- 🎵 Melody — a guided curriculum that unlocks the next exercise only when you've truly mastered the last one.
- 🐢 Practice (wait) — the score freezes at the hit line until you play the right note, so you can learn a piece at your own pace.
- 🎶 Songs — a public‑domain song library (Twinkle, Ode to Joy, Jingle Bells…), playable with the right hand, left hand, or both.
- 📂 Load MIDI — bring your own
.midfile; the app splits it into right/left hands. - 🔁 Echo — the app plays a phrase, you repeat it back.
- 🧠 Memory — a growing Simon‑style sequence.
- 🔎 Find the note / 👂 Listen & play — keyboard geography and ear training.
- 🥁 Tap the beat — rhythm practice to a metronome.
- ✨ Free play — a no‑pressure sandbox.
Each attempt records real metrics — accuracy, note‑find speed, tempo and timing — and a mastery map (locked → in progress → mastered) with a hard criterion. The Progress screen shows parents genuine improvement, not invented bars.
Everything stays on your device. No backend, no account, no analytics, no cookies. Practice data lives in your browser's local storage. (Microphone input for tablets without MIDI is on the roadmap and will also run fully on‑device.)
- A MIDI keyboard connected to the device.
- A browser with Web MIDI — Chrome/Edge on a laptop, or Chrome on Android. (Note: iOS Safari has no Web MIDI; tablet support via the microphone is planned.)
Vanilla TypeScript + Canvas, Vite, an offline PWA (vite‑plugin‑pwa), Web MIDI and Web Audio. No framework, no backend. Tested with vitest.
npm install
npm run dev # local dev server
npm run lint # eslint (src + tests)
npm run type:check # tsc --noEmit (src + tests)
npm run test:run # vitest run
npm run build # production build (dist/)MIT — see LICENSE.