- Svelte 41.8%
- JavaScript 32.3%
- HTML 21.5%
- CSS 1.9%
- Dockerfile 1.3%
- Other 1.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The button beside zoom opens the page chooser and saves the picked pages straight away through PUT /api/resources/:id/pages, without the editor. Player and app shortcuts now ignore keys while the chooser is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| docs/superpowers | ||
| practice-room | ||
| server | ||
| shared | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| TODO.md | ||
| vite.config.js | ||
SaxPrax
Sheet music and backing tracks in one place. A tutor sends a PDF and a few audio tracks each week. SaxPrax keeps each set together as a piece, so a student can read the music, play along, mark the hard bits and loop them.
Status
Version 0.1, working and self-hosted for a single family. It has no login. API tests run with npm test. See TODO.md for known issues and open decisions.
Features
Library
- Pieces with a title plus optional instrument, tutor, year, class, book or source, and teacher's notes. The free-text fields suggest values already in use.
- Search over title, notes and book, plus filters by instrument, tutor, year and class. Put pieces in your own order by dragging the grip next to each one (or Alt+↑ / Alt+↓). New pieces go on top.
- The last piece opened and whether the library is shown are remembered. Toggle the library with B.
Sheet music
- PDF pages are drawn with pdf.js, sharp on high-DPI screens.
- Zoom from 50% to 250% with the buttons, a trackpad pinch or a touch pinch. The zoom level is remembered.
- Print the PDF, or show the music full screen (F).
- Pick the pages for your instrument when a PDF holds several parts. Only those show and print, and a toggle in the zoom controls shows them all.
- If a PDF can't be drawn, you get a link to open the file instead.
Player
- Several tracks per piece, such as "With sax" and "Play along". Switching tracks keeps your place and keeps playing. Click the current track's button to rename it.
- A waveform, worked out on the server at upload. Click or drag on it to seek.
- Play and pause, back or forward 5 s, back to the current marker, and loop.
- Speed from 50% to 125% with pitch kept the same, plus a lead-in of 0 to 4 s before a marker.
- Lock-screen and headphone controls through the Media Session API.
- The player can shrink to a few controls floating over the music (P).
Markers
- Each track has its own markers. Add one at the current position with M.
- Markers are numbered by time, so their numbers change when you reorder them. Drag a marker on the waveform to move it.
- Jump to a marker with 1–9 or by clicking its chip. Rename it, move it to the current position, or delete it.
- Loop runs from the active marker to the next one, or over the whole track when no marker is active.
Adding and editing pieces
- Upload one PDF (up to 25 MB) and MP3, M4A or WAV tracks (up to 50 MB each), with progress bars. File types are checked from their content, not their extension.
- Or paste a link (YouTube, SoundCloud, Vimeo, Bandcamp and other sites yt-dlp supports) and the server downloads its audio as a track, up to 30 minutes and 50 MB. The track keeps a link back to where it came from.
- Track names are guessed from the file names, for example "Slow Tempo (80bpm)" or "Play along". Tracks can be renamed, reordered and removed.
- Encrypted PDFs that open without a password are stored with the encryption removed. PDFs that need a password to open are refused.
- Deleting a piece needs a second click to confirm. Files no piece uses are cleaned up after 6 hours.
Everywhere: light and dark themes, layouts for phone and iPad, and noindex headers and robots.txt, because the material is copyrighted.
Keys
Space play/pause · M add marker · L loop · 1–9 jump to marker · ←/→ 5 seconds · F full screen · B library · P full or collapsed player
Run
mkdir -p data # must exist and be owned by PUID/PGID (default 1000:1000)
docker compose up -d --build
The first start after updating to this version removes encryption from any stored PDFs, in place. Copy ./data somewhere safe before you deploy it.
Open http://<host>:8380. Change the port with SAXPRAX_PORT=9000 docker compose up -d.
There is no login. Anyone who can reach the port can view, edit and delete. Keep the port on the LAN. For access from outside, put Pangolin in front with authentication turned on for this resource.
Data and backups
Everything lives in ./data: saxprax.db (SQLite) and files/ (uploads). Back up the whole folder. For a consistent copy while it's running, use sqlite3 data/saxprax.db ".backup backup.db", or stop the container first.
Development
You don't need Node on the host; run it in Docker:
docker run --rm -v "$PWD":/app -w /app -u 1000:1000 node:22-bookworm-slim npm install
docker run --rm -v "$PWD":/app -w /app -u 1000:1000 node:22-bookworm-slim npm run build
Run the tests in the dev image, which has Node, qpdf and ffmpeg (link downloads are tested with a fake yt-dlp):
docker build --target dev -t saxprax-dev . # Node + qpdf + ffmpeg
docker run --rm -v "$PWD":/app -w /app -u 1000:1000 saxprax-dev npm test
For manual testing, run against ./data-test, never ./data.
server/: Fastify, SQLite (better-sqlite3, with schema migrations indb.js), and ffmpeg/ffprobe for durations and waveform peaks, qpdf to remove PDF encryption, pdf-lib to trim PDFs.- Links are downloaded with yt-dlp, bundled in the image (with deno, which it needs for YouTube). The server keeps an updated copy in
data/bin/yt-dlp, checked at start-up and daily, so when YouTube downloads break, restarting the container usually fixes it. A broken copy is replaced with the bundled one automatically. SetYTDLP_PATHto use a different binary. The server downloads and runs new yt-dlp releases from GitHub (that is how-Uworks), with the same trust as installing yt-dlp yourself. web/: Svelte 5 and pdf.js, built with Vite intodist/.npm run devproxies/apiand/filesto the server on port 8080.shared/: code used by both the server and the browser.practice-room/: the original spec and prototype, kept for reference. The app departs from the spec on purpose in a few places (no login, a horizontal waveform in a bottom dock); see TODO.md.