No description
  • Svelte 41.8%
  • JavaScript 32.3%
  • HTML 21.5%
  • CSS 1.9%
  • Dockerfile 1.3%
  • Other 1.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Leigh ad773243bf Add a page chooser button to the sheet music controls
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>
2026-10-23 12:54:11 +08:00
docs/superpowers Merge library order and picked PDF pages into tracks from links 2026-10-23 12:06:44 +08:00
practice-room Initial SaxPrax app: Fastify + SQLite server, Svelte frontend, Docker Compose 2026-10-22 19:08:25 +08:00
server Add a page chooser button to the sheet music controls 2026-10-23 12:54:11 +08:00
shared Add /files/:id/pages to serve just the picked PDF pages 2026-10-23 10:44:06 +08:00
web Add a page chooser button to the sheet music controls 2026-10-23 12:54:11 +08:00
.dockerignore Address final review: refresh on failed reorder, early PDF size check, small fixes 2026-10-23 10:53:59 +08:00
.gitignore Split app construction from listen and add an API test harness 2026-10-23 10:32:13 +08:00
docker-compose.yml Initial SaxPrax app: Fastify + SQLite server, Svelte frontend, Docker Compose 2026-10-22 19:08:25 +08:00
Dockerfile Merge library order and picked PDF pages into tracks from links 2026-10-23 12:06:44 +08:00
package-lock.json Remove PDF encryption on upload and at startup; refuse password-protected PDFs 2026-10-23 10:40:31 +08:00
package.json Add /files/:id/pages to serve just the picked PDF pages 2026-10-23 10:44:06 +08:00
README.md Merge library order and picked PDF pages into tracks from links 2026-10-23 12:06:44 +08:00
TODO.md TODO: point the .dec leftover item at server/pdf.js 2026-10-23 10:55:19 +08:00
vite.config.js Initial SaxPrax app: Fastify + SQLite server, Svelte frontend, Docker Compose 2026-10-22 19:08:25 +08:00

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 in db.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. Set YTDLP_PATH to use a different binary. The server downloads and runs new yt-dlp releases from GitHub (that is how -U works), with the same trust as installing yt-dlp yourself.
  • web/: Svelte 5 and pdf.js, built with Vite into dist/. npm run dev proxies /api and /files to 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.