https://bell-kevin-practiceb-40zm.bolt.host
Practice Buddy turns MusicXML or a printed sheet-music scan into an interactive practice player. It requires no account, keeps direct score imports on the device, and uses only FLOSS application components. The frontend is a static Vite React single-page application that can be published by Bolt or any static web host.
- Open
.musicxml,.xml, and compressed.mxlscores. - Drag and drop a file or choose one from a phone, tablet, or computer.
- Use the rear-camera picker on supported Android and iOS browsers.
- Upload a PDF or image produced by a physical scanner.
- Render notation with OpenSheetMusicDisplay.
- Play synthesized notes with Tone.js and the browser Web Audio API.
- Change tempo from 30 to 220 BPM, seek, restart, enable a metronome, and loop a range of measures.
- Zoom the score and export the current score as MusicXML.
- Reopen or remove recent scores stored in the browser.
- Try the included public-domain sample.
No login, cookie session, hosted database, or Supabase project is required.
| Input | Where it is processed | Requires the OMR worker? |
|---|---|---|
.musicxml, .xml |
In the browser | No |
.mxl |
Decompressed in the browser | No |
| Sent directly from the browser to the Audiveris worker | Yes | |
| PNG, JPEG, WebP, TIFF, BMP, HEIC, HEIF | Sent directly from the browser to the Audiveris worker | Yes |
| Phone camera photo | Same direct worker path as above | Yes |
Imports are limited to 25 MB. An extracted MusicXML document from an .mxl
archive is limited to 40 MB.
A web page cannot directly operate every physical scanner. Scan the page with the scanner's own software, save it as a supported PDF or image, and upload that file. On mobile, Take a photo asks the browser for its environment-facing camera; the exact interface is controlled by Android, iOS, and the browser.
- Open Practice Buddy. There is no sign-up step.
- Drop a file onto Add your score, choose a file, use Take a photo, or load the sample.
- For a photo or PDF, wait while the configured Audiveris worker recognizes the page. MusicXML/MXL imports skip the network and recognition step.
- Press Play. The first press also grants the browser permission to start Web Audio.
- Adjust the tempo, seek through the score, turn on the metronome, or select a measure loop.
Keyboard shortcuts in the practice view are Space for play/pause and Home
to restart.
Practice Buddy has a fast, entirely client-side score path and an optional recognition path:
MusicXML / XML / MXL
-> browser validation and optional JSZip extraction
-> IndexedDB on this device
-> MusicXML parser
-> OpenSheetMusicDisplay + Tone.js
Camera / image / PDF
-> browser POST to VITE_OMR_SERVICE_URL/recognize
-> Audiveris worker
-> Audiveris returns MXL/MusicXML
-> the same browser storage, rendering, and playback path
The static React entry point and global styles live in src/, with the main UI
in components/practice/. Score import and on-device persistence live in
lib/scores/; parsing and audio scheduling live in lib/music/. The
separately deployable FLOSS recognition worker is in omr-service/.
This split keeps MusicXML playback responsive and makes the public site a small set of static files. Audiveris is a JVM application with substantially higher CPU and memory needs, so it runs on a container host rather than inside the static site.
- MusicXML, XML, and MXL imports are read locally and saved only in that browser's IndexedDB. They are not uploaded.
- Recent-score storage is per browser profile and per origin. Removing a recent score or choosing Clear all removes Practice Buddy's IndexedDB copy; it does not delete the original file from the device.
- If IndexedDB is unavailable, the current score can still open in memory but may not be saved for later.
- Photos, images, and PDFs leave the device when recognition is enabled. The
browser sends them directly to
VITE_OMR_SERVICE_URL. The included worker uses a unique temporary directory and removes it after the response or an error. - A production worker host or reverse proxy may retain access logs or request metadata. Configure its logging and retention policy to match the privacy promise you publish.
Only upload or scan music that you have the right to use.
- Direct score imports do not wait for a network round trip.
- JSZip is loaded only for compressed MusicXML.
- Audio scheduling and playback run in the browser.
- Recognition uploads and results are not cached by the included worker.
- Upload and extraction limits prevent accidentally loading extremely large documents.
OMR is the expensive path. For the best recognition speed, use a sharp, high-contrast, tightly cropped image of one straight page. A warm worker near the user is faster than a container that must cold-start. Give the worker enough CPU and memory, but keep its concurrency low to prevent simultaneous Audiveris jobs from exhausting the host.
- Optical music recognition is probabilistic. Always compare recognized notation and playback with the source.
- Audiveris targets printed common Western music notation. Handwritten scores are not supported by the included worker.
- Multi-page, dense, skewed, shadowed, low-resolution, or unusual scores can be slow or fail.
- Playback focuses on pitched notes, rests, ties, time signatures, parts, transposition, and MusicXML tempo changes. Repeat roadmaps, dynamics, articulations, ornaments, lyrics, instrument changes, and other advanced engraving details may render but are not guaranteed to affect playback.
- Playback uses a simple synthesizer; it is a practice reference, not a performance-quality interpretation.
- Browser camera capture and Web Audio behavior vary by device. HTTPS is required for normal production camera behavior.
- Node.js 20.19 or newer; a current Node.js LTS release is recommended
- npm
- Docker or Podman only if local photo/PDF recognition is needed
Install dependencies and start the Vite development server:
npm ci
npm run devOpen the local URL printed by Vite, normally http://localhost:5173. MusicXML, XML, MXL, local history, notation, and playback work without environment variables.
Useful commands:
npm run dev
npm run typecheck
npm run build
npm run previewnpm run devstarts the development server.npm run typecheckchecks TypeScript without emitting files.npm run buildproduces the static production site indist/.npm run previewserves the built site locally for final verification.
Copy the environment template to .env.local:
Copy-Item .env.example .env.localBuild and start the worker from the repository root:
docker build -t practice-buddy-omr ./omr-service
docker run --rm -p 8000:8000 \
-e OMR_ALLOWED_ORIGINS=http://localhost:5173 \
practice-buddy-omrThen set this in .env.local and restart npm run dev:
VITE_OMR_SERVICE_URL=http://localhost:8000
The included worker image currently targets linux/amd64 because it installs
Audiveris's Ubuntu x86-64 release package. See
omr-service/README.md for endpoint, limits, CORS, and
production-hardening guidance.
The Vite build is static and requires no site runtime or server functions.
Bolt/Netlify should run npm run build and publish dist/; those values are
also checked into netlify.toml.
-
Put this repository on public GitHub and import it as a new Bolt project.
-
Let Bolt install dependencies and run
npm run build. -
Preview the generated app, then publish it. Bolt publishes the contents of
dist/. -
With no environment variables, MusicXML/XML/MXL rendering and playback are fully functional.
-
To enable photo and PDF recognition, deploy
omr-service/on a separate container-capable host. -
Set the public frontend build variable to the worker's HTTPS base URL:
VITE_OMR_SERVICE_URL=https://your-worker.example -
On the worker, allow the exact published site origin:
OMR_ALLOWED_ORIGINS=https://your-practice-buddy.example -
Rebuild and republish after changing a
VITE_variable because Vite embeds it into the generated browser files at build time.
VITE_OMR_SERVICE_URL is public by design and must not contain credentials.
The browser cannot safely keep an OMR_SERVICE_TOKEN or any other shared
secret. Therefore, direct-browser mode must leave OMR_SERVICE_TOKEN unset.
CORS controls which browser origins may read responses, but it is not
authentication and does not block non-browser clients. Protect the public
worker with HTTPS, strict request-size limits, per-IP rate limits, abuse
monitoring, and a low concurrency cap at its reverse proxy.
Bolt and Netlify are deployment conveniences, not proprietary application dependencies. The static frontend can be hosted by any static web server, and the worker can run on any compatible OCI-container host.
Practice Buddy's original code is free/libre and open-source software licensed
under GNU AGPL version 3 only (AGPL-3.0-only). See LICENSE.
If you run a modified version as a network service, review section 13's source
availability requirement.
Third-party components remain under their own FLOSS licenses. The main
dependencies and source links are recorded in
THIRD_PARTY_NOTICES.md; exact versions are pinned in
package-lock.json and omr-service/requirements.txt.
Contributions are welcome through the public repository. By contributing, you
agree to license your contribution under AGPL-3.0-only.