{"slug": "i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me", "title": "I Built a Real-Time Collaborative Sheet Music Editor — Here's What MusicXML and CRDTs Taught Me", "summary": "A developer built ScoreTail, a browser-based real-time collaborative sheet music editor, using MusicXML and CRDTs. The project relied heavily on AI-assisted coding, with Claude Code writing about 90% of the code, and a round-trip test harness of 250 real-world scores to catch regressions. The developer highlighted the challenges of MusicXML's backup/forward elements and the complexity of applying CRDTs to musical scores.", "body_md": "I wanted a \"Google Docs for sheet music\": open a browser tab, start writing notation, share a URL, and have someone else edit the same score with you in real time. Nothing like that existed. MuseScore, Finale, and Sibelius are all excellent, but they're desktop apps built for people who already know notation software inside out.\n\nSo I built [ScoreTail](https://scoretail.com), a browser-based score editor, mostly alone, with a lot of AI-assisted development. This post is about the parts that turned out to be genuinely hard: the MusicXML format, real-time collaborative editing over CRDTs, and rendering performance.\n\nMusicXML is a huge spec — pitch, duration, voices, staves, backup/forward, divisions, ties, slurs, ornaments, tuplets, lyrics, chord symbols, dynamics, repeat structures. Implementing all of it solo, the traditional way, is a multi-year project.\n\nLate last year I made a bet: let AI (Claude Code, ~90% of the code; Antigravity, ~10%) write most of the implementation, while I focused on defining what \"correct\" means — architecture decisions and test cases — rather than typing every line myself.\n\nThe thing that made this workable wasn't the AI, though, it was the test harness: I collected around 250 real-world scores (Bach, Beethoven, Chopin, Brahms, Debussy, plus the LilyPond regression test corpus) and ran them through an import → export → re-import round-trip. Any structural drift between the first and second import is a bug. AI writes code, the round-trip suite catches regressions, AI fixes them, repeat. Humans stayed in the loop for the judgment calls; the AI did the volume work.\n\nThe single hardest part of MusicXML to get right is `<backup>`\n\n/`<forward>`\n\n. MusicXML is a flat, sequential XML structure, but a measure isn't sequential — it has multiple simultaneous voices, and a piano part has two staves (treble/bass) sharing one measure. To represent that in a flat stream, the format writes voice A's notes, then emits a `<backup>`\n\nelement to rewind the internal time cursor, then writes voice B's notes starting from the same beat.\n\nThat means any code that reads or writes MusicXML has to maintain its own notion of \"where am I on the timeline right now\" independent of document order — and get it right across combinations of backup + multi-voice + multi-staff, which is where most real-world parser bugs live.\n\nEarly on I decided every operation had to leave the score in a musically valid state — correctness over flexibility. Concretely: if a 4/4 measure has beat 4 occupied by a quarter note, you can't just change it to a half note, because that would overflow the measure boundary. For every edit, the editor has to decide: overflow into the next measure, auto-insert a tie, or reject the operation outright.\n\nThe harder cases are things like inserting a note when another voice already has something at that beat, notes of different lengths sharing a beat, or replacing a rest with a note that doesn't fill the remaining space cleanly. This constraint exists because the target user isn't a notation expert — someone who doesn't know music theory should still end up with a structurally correct score, not a fragile one.\n\nFor multiplayer editing I used [Yjs](https://github.com/yjs/yjs), a CRDT (Conflict-free Replicated Data Type) library — no server-side conflict resolution, edits from multiple clients converge automatically, the same approach Google Docs and Figma use.\n\nText CRDTs only have to reason about character insertion and deletion in a 1D sequence. A score has two simultaneous temporal dimensions per note — its position (which beat) and its duration (how many beats it occupies) — and multiple users can be editing different voices in the same measure concurrently, or one user can be inserting a note while another is changing the measure's time signature or key signature mid-edit.\n\nMy approach: map the MusicXML tree onto Yjs's `Y.Map`\n\n/`Y.Array`\n\ntypes 1:1. Yjs guarantees *structural* convergence — every client ends up with the same tree — but it has no idea what a musically valid tree looks like, so structural convergence and musical validity are two separate problems. The safety net is an auto-undo: if an export ever produces an invalid document, the last operation is automatically rolled back rather than left in a broken state.\n\nRendering runs through [Verovio](https://www.verovio.org), an open-source C++ engraving engine compiled to WebAssembly. Two problems showed up once scores got non-trivial in size:\n\n**Performance.** Full-score re-rendering on every edit blocks the main thread for large scores. Fix: move rendering into a Web Worker, and stream rendering page-by-page, prioritizing whichever page the user is currently looking at.\n\n**Identity.** Verovio has its own internal IDs for rendered SVG elements; Yjs has its own IDs for the underlying data. When a user clicks a note on screen, you need to resolve an SVG element back to the exact Yjs entry it came from. I ended up computing a fingerprint per note — part index, measure number, beat position, voice, pitch — and rebuilding the mapping table on each render pass. Building a notation renderer from scratch was on the table at one point; I shelved it as a multi-year project of its own and leaned on Verovio instead.\n\nTurning a click into \"beat X, pitch Y\" is deceptively harder than the equivalent problem in a text editor. A whole note is visually wide, a sixteenth note is narrow, dotted notes are wider still, and note spacing varies measure-to-measure based on how packed the measure is — so the mapping has to be derived from Verovio's actual final layout, not from a fixed grid.\n\nFor feedback, the editor shows a translucent \"ghost\" preview of the pending note before you commit the click: purple for a new note, blue for adding to a chord, green for replacing a rest, gray when the position conflicts with something already there.\n\nNotation software has effectively unlimited surface area — tablature, drum notation, transposing instruments, automatic part extraction, fine-grained layout control, and on. As a (mostly) solo project, the operative question stayed \"what helps the most people right now,\" and a lot got deliberately cut or deferred. Current honest state: a 5–10 minute piano score performs well; large orchestral scores are still rough.\n\nMusicXML's other trap is that the spec and what software actually emits diverge — MuseScore and Dorico export structurally different XML for the same piece. There's no shortcut around this beyond feeding the parser a large, varied corpus of real files and absorbing the \"dialects\" one by one.\n\n[ScoreTail](https://scoretail.com) is free, browser-based, and works without an account for a quick edit. If you've dealt with MusicXML, CRDT-based collaborative editing, or WASM rendering engines, I'd be curious to hear how you approached the same problems.", "url": "https://wpnews.pro/news/i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me", "canonical_source": "https://dev.to/tan-z-tan/i-built-a-real-time-collaborative-sheet-music-editor-heres-what-musicxml-and-crdts-taught-me-1dii", "published_at": "2026-08-15 10:14:05+00:00", "updated_at": "2026-08-15 10:42:14.191572+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence"], "entities": ["ScoreTail", "MusicXML", "Yjs", "Claude Code", "Antigravity", "MuseScore", "Finale", "Sibelius"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me", "markdown": "https://wpnews.pro/news/i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me.md", "text": "https://wpnews.pro/news/i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me.txt", "jsonld": "https://wpnews.pro/news/i-built-a-real-time-collaborative-sheet-music-editor-here-s-what-musicxml-and-me.jsonld"}}