
tab-debug
by Jollyhrothgar
SKILL.md
name: tab-debug description: TEF/tablature debugging for parsing issues, missing notes, wrong articulations, tuning problems. Use when debugging banjo tabs from Banjo Hangout or other TEF sources.
Tab Debugging Skill
Use this skill when debugging tablature parsing, rendering, or playback issues for TEF (TablEdit) files.
When to Use
- User reports a tab is rendering incorrectly
- Notes are missing, duplicated, or in wrong positions
- Articulations (slides, hammer-ons, ties) aren't showing
- Playback timing is wrong
- Tab shows blank/empty
Debugging Workflow
1. Get the source info from work.yaml
cat works/{work-slug}/work.yaml | grep -A5 provenance
Look for:
source_id- the BH tab IDsource_url- download URL for TEF file
2. Re-download the TEF file if needed
curl -L -o sources/banjo-hangout/downloads/{id}.tef "{source_url}"
3. Parse and inspect the TEF file
import sys
sys.path.insert(0, 'sources/banjo-hangout/src')
from tef_parser.reader import TEFReader
from tef_parser.otf import tef_to_otf
from pathlib import Path
reader = TEFReader(Path('sources/banjo-hangout/downloads/{id}.tef'))
tef = reader.parse()
# Basic info
print(f"Format: {'V2' if tef.header.is_v2 else 'V3'}")
print(f"Title: {tef.title}")
print(f"Time sig: {tef.header.v2_time_num}/{tef.header.v2_time_denom}")
print(f"Note events: {len(tef.note_events)}")
4. Inspect specific measures
Measure N corresponds to positions (N-1)*16 to N*16-1:
def show_measure(tef, measure_num):
start = (measure_num - 1) * 16
end = start + 16
for evt in tef.note_events:
if start <= evt.position < end:
result = evt.decode_string_fret()
if result:
string, fret = result
raw = evt.raw_data.hex() if evt.raw_data else 'none'
print(f'pos={evt.position}, s{string}f{fret}, marker={evt.marker!r}, raw={raw}')
show_measure(tef, 25) # Check measure 25
5. Check raw bytes for articulations
For V2 format, the 6-byte note record structure:
Byte 0: position (0-255)
Byte 1: string/fret encoded
Byte 2: duration
Byte 3: marker character ('I', 'F', 'L', 'K', etc.)
Byte 4: effect1 (articulations)
Byte 5: effect2 (overlays)
for evt in tef.note_events:
if evt.raw_data and len(evt.raw_data) >= 6:
effect1 = evt.raw_data[4]
effect2 = evt.raw_data[5]
if effect1 or effect2:
result = evt.decode_string_fret()
if result:
print(f's{result[0]}f{result[1]}: effect1=0x{effect1:02x}, effect2=0x{effect2:02x}')
6. Re-convert and test
otf = tef_to_otf(tef)
Path('works/{work-slug}/banjo.otf.json').write_text(otf.to_json())
# Also update docs for live testing:
Path('docs/data/tabs/{work-slug}-banjo.otf.json').write_text(otf.to_json())
TEF Format Reference
V2 vs V3 Detection
# V2: starts with 0x1A byte, has fixed header structure
# V3: starts with 'TablEdit' magic, uses chunk-based format with 'debt' marker
if tef.header.is_v2:
# 6-byte note records, positions 0-255 per measure
else:
# Variable record format, uses 'debt' chunk for note data
Marker Types (V2 byte 3)
| Byte | Char | Meaning | Action |
|---|---|---|---|
| 0x49 | 'I' | Initial/melody note | Keep |
| 0x46 | 'F' | Fret/melody note | Keep |
| 0x4C | 'L' | Legato (hammer/pull) | Keep, check direction for h/p |
| 0x4B | 'K' | Triplet note | Keep, apply triplet timing |
| 0x43 | 'C' | Chord diagram | SKIP |
| 0x44 | 'D' | Diagram overlay | SKIP |
| 0x40 | '@' | Alternate/chord tone | Heuristic: keep highest fret only |
Effect1 Byte (V2 byte 4) - Articulations
| Value | Meaning |
|---|---|
| 0x00 | No effect |
| 0x01 | Hammer-on or pull-off (check fret direction) |
| 0x02 | Unknown (possibly slide related) |
| 0x03 | Slide (bits 0x01 + 0x02 both set) |
| 0x04 | Bend (1/4 step) - NOT legato, ignore |
| 0x80 | Tie to previous note |
Effect2 Byte (V2 byte 5) - Overlays
| Value | Meaning |
|---|---|
| 0x07 | Chord overlay - SKIP this note |
Tie Detection
Ties use the 0x80 bit in the marker byte:
def is_tie(evt):
if evt.raw_data and len(evt.raw_data) >= 4:
marker_byte = evt.raw_data[3]
return bool(marker_byte & 0x80)
return False
Slide vs Hammer-on Detection
def get_articulation(evt):
if evt.raw_data and len(evt.raw_data) >= 5:
effect1 = evt.raw_data[4]
if effect1 == 0x03: # Both bits set = slide
return '/'
elif effect1 == 0x01: # Just bit 0 = legato
# Check direction for h vs p
return 'h' if going_up else 'p'
return None
Common Issues & Fixes
Empty notation (0 events)
Cause: V3 format variant without 'debt' chunk marker
Status: Unsupported - these files use a different V3 sub-format
Check: conversion_log.json shows "Empty notation (0 events) - format: v3"
"Cannot read properties of undefined (reading 'id')"
Cause: TEF file has no instrument definitions, so otf.tracks is empty
Fix: Added default "banjo" track creation in otf.py when no instruments detected
Applied: 2026-01-04 - if you see this error, re-run batch_convert.py
Extra notes appearing
Cause: Chord diagram notes ('C', 'D' markers) or overlay notes (effect2=0x07) not filtered
Fix: Check marker filtering in reader.py
@ marker notes cluttering tab
Cause: '@' markers are alternate voicings/chord tones Fix: Heuristic in reader.py keeps only highest fret at each position when no melody markers present
False legato/hammer-ons on every note
Cause: V2 TEF files use marker='L' for "lead/melody note", not "legato" Symptom: Every note shows hammer-on slur Fix: Don't check marker='L' for articulation - only use effect1 bytes:
has_legato_effect()- checkeffect1 & 0x07onlytechnique_from_event()- removed marker='L' check Files:sources/banjo-hangout/src/tef_parser/otf.py
Wrong fret on notes with text annotations
Cause: V2 format uses bit 5 of fret byte + effect2 for high frets, but annotations also set bit 5:
- effect2=0x06: text annotation
- effect2=0x07: chord overlay
- effect2=0x0c: fingering annotation
Symptom: Random notes have wrong fret (e.g., fret 12 instead of fret 0) Fix: Only add effect2 to fret when effect2 > 0x0c (not a special marker):
effect2_val = rec[5] if len(rec) > 5 else 0
if (fret_byte >> 5) & 0x01 and effect2_val > 0x0c:
fret += effect2_val
Files: sources/banjo-hangout/src/tef_parser/reader.py line ~1013
Multi-track TEF files show all tracks merged onto one
Cause: Instrument names with embedded tuning (e.g., "Mandolin GDAE" instead of "Mandolin\x00GDAE") not parsed
Symptom: Header says N tracks/M strings, parser finds fewer instruments
Debug: Check tef.header.v2_tracks vs len(tef.instruments) - they should match
Fix: Add patterns for combined instrument+tuning names in parse_instruments():
instrument_patterns = [
(b"Mandolin GDAE", 4),
(b"Mandolin Standard", 4),
(b"Mandolin", 4),
...
]
Files: sources/banjo-hangout/src/tef_parser/reader.py line ~960
Bends showing as pull-offs
Cause: effect1=0x04 was incorrectly interpreted as "explicit pull-off" Symptom: Notes with 1/4 step bends show pull-off slurs Fix: Removed 0x04 from legato detection - it's actually a bend indicator
has_legato_effect()now useseffect1 & 0x03(not 0x07)technique_from_event()no longer checks for 0x04 Files:sources/banjo-hangout/src/tef_parser/otf.py
Wrong articulation (slide showing as hammer)
Cause: effect1=0x03 (slide) being detected as 0x01 (hammer)
Fix: Check slide first in compute_articulations() in otf.py
V2 reading list (repeats) not showing
Cause: V2 reading list wasn't being parsed
Symptom: Song plays through once without repeats
Fix: Added parse_reading_list_v2() in reader.py:
- Count is at byte 222 of V2 header
- Data starts after components (offset 258 + component_count * 6)
- Each entry is 2 bytes: (from_measure, to_measure)
Frontend:
expandNotationWithReadingList()in work-view.js expands measures Files:sources/banjo-hangout/src/tef_parser/reader.py,docs/js/work-view.js
Cross-measure ties not rendering
Cause: Each measure is a separate SVG row, can't draw arc across
Workaround: Tied notes show bracket notation [7] instead of arc
Notes clustered in first half of measure (2/4 time rendered as 4/4)
Cause: Time signature not passed to TabRenderer
Symptom: For 2/4 tabs, notes appear in first ~45% of each measure
Fix: Ensure time signature is passed as 4th argument to renderer.render():
const timeSignature = otf.metadata?.time_signature || '4/4';
renderer.render(track, notation, ticksPerBeat, timeSignature);
Files: docs/js/song-view.js, docs/js/work-view.js - both need to pass time signature
Triplet timing off
Cause: 'K' marker notes need 2/3 duration, beamed together Fix: Check triplet detection and grouping in otf.py
Key Files
| File | Purpose |
|---|---|
sources/banjo-hangout/src/tef_parser/reader.py | Binary parsing, marker filtering |
sources/banjo-hangout/src/tef_parser/otf.py | TEF→OTF conversion, articulations, triplets |
docs/js/renderers/tablature.js | SVG rendering, slurs, brackets |
docs/js/renderers/tab-player.js | Audio playback, note scheduling |
sources/banjo-hangout/conversion_log.json | Batch conversion results |
Testing Changes
After modifying parser:
# Re-convert single file
uv run python sources/banjo-hangout/src/batch_convert.py
# Or convert specific file
uv run python -c "
from pathlib import Path
import sys
sys.path.insert(0, 'sources/banjo-hangout/src')
from tef_parser.reader import TEFReader
from tef_parser.otf import tef_to_otf
tef = TEFReader(Path('sources/banjo-hangout/downloads/{id}_tef.tef')).parse()
otf = tef_to_otf(tef)
Path('docs/data/tabs/{slug}-banjo.otf.json').write_text(otf.to_json())
"
# Test in browser
./scripts/server
# Navigate to http://localhost:8080/#work/{slug}
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon