BhajanTabs
A chord-sheet app for learning bhajans on guitar — tap chords straight onto the lyrics, transpose to any key, play along to backing tracks.
The problem
Personal-first (my own guitar learning), shareable with musicians in my circle — not a community tool.
Learning bhajans on guitar: devotional songs need transposing into the player's key. (The Clerk auth + pending→active approval flow is real but incidental to the personal framing.)
What I built
- Tappable chord editor (the creativity hero) — instead of typing [C] bracket markers, tap a spot in the lyric and pick a chord. A mobile-guitarist UX call: phone on a stand, guitar in hand, typing brackets is miserable.
- Multiple arrangements per song at varying difficulty — one bhajan, several tabs (the isTemplate/multi-tab feature).
- Transposition as a first-class, persisted feature.
- Backing-track play-along for practice.
- Printable PDF / Markdown export for offline use.
How it works
Frontend — React 19 · Vite 7 · TS · Tailwind 4 + shadcn/ui · wouter (patched) · TanStack Query · React Hook Form + Zod · Framer Motion. Backend — Express 4 · tRPC 11 · Clerk auth (JWT). Data — Drizzle ORM over MySQL. Storage — AWS S3 (Railway bucket) via presigned URLs. Export — jsPDF + html2canvas. Deploy — Railway (single-port prod serving).
Schema — users (clerkId, role, status pending|active) · songs (title, originalKey, tempo, language, tags, backingTrackUrl) · tabs (songId FK, label, key, isTemplate, content as a plain-text chord sheet).
The tappable chord editor decision (the creativity hero); multiple arrangements per song at varying difficulty; transposition as a first-class persisted feature; backing-track play-along; PDF/Markdown export; plus conception, deploy, daily personal use, and sharing.
Under the hood
Under the hood, two pieces do the heavy lifting:
client/src/lib/chordEngine.ts — parses 3 chord-sheet formats without mis-tagging lyric lines as chords, with enharmonic sharp/flat handling, slash chords, and modulo-12 chromatic math.
TappableChordEditor.tsx — caret-from-tap resolution, cross-browser caretRangeFromPoint, and string-offset arithmetic.