discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
sirged/fermata · source
Snapshot 0 · 2026-11-04T12:47:28Z · Python · MIT
Content fingerprint
d823358361cfc96ebde1d217f250e83dd333a155db435ce7c123d75727a1a54eGitHub description at capture
Self-hosted sheet music server: organize, search, and practice from your own library — PDF reader plus interactive notation/tab rendering
README at capture
<p align="center"> <img src="assets/banner.png" alt="" width="100%" /> </p> <h1 align="center"> <img src="assets/logo.png" alt="" width="72" align="center" /> Fermata </h1> **A self-hosted sheet music server.** Point it at a folder of sheet music, and get a fast, beautiful library you can browse, search, tag, and practice from — on any device on your network. Think of it as the music-stand equivalent of Jellyfin or Calibre-Web: your files stay yours, on your hardware, organized the way you already organize them. ## Features - **Library scanning** — walks your music folder, extracts titles, composers, collections, and page counts from folder structure and embedded PDF metadata, and generates cover thumbnails for PDFs only. - **PDF practice reader** — continuous reading, keyboard/pedal page turns (any Bluetooth pedal that sends arrow keys works), remembers your last page per piece, and a dark "practice room" inversion mode. - **Notation / tab / both** — MusicXML files render interactively and can be switched between standard notation, tablature, or both staves at once, with audio playback, adjustable speed, and a moving cursor. Guitar Pro files are accepted by the scanner and handed to the same renderer's importer, which reads that format natively; a browser spec carries an original Guitar Pro 7 fixture through that whole path (see the format table below). The built-in synthesizer also drives practice tools: drag-select a passage on the score to loop it, plus a count-in. A "Download MIDI" control takes the rendered score's playback out of the browser as a standard MIDI file. The staff is themed to match the interface, lays itself out differently on a phone, a tablet on a stand and a desktop, and can be drawn dark for practising in the dark — see [how scores are rendered](docs/rendering.md) for what the renderer does, what that layer adds, and why this renderer. - **Tab out of a PDF** — engraved guitar PDFs carry their tab as real text and their rhythm in the music font's own glyphs, so Fermata reads both directly instead of guessing at pixels, and renders the result as a playable, editable staff beside the original page. The same editor works on a native MusicXML score too — turning a rest into a note, extending a selection across a run of notes so a passage's durations, dots, spelling or string change at once rather than one note at a time (fret entry, ties and voice moves stay per note), and editing the bars themselves: the key signature, the time signature, and inserting or deleting a whole bar. A meter the bar's music does not already add up to is refused, naming the bar and saying how many beats it holds against how many the meter asks for, rather than being applied over a bar that would then be wrong; deleting a bar that carries half of a repeat is refused for the same reason. It refuses to open at all on a score with more than one part, rather than silently editing just the first. Three music-font vocabularies are calibrated: Finale's Maestro, Sibelius's Opus, and any font following the SMuFL standard, which covers what free engravers such as MuseScore produce. A score drawn in something else still gives up its fret numbers, with the rhythm estimated from note spacing and labelled as such. The transcription is written as **MusicXML**, so it opens in other notation software rather than only here — see [the tab profile](docs/musicxml-tab-profile.md) for exactly what is written. Fret numbers, strings and chords come out reliably; how much of the rhythm survives depends on the engraving, and whatever stayed uncertain is listed with the staff rather than hidden. A bar that does not add up — because its voices were flattened into one, or a note was read short, or one was dropped — is reported as such, and that is a stated conformance rule of the profile, so any MusicXML tool finds the same bars from the file alone. Where one voice of a bar has to be filled out with silence for the bar to play in time, that silence is marked in the MusicXML, counted as missing rather than as read, and the bars it happened in are named. Scanned PDFs have nothing to read and are not transcribed. A tie is written where the curve joining its two notes was matched, and a note the engraving marks as a harmonic is written as one; a tie drawn across a system break is not matched, and the ends of it that were found are counted rather than half-written. A whole library, or one collection, transcribes in a single background pass rather than one score at a time. - **Instruments** — define what you actually play: any number of strings, any tuning including reentrant ones, a capo, and a reference pitch other than A440. Each string shows the note and frequency it sounds and can be played, because choosing between two positions for the same written note is done by ear, and on an unfretted instrument a heard pitch is the reference rather than a convenience. Presets cover guitars, basses, ukulele, violin, viola and cello. Transcription does not yet use a score's instrument — it still assumes a six-string guitar in standard tuning. - **Metronome, everywhere** — one click, available over a piece, on the practice page, and on a page of its own, because "I just want a metronome" is a real thing to want from a practice tool. Wherever it appears it arrives already set up for that context — over a piece, to that piece's tempo and time signature — and every pre-filled value is still adjustable, because a marking is sometimes wrong, sometimes aspirational, and frequently not the speed a passage should be practised at today. Tempo is settable as a proportion of the piece from 15% to 175% (far below half speed, because for a passage that is beyond you half speed is not slow enough) or as a fixed number, one beat per minute at a time. When a setting asks for a click slower or faster than one can actually be sounded, it shows the rate it is really clicking at and says plainly that it is at the end of its range, rather than leaving a percentage on screen that has stopped describing what you hear. It does not use the renderer's own metronome, which cannot run at a tempo different from the notes beside it — see [how scores are rendered](docs/rendering.md). - **Practice tracking** — a session timer per piece, with recently-practised and neglected views so the library reflects what you are actually working on. A session can record what the work was: which bars or pages, at what tempo, section work or a run-through, and how it went. Practice that is not a piece at all — technique, ear training, simply playing — is recorded the same way and in the same history ([the data model](docs/practice-data.md)). - **Hear a note, name it** — the first exercise: a pitch sounds, and you name it from four. The three you did not hear are chosen to be worth confusing — a semitone away, the same name an octave out, and one a step or two off — because four notes far apart teaches nothing. Hear it again as often as you like, before and after answering, and nothing counts that. With one instrument defined the drill draws from that instrument's own range; with several it uses a plain four octaves, because Fermata knows what you own and not which one is in your hands. Two counts are stated and nothing is graded: there is no accuracy percentage and no streak, and a note you could not name is what the practice consists of. The time lands in your practice history as ear training and counts towards a weekly goal like anything else. - **Fretboard drills** — *Fret to note* asks in both directions: a position is shown and you name what it sounds, or a note is named and you tap where it lies. *Chord flash cards* does the same for major, minor and seventh shapes, both open-position and moveable barre forms, with each chord's tones named from its own root rather than off a fixed table. Either can be narrowed to the strings, the fret range and the key you are actually working on, and that scope can be saved under a name and picked up again in either drill. Every answered question is stored as a structured row — which positions and which chords were missed, counted, never divided into an accuracy percentage — and the fretboard drill shows its most-missed positions back beside the questions as its own weak-spots panel ([the data model](docs/practice-data.md)). The time lands in your practice history like anything else. - **Weekly goals, and an honest review** — how many days you mean to practise and for how long, on what. While the week runs it says where you stand so the goal can still change it; afterwards it states plainly what happened and asks whether the goal was realistic. No streaks, no comparison with a better week, and nothing that grades you for a week that did not go to plan. - **Setlists** — ordered collections of pieces to work through: a gig set, a lesson plan, a practice rotation. Name one, add scores, and arrange the order by hand — the order is kept, not left to chance — then work through it in the ordinary viewer, with each piece's own practice progress shown beside it. A piece can be in several setlists at once; removing it from one, or deleting a setlist outright, never deletes the piece or its history. A piece you have sent to the trash stays in its setlists, marked, rather than becoming a dead link. - **Gig mode** — fullscreen, screen kept awake, oversized tap targets and half-page turns for a tablet on a music stand. - **Organize** — collections (from your folder layout), free-form tags, favorites, content-type labels (notation / tab / both), search by title, composer, source and series, and duplicate detection by file contents. A key signature, a tempo and a 1-5 difficulty rating can be set per score too — the key is filled in on its own from a transcription's decoded key when one is transcribed and left alone once set by hand — and the library grid filters by key and difficulty (a tempo range is available through the API). - **Reorganize, from the app** — move and rename scores, make and rename folders, move a batch in one go. The change is applied to the real file on disk and the score follows it, so its practice history, tags, goals and any transcription stay attached. Every bulk move shows you exactly what it will do before it does it, and nothing is ever overwritten. - **Delete that isn't destruction** — deleting a score moves its file to a trash folder inside your library and keeps everything hanging off it; Trash puts it back exactly as it was. Destroying it for good is a separate, deliberate second step that tells you what it will destroy — and even then, the practice you logged against it stays in your history. - **Upload** — drag files in through the browser; they land in your library folder and are indexed immediately. - **Single container** — SQLite inside, two volume mounts, no external services. - **A documented REST API** — everything above is one client of `/api`, which is documented, not just present: `/docs` for interactive Swagger UI, `/openapi.json` for the schema itself, generated from response models kept in sync with what each endpoint actually returns. Enough to script the library, log practice, or build a companion app against — see [the API guide](docs/api.md). A companion server, off by default, speaks the Model Context Protocol, an open standard for describing a set of tools to a program that reads them — a structured-data interface offering a fixed set of read-only tools built from that same API rather than a second copy of it; see [the deployment guide](docs/deployment.md#the-model-context-protocol-server). ## Quick start ```bash git clone <this repo> cd fermata mkdir -p library config # put some sheet music in ./library (PDF, MusicXML, Guitar Pro) BUILD_COMMIT=$(git rev-parse --short HEAD) BUILD_DATE=$(date -u +%Y-%m-%d) docker compose up --build -d ``` The two build variables stamp the image so `GET /api/version` can tell you which commit is running; without them it reports `dev`. Open http://localhost:8080 — your library is scanned automatically on startup, or hit **Scan library** in the sidebar. See [the deployment guide](docs/deployment.md) for backups, upgrading, and current limitations — read that before deciding whether this belongs on your home network. ### Volumes | Mount | Purpose | | --------------- | ---------------------------------------------- | | `/data/library` | Your sheet music files (read + uploads) | | `/data/config` | Database, thumbnails, cache — back this up | Fermata creates `/data/config` if it needs to, but never `/data/library` — a library folder that isn't there is usually a volume that didn't mount, and an empty one is indistinguishable from a library with nothing in it. It stops with an explanation instead, and recovers by itself once the mount appears. ## Development Backend (FastAPI, Python 3.12): ```bash cd server pip install -e . FERMATA_LIBRARY=../library FERMATA_CONFIG=../config uvicorn fermata.main:app --port 8080 --reload --no-proxy-headers ``` `--no-proxy-headers` turns off uvicorn's own X-Forwarded-For handling, which would otherwise let a request carrying that header rewrite what Fermata sees as its peer address - see docs/deployment.md's "Reverse proxy authentication" section for why that matters even for a plain dev server bound to loopback. Frontend (Svelte 5 + Vite, proxies `/api` to :8080): ```bash cd web npm install npm run dev ``` Everything to do with drawing a staff — appearance, responsive layout, and the renderer's quirks — lives in `web/src/lib/score-render.js`; components never touch the renderer directly. [docs/rendering.md](docs/rendering.md) explains that boundary and the decisions behind it. Tests: ```bash cd server pip install -e ".[dev]" python -m pytest -q ``` Tests that need real sheet music read it from `FERMATA_TEST_LIBRARY` and skip when it is unset. Setting `FERMATA_MUSICXML_XSD` to a local copy of the MusicXML 4.0 schema additionally validates the emitted transcriptions against it — see [the tab profile](docs/musicxml-tab-profile.md#checking-a-file). ## Formats | Format | How it reads | | --- | --- | | PDF, engraved with a tab staff | Practice reader, plus transcription to MusicXML — a playable staff beside the page, and a file other notation software reads ([profile](docs/musicxml-tab-profile.md)) | | PDF, engraved without tab | Practice reader; nothing to transcribe from | | PDF, scanned | Practice reader only — a scan holds no text or glyphs to read | | MusicXML (`.musicxml`, `.mxl`) | Interactive: notation / tab / both, audio, full fidelity, and hand-editable — a correction is kept as its own transcription row, the file on disk never rewritten | | Guitar Pro (`.gp3`–`.gp5`, `.gpx`, `.gp`) | Scanned and indexed, then handed to the renderer's own importer, which reads the format natively[^gp] | A PDF is a fixed rendering, so the notation/tab toggle belongs to the structured formats — and to a transcription made from a PDF, which is what the side-by-side view is for. There's a bundled demo (sidebar → *Notation/tab demo*) if your library is PDF-only so far. ### What transcription actually yields Measured 2026-10-14 over a 297-PDF personal library, on commit `eebca87`: | Measure | Result | | --- | --- | | PDFs with a readable tab staff | 293 of 297 (3 notation-only, 1 raster scan) | | Rhythm confidence | high 6, medium 44, low 243 | | Scores with every bar adding up | 6 of 293 | | Bars that do not add up | 5,259 of 10,762 (1,535 overfull and 4,152 short; a bar can be both. 3,605 of the short bars had a voice padded with silence.) | | Bars with nothing readable, emitted as rests | 20 | | Tuning read from the score | 100 of 293; the other 193 assume standard | These figures come from `server/tools/library_census.py` and are re-derived by running it over a library, not maintained by hand. [^gp]: Checked against a real, original Guitar Pro 7 (`.gp`) fixture: `web/test-fixtures/guitar-pro-import-fixture.gp`, a few original bars built with alphaTab's own exporter from the alphaTeX source committed beside it, never a borrowed arrangement. `web/tests/browser/guitar-pro-import.spec.js` uploads it through the real `/api/upload` path and the real scanner, opens it through the real `Viewer` → `TabViewer` → `score-render.js` path, and checks the bar count, note count and tuning the real importer actually produced. ## Roadmap - **Tuplets when transcribing** — the largest remaining gap between a transcription and a score you could practise from, and the reason bars still overfill once voices have been separated - **Grace notes when transcribing** — currently read as ordinary notes and given a duration they do not have, which overfills the bar they sit in - **A harmonic's DURATION** — the note is marked as a harmonic, but a diamond notehead's own value is still not calibrated, so a harmonic engraved as a half note reads as a quarter - **Instrument-aware transcription** — reading a score against the instrument it is written for, instead of assuming standard six-string tuning whatever the score says - **Interval and reach drills** — finding the third, the fifth or the octave from a given position, and widening a fret span on purpose. The fretboard drills that exist today test recall, not reach - **A two-page spread for wide and foldable screens** — being built under #307 ## License [MIT](LICENSE)