From 0d11570b58d8e718e9d28dc89847ebb9e1e54828 Mon Sep 17 00:00:00 2001 From: Jens Mohrmann Date: Mon, 14 Sep 2026 00:05:20 +0200 Subject: [PATCH] Add Phrase Mode requirements & implementation plan (draft) --- PHRASE_MODE_PLAN.md | 300 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 PHRASE_MODE_PLAN.md diff --git a/PHRASE_MODE_PLAN.md b/PHRASE_MODE_PLAN.md new file mode 100644 index 0000000..120534a --- /dev/null +++ b/PHRASE_MODE_PLAN.md @@ -0,0 +1,300 @@ +# Phrase Mode — Requirements & Implementation Plan + +**Status: DRAFT — awaiting approval ("Go") before implementation.** + +This document describes a proposed third game mode ("Phrase Mode") for the Sheet +Music Trainer. It is written as stakeholder requirements: what the mode does, +what it deliberately does **not** do, how it will be built, and which decisions +need the owner's sign-off. Section 8 lists the open decisions together with the +defaults I will assume if no feedback arrives in time — so an autonomous +overnight run can proceed without blocking on any of them. + +--- + +## 1. Vision + +Today the app trains **single notes**: one note appears, you play it, the app +confirms, repeat. This is great for building note-lookup reflexes, but real +sight-reading is about reading **sequences** — a bar or a few bars of connected +music, played in order, ideally in time. + +Phrase Mode adds exactly that, without disturbing the minimal feel of the +existing modes: + +- The staff shows **1–8 bars of single-voice music** (notes with real durations, + bar lines, key/time signature). +- You **play the notes one by one**; the app tracks your position and gives the + same kind of quiet, unobtrusive feedback as today. +- Two paces: **Step mode** (beginner — play each note correctly, no clock) and + **Tempo mode** (metronome — the phrase moves on the beat, hit or miss). +- Material comes from two sources: **rule-based generated melodies** (in a key, + scale or fretboard position of your choosing) and a **bundled library of fixed + exercises** (e.g. Giuliani studies, folk tunes) parsed at runtime from a + simple text file format. + +Zen mode, mic handling, virtual instruments, themes and all existing modes stay +as they are. Phrase Mode is *additive*: a third toggle next to "Sight Reading" +and "Ear Training". + +## 2. User stories + +| # | Story | +|---|--------| +| U1 | As a guitar student, I want the app to generate short melodies within a key and a fretboard position, so I practice reading connected lines instead of isolated notes. | +| U2 | As a beginner, I want a mode where the phrase waits for me: the next note is only "armed" after I played the current one, so I can go at my own pace. | +| U3 | As a more advanced student, I want the phrase to advance on the metronome, marking notes I hit in time vs. missed, so I practice real sight-reading under tempo. | +| U4 | As a classical player, I want to practice fixed material (e.g. Giuliani right-hand studies) from a library, so I rehearse real repertoire. | +| U5 | As a future contributor, I want exercises stored in a simple text format in the repo, so more pieces can be added later — found somewhere else, converted, or generated. | +| U6 | As a user of the existing modes, I expect Zen mode, hints, virtual instruments and themes to keep working unchanged in the new mode. | + +## 3. Requirements + +### 3.1 Core (MUST) + +- **R1 — Third game mode.** `gameMode: 'phrase'` alongside the existing two. + Header shows three toggles. Switching modes must not regress existing modes. +- **R2 — Multi-bar rendering.** The staff renders a phrase of 1–8 bars with + correct notation: key signature (reusing existing key machinery), time + signature, note durations (whole/half/quarter/eighth, dots if the library + needs them), bar lines. Reuses the per-instrument clef + guitar transpose + logic, incl. grand staff for piano. Notes fit the width; narrow screens show + fewer bars per system (wrapping into multiple systems). +- **R3 — Cursor & feedback.** The note you currently have to play is visually + marked (e.g. colored). Played notes get subtle pass/fail coloring. No big + popups per note — the "minimal feeling" of the existing modes must be kept. +- **R4 — Step mode (beginner).** The next note is armed only after the current + one was matched (same flicker-tolerant matching as today: `MatchTracker`, + 50 ms hold). Wrong notes simply don't advance (plus the existing "skip" + affordance to bail out of a note). +- **R5 — Tempo mode.** Metronome at the existing BPM setting; 1-bar count-in; + the cursor advances at each note's notated duration regardless of hit/miss. + A note counts as *hit* if the correct pitch is detected and held for the + match threshold at any point within its window. Missed notes are marked but + never stop the run. At the end: a compact summary (X/Y notes) with + *Retry* / *Next phrase*. +- **R6 — Generated melodies.** Rule-based generator producing short + single-voice melodies: all pitches within the chosen key/scale **and** the + instrument's playable note set (existing difficulty ranges — incl. custom + fret windows, i.e. "position playing" comes for free via the existing + custom_fret difficulty), durations summing exactly to full bars, sane + musical rules (see 4.1), deterministic under a seed → fully unit-testable. +- **R7 — Exercise library.** A bundled set of fixed exercises parsed at + runtime from the chosen file format (see 5), plus a picker in the mode's + UI (title list, grouped by source/difficulty). +- **R8 — Play-phrase preview.** A "Play" button (and keyboard shortcut) plays + the phrase through the existing `AudioEngine` so the user can hear it before + reading it — the phrase analog of the existing "Play Note". +- **R9 — Zen mode compatibility.** Phrase Mode honors Zen mode (hide chrome, + keep staff + minimal controls) and works in landscape/mobile layouts. +- **R10 — No regressions.** Existing tests pass; new logic is unit-tested; + build and lint stay green; README and TECHNICAL_REALIZATION.md updated. + +### 3.2 Nice to have in v1 (SHOULD — done only if time permits after the core) + +- S1 — End-of-phrase auto-advance ("practice loop"): after the summary, the + same phrase repeats (Retry) or the next one loads automatically. +- S2 — Scale drill material: runs/arpeggios in a selected key and position, + as either generated material or tiny bundled exercises (this is the + "scales in different positions" ask expressed in the simplest useful form). +- S3 — Repeat marks / looping a sub-range of a longer exercise. +- S4 — Ear-training variant of phrases ("listen to the phrase, then play it + back") — deliberately deferred, see 3.3. + +### 3.3 Explicitly out of scope for v1 (WON'T) + +- **Chords / multiple simultaneous voices.** The matching pipeline is + monophonic by design (single pitch per frame). The data model will keep a + `voice`-shaped structure so a polyphonic future is not blocked, but v1 + renders and matches single voice only. +- **Polyphonic pitch detection** (fundamental research territory). +- **Scoring history / statistics persistence.** +- **Importing arbitrary user files via file picker** — the library lives in + the repo; a user-facing importer (file picker → localStorage) is a small + follow-up once the format is proven. +- **MIDI import.** See 5 for the reasoning; can be added later as a + build-time/offline converter into the same internal model. + +## 4. Functional specification + +### 4.1 Melody generator (rule set for v1) + +Configurable via settings: key/mode, number of bars, rhythm complexity level, +and the existing difficulty range (which constrains the playable pitch set, +including fretboard position via the custom fret window). + +Rules (v1 — deliberately simple, testable invariants in parentheses): + +1. Pitch pool = scale of the chosen key/mode ∩ playable notes of the + instrument/difficulty. *(invariant: every generated pitch ∈ pool)* +2. Start on a stable degree (1, 3 or 5 of the scale); end on the tonic + (or dominant), placed on a strong beat. +3. Mostly stepwise motion (≈70% steps of a 2nd); leaps mostly 3rd/4th, + rare 5th/6th/octave; a leap is followed by a step in the opposite + direction (classic counterpoint heuristic — produces melodic, not + chaotic, lines). *(invariant: no two consecutive leaps > 4th without + an intervening step in opposite direction)* +4. No immediate repetition of the same pitch more than twice in a row. +5. Rhythm: per-bar patterns drawn from a complexity-tiered pool + (level 1: quarters; level 2: + paired eighths / half notes; level 3: + + dotted quarters & mixed). No rests in v1. Final note ≥ half note. + *(invariant: durations per bar sum to the bar exactly)* +6. Motivic shape: 4-bar phrases built as 2 bars of motif + 2 bars answering + (transposed/varied motif) — cheap to implement, dramatically improves + "sounds like a melody" impression. Optional at difficulty level 3 only. +7. v1 keys: all majors and minors from the existing key-signature list plus + major/minor **pentatonic**; additional modes (Dorian, Mixolydian, …) are + trivial follow-ups of the same scale machinery. + +### 4.2 Playback state machine + +``` +IDLE → (start) → COUNT_IN (tempo mode only) → PLAYING → DONE → (retry|next) → … +In PLAYING: cursor at note i, + step mode: wait for match(i) → color green → cursor i+1 + tempo mode: window(i) = [t_i, t_i + dur_i); at window end evaluate + MatchTracker state → green/red → cursor i+1 +``` + +Matching reuses `MatchTracker` unchanged (hold 50 ms, 150 ms flicker grace). +The mic is gated during speaker output exactly as today (`audioEngine. +isAudible()`), so the preview button and virtual instruments can't self-match. + +### 4.3 UI concept + +- Header: third toggle **"Phrases"** (naming: see 8.1). +- Main stage (non-zen): phrase staff; below it a compact status line + (step mode: "Play the highlighted note" / tempo mode: bar count-in + indicator + running hit/miss count); controls: Play (preview), + Restart, New phrase / Next exercise, plus the existing hint/virtual + instrument buttons which keep working (hint marks the current note's + fretboard position). +- End of phrase: one-line summary + Retry / Next. No modal, no confetti. +- Zen mode: staff + exit button only, as today. + +## 5. Fixed-material file format — recommendation + +The internal representation is a plain TypeScript model (notes with midi + +duration in beats, metadata) that both the generator and the library feed +into. The open question is the ** interchange format** for the library. + +| Option | Pros | Cons | +|--------|------|------| +| **ABC notation** ✅ recommended | Text: easy to bundle in git, to author by hand, to LLM-generate, to review in diffs; huge public-domain corpus (folk tunes in ABC everywhere); encodes bars, durations, key, **and note spelling** directly — maps 1:1 onto VexFlow; a subset parser is ~300 lines, fully unit-testable | Little pre-existing classical-pedagogy material in ABC (Giuliani studies must be transcribed — but arpeggio studies are extremely regular and transcribe quickly) | +| MIDI | Abundant files; import of anything | Binary; durations need quantization (grid inference); no spelling — must be re-derived; repeats/structure opaque; parser + quantizer ≈ same size as ABC parser with worse output | +| MusicXML | Notation-perfect | Heavy, verbose, no light-weight parser; overkill for monophonic v1 | + +**Recommendation:** bundle exercises as small `.abc` files in the repo +(`src/exercises/library/`), parsed at runtime by a small subset parser +(inline notes, durations, octaves, accidentals, bar lines, key/meter/time +headers, simple `|:` `:|` repeats expanded at parse time; no ties/grace +notes/tuplets in v1). Seed library (public domain — Giuliani died 1829): + +- Giuliani Op. 1 right-hand arpeggio studies (selection, transcribed) +- 1–2 Sor/Carcassi-style étude excerpts if the arpeggios go fast +- A handful of well-known public-domain folk melodies +- Scale/run patterns as tiny generated "built-ins" if useful + +MIDI stays possible later: an offline converter (Node script) MIDI → same +model can be added without touching the app. + +## 6. Technical plan + +New code, mirroring the existing layout: + +``` +src/ +├── music/ +│ ├── scales.ts # scale/mode definitions, key→pitch-class sets +│ ├── melodyGenerator.ts # rules → Phrase model (seeded RNG, pure) +│ └── NoteUtils.ts # extended: duration→VexFlow glyph mapping (shared) +├── exercises/ +│ ├── model.ts # Phrase / PhraseNote / ExerciseInfo types +│ ├── abcParser.ts # ABC subset → Phrase (pure, tested) +│ ├── library.ts # bundled .abc index + metadata +│ └── library/*.abc # the exercise files themselves +├── hooks/ +│ ├── usePhraseTrainer.ts # state machine from 4.2 (uses MatchTracker) +│ └── usePhraseClock.ts # audio-clock lookahead scheduler (tempo mode), +│ # patterned after the existing useMetronome +├── components/ +│ ├── PhraseSheetMusic.tsx # multi-bar VexFlow renderer w/ cursor+colors +│ ├── PhraseControls.tsx # mode-local buttons/status line +│ └── ExercisePicker.tsx # library picker (R7) +└── types/SettingsTypes.ts # new phrase-related settings +``` + +Integration approach in `App.tsx`: pitch detection stays exactly as-is and +produces `pitchData` once. If `gameMode === 'phrase'`, App renders the +`PhraseTrainer` subtree (staff + controls) fed by the same `pitchData`, mic +gate and audio engine; the existing single-note subtree is untouched. This +keeps `useGameLogic` clean and guarantees the existing modes are unregressed +by construction. + +Settings additions (all optional, with defaults — existing installs keep +working): + +```ts +phrase?: { + material: 'generated' | 'library'; + libraryId?: string; // selected exercise + key: string; // generator key/mode (e.g. 'G', 'Am', 'C-penta-maj') + bars: number; // 1–8 + rhythmLevel: 1 | 2 | 3; + pace: 'step' | 'tempo'; + bpm: number; // tempo mode; defaults to existing rhythm.bpm +} +``` + +Tempo-mode scheduling follows the proven metronome pattern (lookahead timer +scheduling against `AudioContext.currentTime`) so cursor movement doesn't +drift; note windows are derived from the same schedule, not from `Date.now()`. + +### Testing + +- `melodyGenerator.test.ts` — invariants from 4.1 (pool membership, bar sums, + leap rules, determinism under seed). +- `abcParser.test.ts` — golden strings for each syntax feature; error cases. +- `usePhraseTrainer` logic — extract pure reducer/clock math and test the + state machine transitions with a fake clock (step & tempo mode, hit/miss). +- Renderer gets a thin smoke test; visual QA happens in the browser. +- Full `npm run lint && npm run build && npm test` must pass before finishing. + +### Execution / cost control + +- Core architecture, integration and the clock/trainer logic: implemented + directly (these are the parts where mistakes are expensive). +- Delegate to cheaper subagents (pi supports spawning agents with a cheaper + model, e.g. GLM-5.3-Flash): ABC transcriptions of the Giuliani studies, + the parser/generator test corpora, the exercise picker UI and other + template-like UI work — each with tight, verifiable specs, and their output + re-checked by the main agent before merging. +- Each phase ends in a green build + commit, so the night produces usable + checkpoints, not one big risky blob. + +## 7. Implementation phases + +| Phase | Contents | Exit criterion | +|-------|----------|-----------------| +| **P1** | Model + scales + generator (rules 1–5) + multi-bar renderer + Step mode end-to-end (R1–R4, R6 core) | Playable generated melodies in step mode, no clock | +| **P2** | Tempo mode: phrase clock, count-in, per-window hit/miss, summary (R5) | Full generated-melody experience | +| **P3** | ABC parser + bundled library + picker (R7), Play-preview (R8) | Fixed exercises playable in both paces | +| **P4** | Motifs/pentatonic (rule 6–7), S1/S2 if time, Zen-mode polish, keyboard shortcuts, help text, README/TECH docs, final lint/build/tests, push | Night done | + +If time runs short, P1–P3 are the must-have spine; everything in P4 is +incremental and can be dropped mid-phase without leaving the app broken. + +## 8. Open decisions — defaults if no feedback + +| # | Decision | Default I'll assume | +|---|----------|---------------------| +| 8.1 | Mode name / labels | Third toggle labeled **"Phrases"**; existing two keep their current names ("Sight Reading", "Ear Training"). If you'd rather rename the pair (e.g. "Notes" / "Ear" / "Phrases"), say so — cosmetic change. | +| 8.2 | Library format | **ABC** (rationale in 5). MIDI deferred to a later offline converter. *If you feel strongly about MIDI as the primary format, this is the decision to veto.* | +| 8.3 | Tempo-mode strictness | Lenient: misses are marked, never abort. No per-note gating beyond the existing 50 ms hold. | +| 8.4 | Generator keys in v1 | Majors + relative minors from the existing key-signature list + major/minor pentatonic. Church modes: later. | +| 8.5 | Phrase length default | 4 bars, 4/4 only in v1 (3/4 comes free with the model; generator stays 4/4 unless trivial to add). | +| 8.6 | Hints in phrase mode | Reuse existing hint toggle: fretboard/piano hint marks the **current** note only. | +| 8.7 | Publishing | Repo `main` is currently 4 commits ahead of `origin`. Pushing `main` triggers the GitHub Pages deploy. Default: at the end of the night, push **everything** (your 4 commits + mine) after a green build, so the work isn't lost. Say "don't push" if you'd rather review first. | +| 8.8 | Seed library size | ~10–15 items (5–8 Giuliani arpeggio studies, 3–5 folk tunes, 1–2 technical patterns). Quality-checked, not bulk-dumped. | + +**Approval:** reply "Go" (optionally with edits/vetoes on the table above) and +I'll implement autonomously overnight following P1→P4.