
Guitar Tab Editor
A browser-based guitar tab editor. Standard notation and tablature render side by side and stay in sync as you type, the score plays back through an in-browser synth, and tabs can be imported from ASCII, MIDI or an audio recording.
Stack: React and TypeScript on Vite, FastAPI on Python, Tone.js with a raw Web Audio fallback, SVG for engraving.
Methodology
This was my first project built end to end with AI coding agents. I learnt a lot about the process of vibe coding as well as best practices and limitations. Over the months making this project, model capabilities have increased dramatically allowing me to create more complicated functionality.
The method I employed was plan first (in docs/plans/plan-tabRenderer.prompt.md) and drive implementation second using the overarching project plan to guide me. For each feature and incremental step, I would use the agent's plan feature and review it before writing code. With big shifts/ redesigns to the project the plan gets updated. , I would also review the foundation structure of the code to ensure new features are being added efficiently.
The main bottleneck with all uses of AI has become validation. Since AI describes facts with such certainty, it is very appealing to get drawn into believing it. Guilty of this myself, I tried to validate as much as possible what Claude was doing for me.
How it works
The score model
frontend/src/score/ holds the editor's authoritative state: measures, voices, beats, note effects, tempo and tuning. It is split by role, with model.ts for the shape, mutations.ts for edits, queries.ts for reads, and normalization.ts for putting the score back into a valid state after every change. A legacy.ts adapter maps the older ASCII-derived model onto this one, so imported tabs and edited scores end up in the same representation.
Engraving and rendering
frontend/src/engraving/ is the layout maths, kept separate from anything React. It positions noteheads and chord stacks, wraps the score into rows as bars expand or contract, and spaces dense passages so glyphs do not collide.
frontend/src/rendering/ then draws the result, one React component per element type. Layout is covered by golden snapshot tests, so a change that shifts glyphs shows up as a diff instead of something to catch by eye. I iterated on the staff and tab layout to make it accurate and visually coherent.
Editing
frontend/src/editor/ models edits as commands against a store. The two surfaces do different jobs: clicking the notation above changes rhythm, and typing on the tab below changes frets. Rests, ties, hammer-ons, pull-offs and slides are all edits against the same score model, so nothing needs a separate code path to render.
Playback
frontend/src/playback/ schedules notes through Tone.js with real sustain, rather than only triggering onsets, which is what makes written rhythm audible. Tone.js requires a secure context, so a second implementation over raw Web Audio sits behind the same interface for when the editor is served over plain HTTP.
Import
Three routes in, all landing on the same score model.
- ASCII tab is parsed by
backend/parser/parser.pyinto theTabmodel. This path can render straight to SVG throughbackend/renderer/without involving the editor at all, which is still the fastest way to turn a pasted tab into a picture. - MIDI is parsed by
backend/parser/midi_parser.py, using a lowest-fret heuristic to pick string positions. - Audio goes to gtab, the transcription pipeline I built for it, running on a Modal GPU service.
Onsets from both the MIDI and audio paths are snapped to the editor's tick grid. Without that, imported notes sit fractionally off the beat and every later rhythm edit fights the original timing.
Deployment
The frontend deploys to Vercel. Transcription runs on Modal instead, because a serverless web host has neither a GPU nor room for a job lasting minutes, and the Vercel build posts audio there directly. The job client polls for results, and had to be rewritten after an early version polled hard enough to become a problem in itself.
Final thoughts
I started off the project with creating the ASCII pipeline before being able to create the interactive model. This has led to requiring an adapter to reconcile them. To prevent this, I need more frequent reviews that catch this kind of divergence before it's too late, avoiding the need for an adapter.
This is the only project-level change I would make. The project has helped me learn vibe-coding methodology greatly, and I will hopefully continue improving all the features, namely the transcription pipeline.
What's in the repo
frontend/src/score/: the editor's score model, mutations and normalisationfrontend/src/engraving/: layout maths and golden snapshot testsfrontend/src/rendering/: SVG components for staff, tab, noteheads and playheadfrontend/src/editor/: command store for editsfrontend/src/playback/: Tone.js scheduler and Web Audio fallbackfrontend/src/io/: project save and load, audio import, legacy formatsfrontend/src/api/client.ts: backend and Modal transcription clientsbackend/parser/: ASCII and MIDI parsersbackend/renderer/: standalone SVG layout and rendererbackend/transcription/: gtab configs and the transcription service