Claude Code Plugins

Community-maintained marketplace

Feedback

TEF/tablature debugging for parsing issues, missing notes, wrong articulations, tuning problems. Use when debugging banjo tabs from Banjo Hangout or other TEF sources.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

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 ID
  • source_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() - check effect1 & 0x07 only
  • technique_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 uses effect1 & 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}