Phase 4: drone + T6/E-series + polish + deploy + README
Add drone synth + DronePlayer, wire T6 (mode over drone) and E1-E3 info pages (E3 carries a drone). Screen Wake Lock while any click/drone plays. Vitest suite for scheduler timing (no drift over 5 min) and music-layer correctness (triads, CAGED, diatonic, fretboard). Deploy artifacts: public/_redirects, multi-stage Dockerfile + nginx.conf (SPA fallback, immutable asset caching, gzip), .dockerignore. README covering build, Cloudflare Pages / nginx / Docker deploy, and the deep-link URL format. prefers-reduced-motion honored throughout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# Guitar Practice Helper
|
||||
|
||||
A static web app that turns the drill codes from my Obsidian practice wiki (R1, F2,
|
||||
T3, …) into interactive helper pages: click tracks, pattern grids, chord & fretboard
|
||||
diagrams, and a drone. Open a drill link from the wiki on phone or desktop, press
|
||||
play, practise for ~5 minutes. No accounts, no backend, no analytics.
|
||||
|
||||
Built with **Svelte 5 + Vite** as a fully static SPA. Music theory is powered by
|
||||
[`tonal`](https://github.com/tonaljs/tonal). Timing uses a Web Audio lookahead
|
||||
scheduler so the metronome never drifts.
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev # local dev server
|
||||
npm run check # svelte-check (typecheck)
|
||||
npm test # vitest (scheduler timing + music-theory correctness)
|
||||
npm run build # typecheck + production build to dist/
|
||||
npm run preview # serve the built dist/ locally
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
The build is fully static (`dist/`). Because the app uses **path routing**
|
||||
(clean URLs like `/drill/R3`), the host must serve `index.html` for unknown paths
|
||||
(SPA fallback), and the base path is baked in at build time.
|
||||
|
||||
### Base path
|
||||
|
||||
- Dedicated domain / served at the root: default, `base = '/'`.
|
||||
- Subpath (e.g. `https://example.com/practice/`): build with
|
||||
`VITE_BASE=/practice/ npm run build`.
|
||||
|
||||
### Cloudflare Pages
|
||||
|
||||
Framework preset: none. Build command `npm run build`, output directory `dist`.
|
||||
`public/_redirects` (already included) provides the SPA fallback:
|
||||
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
For a subpath, set the `VITE_BASE` environment variable in the Pages build settings.
|
||||
|
||||
### Docker + nginx
|
||||
|
||||
A multi-stage `Dockerfile` (Node build → `nginx:alpine`) with the correct config
|
||||
(`nginx/default.conf`: SPA fallback via `try_files`, immutable caching for hashed
|
||||
assets, `no-cache` for the HTML shell, gzip):
|
||||
|
||||
```bash
|
||||
docker build -t guitar-practice . # root deploy
|
||||
docker build --build-arg VITE_BASE=/practice/ -t guitar-practice . # subpath
|
||||
docker run -p 8080:80 guitar-practice # http://localhost:8080
|
||||
```
|
||||
|
||||
### Existing nginx server
|
||||
|
||||
Serve `dist/` and add the SPA fallback to your location block:
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
```
|
||||
|
||||
## Deep-link URL format
|
||||
|
||||
The whole point — link to any drill directly from the wiki:
|
||||
|
||||
```
|
||||
https://<host>/<base>drill/<CODE>
|
||||
```
|
||||
|
||||
Optional query parameters override the stored/default settings:
|
||||
|
||||
| Param | Applies to | Example |
|
||||
|------------|-----------------------|----------------|
|
||||
| `bpm` | click-track drills | `?bpm=66` |
|
||||
| `variant` | drills with variants | `?variant=2-3-2` |
|
||||
| `key` | T2, T4 | `?key=D` |
|
||||
| `root` | T3, T5, T6, L4 | `?root=A` |
|
||||
| `interval` | T5 | `?interval=P5` |
|
||||
| `mode` | T6 | `?mode=dorian` |
|
||||
| `pair` | R4 | `?pair=g-c` |
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
https://example.com/practice/drill/R3?bpm=66&variant=2-3-2
|
||||
https://example.com/practice/drill/T6?root=A&mode=dorian
|
||||
https://example.com/practice/drill/R4?pair=f-bm
|
||||
```
|
||||
|
||||
Unknown codes resolve to a friendly “unknown drill” page linking back to the index
|
||||
(`/` shows all drills grouped by category).
|
||||
|
||||
## Drills
|
||||
|
||||
| Code | Name | Helper |
|
||||
|------|------|--------|
|
||||
| R1 | 16th-note strum, mute the off-beats | metronome + strum grid |
|
||||
| R2 | Accent patterns | metronome + strum grid |
|
||||
| R3 | 7/8 loop (drop to 4/4 live) | metronome + strum grid |
|
||||
| R4 | One-minute chord changes | countdown + tap counter + chord diagrams |
|
||||
| R5 | Palm-mute chugging | metronome + strum grid |
|
||||
| F1 | Travis picking | metronome + picking lane + chords |
|
||||
| F2 | PIMA arpeggio over I–V–vi–IV | metronome + picking lane + chords |
|
||||
| F3 | Thumb-independent bass + melody | metronome + picking lane |
|
||||
| T1 | Name every note on one string | metronome prompter + fretboard |
|
||||
| T2 | CAGED shapes | fretboard, prev/next |
|
||||
| T3 | Triads on a 3-string set | chord diagrams, inversions |
|
||||
| T4 | Diatonic chords of a key | chord diagram row |
|
||||
| T5 | Intervals from a root | fretboard |
|
||||
| T6 | Mode over a drone | drone + fretboard |
|
||||
| L1 | Alternate picking builder | metronome + picking lane |
|
||||
| L2 | Legato runs (triplets) | metronome + picking lane |
|
||||
| L3 | String skipping | metronome + picking lane |
|
||||
| L4 | Bends and vibrato | reference tone |
|
||||
| E1 | Interval recognition | info |
|
||||
| E2 | Chord quality recognition | info |
|
||||
| E3 | Play along by ear | info + drone |
|
||||
|
||||
## Keyboard (desktop)
|
||||
|
||||
- **Space** — play / pause
|
||||
- **↑ / ↓** — BPM ±2
|
||||
|
||||
## Notes
|
||||
|
||||
- Audio unlocks on the first tap/keypress (mobile autoplay policy).
|
||||
- Per-drill settings (BPM, variant, key, best scores) persist to `localStorage`
|
||||
under the `gph:` namespace.
|
||||
- Screen Wake Lock keeps the phone awake while a click or drone is playing.
|
||||
- Respects `prefers-reduced-motion`: the beat still advances, without the glow/swing.
|
||||
Reference in New Issue
Block a user