Interactive guitar tab sample
This is a dependency-free proof of concept: a reusable browser renderer reads a versioned JSON tab file and turns it into an interactive practice sheet.
The provided file is named kesariya.pdf, but the title printed inside it is Haseen (Talwinder). The sample follows the title and music shown in the document. All eight bars are kept in one continuous array, in source order. The license watermark email is intentionally not copied into the web data.
Files
index.html- the embeddable page surfacestyles.css- responsive presentationapp.js- generic renderer, JSON loader, bar navigation, practice cursor, metronome, zoom, and keyboard controlshaseen.tab.json- the complete source data transcribed from the PDFguitar-tab.schema.json- the reusableguitar-tab/v1JSON Schema
Run the sample
The page fetches its JSON, so serve this folder instead of opening index.html with a file:// URL:
make
This runs uv run python -m http.server on 127.0.0.1:8080. Override either value when needed, for example make PORT=9000 HOST=0.0.0.0. make serve and make start are equivalent aliases.
Run make help to see all targets and options.
Then open http://localhost:8080.
For the compact iframe view, open http://localhost:8080/?embed=1.
To point the viewer at a hosted compatible file, add an encoded src parameter, for example ?embed=1&src=https%3A%2F%2Fexample.com%2Fsong.tab.json. The JSON host must allow the browser request.
Share through Cloudflare Tunnel
The Makefile defaults to the public hostname tabs.bhakat.dev and a named Cloudflare Tunnel called guitar-tabs. Ensure that bhakat.dev is active in the Cloudflare account used by cloudflared.
Create the tunnel and its DNS route once:
make tunnel-setup
Then, for each sharing session, run:
make tunnel
This starts the local static server and leaves cloudflared in the foreground. Press Ctrl+C to stop both processes. The first command stores the tunnel credential file under ~/.cloudflared; it is not part of this repository.
If IPv6 connections to the Cloudflare edge are unreliable, force IPv4 for the tunnel:
make tunnel TUNNEL_EDGE_IP_VERSION=4
Use the same overrides with both commands to choose another tunnel name or subdomain:
make tunnel-setup TUNNEL_NAME=my-tabs TUNNEL_HOST=guitar-tabs.bhakat.dev
make tunnel TUNNEL_NAME=my-tabs TUNNEL_HOST=guitar-tabs.bhakat.dev
Embed it
<iframe
src="https://your-site.example/tabs/?embed=1"
title="Interactive guitar tab"
loading="lazy"
style="width: 100%; height: 900px; border: 0"
allow="autoplay"
></iframe>
The “Copy embed snippet” button generates the equivalent snippet for the current URL.
The standard format
JSON is the canonical interchange format because browsers can load it directly and JSON Schema can validate it. YAML or Markdown can still be used as an authoring format, but should be converted to this JSON shape before rendering.
The hierarchy is:
song
├── metadata + instrument + timing + legend
└── arrangements
└── sections
└── blocks
├── tab → bars → rows
└── chordLyrics → lines → spans
Each tab bar declares columnCount and owns one equal-width ASCII row per string. This keeps barlines, line wrapping, and responsive rendering deterministic. Optional column-positioned markers preserve strum arrows and similar annotations without inserting them into the tab grid. Stable IDs and optional beat events provide anchors for future audio sync or note-level highlighting.
Important conventions:
- Tuning pitches describe open strings before the capo.
- Fret numbers remain relative to the capo, matching ordinary tab notation.
rows[].textcontains the inside of the bar; the renderer supplies barlines.- Every row in a bar must have the same Unicode-code-point width as
columnCount. markers[].atColumnis zero-based within that bar-local row.timeSignature, bar beat counts, note events, repeats, and chord/lyric blocks are optional when the source does not provide them.extensionsis the escape hatch for namespaced vendor data.
The semantic checks in app.js are a normative second validation pass for rules JSON Schema cannot express across sibling values. They enforce unique IDs, columnCount and row-width agreement, tuning/string consistency, legend references, and in-range text, event, note, and marker positions. A production ingestion pipeline should run the JSON Schema first and equivalent semantic checks second.
Interaction included in the sample
- Read all eight source bars as one continuous tab
- Click a bar or use arrow keys to focus it
- Start a BPM-controlled 4-count practice cursor and optional metronome
- Loop the current tab
- Resize the tab without losing alignment
- Load another compatible
.jsonfile from the browser - Use keyboard shortcuts:
Space,←,→,M, andL
The source PDF states 82 BPM but does not state a time signature or exact beat mapping. The sample labels its 4-count cursor as a practice aid rather than presenting it as transcribed rhythm.