All events fired by Tabatura — their timing, payload shape, and example handlers.
Call plugin.on(event, handler) to subscribe.
It returns a numeric subscription ID you can pass to plugin.off to unsubscribe.
Most plugins subscribe in plugin.on_init and never need to unsubscribe.
-- Subscribe
local id = plugin.on("note", function(e)
-- handle event
end)
-- Unsubscribe (rarely needed)
plugin.off("note", id) Keep event handlers short — avoid slow or blocking work inside them, as they run on the same thread that drives the UI.
"file.open" Fired once after a GP file is fully parsed and loaded.
| Field | Type | Description |
|---|---|---|
| song.title | string | Song title |
| song.artist | string | Artist name |
| song.album | string | Album name |
| song.tempo | number | Tempo in BPM |
| song.time_signature | string | e.g. "4/4" |
| song.key_signature | string | e.g. "C Major" |
plugin.on("file.open", function(e)
local s = e.song
print(s.title .. " by " .. s.artist)
print("Tempo: " .. s.tempo .. " BPM | " .. s.time_signature)
end) "file.close" Fired when the user closes the current file. No payload — e is an empty table.
No payload — the event table is empty.
plugin.on("file.close", function(_)
-- Reset any UI state.
plugin.set_label_text(my_label, "No file open")
end) "playback.start" Fired when the user starts playback. Useful for arming UI state, switching from manual practice mode to song-synced behavior, or caching the current song metadata at the moment playback begins.
| Field | Type | Description |
|---|---|---|
| song.title | string | Song title |
| song.artist | string | Artist name |
| song.album | string | Album name |
| song.tempo | number | Tempo in BPM |
| song.time_signature | string | e.g. "4/4" |
| song.key_signature | string | e.g. "C Major" |
plugin.on("playback.start", function(e)
local song = e.song
plugin.set_label_text(status_label,
"Playback started at " .. song.tempo .. " BPM")
end) "playback.stop" Fired when playback stops, either because the user stopped it or because the song finished naturally.
| Field | Type | Description |
|---|---|---|
| completed | boolean | true when playback reached the end of the song; false when it was stopped early |
plugin.on("playback.stop", function(e)
if e.completed then
plugin.set_label_text(status_label, "Song finished")
else
plugin.set_label_text(status_label, "Playback stopped")
end
end) "render" Fired after every view refresh (scroll, resize, zoom, etc.). No payload. Use sparingly — this can fire frequently.
No payload — the event table is empty.
plugin.on("render", function(_)
-- e.g. sync an overlay position after the tab redraws.
end) "beat" Fired once per beat during playback, before note events for that beat. Useful for progress bars or beat counters.
| Field | Type | Description |
|---|---|---|
| beat_number | number | 1-based global beat counter across the whole song |
| total_beats | number | Total number of beats in the song |
plugin.on("beat", function(e)
local pct = e.beat_number / e.total_beats * 100
plugin.set_label_text(progress_label,
string.format("Beat %d / %d (%.0f%%)",
e.beat_number, e.total_beats, pct))
end) "beat.in_measure" Fired once per GP beat slot during playback, carrying beat-level effect data. A GP beat slot is one rhythmic unit (eighth note, sixteenth, etc.) — not necessarily a musical beat. Use beat.metronome_pulse instead if you need to fire exactly once per musical beat.
| Field | Type | Description |
|---|---|---|
| measure | number | 1-based measure number |
| beat_in_measure | number | 1-based beat slot index within the measure |
| numerator | number | Time-signature numerator for this measure (e.g. 4 for 4/4, 5 for 5/8) |
| denominator | number | Time-signature denominator for this measure (e.g. 4 for 4/4, 8 for 5/8) |
| beat_tick | number | Accumulated GP ticks from the start of the measure to this beat slot. Quarter note = 960 ticks. Use beat_tick % (960 * 4 / denominator) == 0 to detect musical beat boundaries. |
| duration | number | Beat slot duration in GP ticks. Quarter note = 960 |
| fade_in | boolean | Beat has a fade-in effect |
| slap | number | 0 = none, 1 = slap, 2 = pop, 3 = tap |
| stroke_down | number | Downstroke speed in ticks (0 = no stroke) |
| stroke_up | number | Upstroke speed in ticks (0 = no stroke) |
| pick_stroke | number | 0 = none, 1 = up, 2 = down |
| repeat_open | boolean | This measure opens a repeat section |
| repeat_close | number | >0: measure closes a repeat N times; 0 = no close |
| repeat_alternative | number | Volta bitmask — bit 0 = 1st ending, bit 1 = 2nd, … |
| tremolo_bar | table? | Whammy-bar envelope. Present only when the beat has a tremolo bar effect. Shape: { type, value, points: [{position, value, vibrato}, …] } |
plugin.on("beat.in_measure", function(e)
if e.repeat_open then
print("Repeat section starts at measure " .. e.measure)
end
if e.repeat_close > 0 then
print("Repeat closes (x" .. e.repeat_close .. ")")
end
end) "beat.metronome_pulse" Fired exactly once per musical beat during playback — N times per measure where N is the time signature numerator. Unlike beat.in_measure (which fires once per GP beat slot), this event fires at the correct musical beat boundaries even when notes span multiple beats. The app splits long note sleeps internally to guarantee accurate timing.
| Field | Type | Description |
|---|---|---|
| measure | number | 1-based measure number |
| musical_beat | number | 1-based beat number within the measure (1 = downbeat, fires first) |
| numerator | number | Time-signature numerator (e.g. 4 for 4/4, 5 for 5/8) |
| denominator | number | Time-signature denominator (e.g. 4 for 4/4, 8 for 5/8) |
plugin.on("beat.metronome_pulse", function(e)
-- e.musical_beat == 1 is the downbeat (accent it)
local is_downbeat = e.musical_beat == 1
play_click(is_downbeat)
plugin.set_label_text(status_label,
"Beat " .. e.musical_beat .. " of " .. e.numerator .. "/" .. e.denominator)
end) "note" Fired for every individual note during playback. A single beat can produce multiple note events simultaneously (one per sounding string). Note events fire after the beat and beat.in_measure events for the same position.
| Field | Type | Description |
|---|---|---|
| track | number | 0-based track index |
| measure | number | 1-based measure number |
| beat | number | 1-based beat within the measure |
| string | number | GP string number. 1 = thinnest (high e), 6 = thickest (low E) for standard guitar |
| fret | number | Fret number (0 = open string) |
| duration | number | Beat duration in GP ticks (shared with beat events) |
| fade_in | boolean | Beat has a fade-in effect |
| slap | number | 0 = none, 1 = slap, 2 = pop, 3 = tap |
| stroke_down | number | Downstroke speed in ticks (0 = no stroke) |
| stroke_up | number | Upstroke speed in ticks (0 = no stroke) |
| hammer_on | boolean | Note is played as a hammer-on |
| pull_off | boolean | Note is played as a pull-off |
| slide_type | number | 0 = none, 1 = shift, 2 = legato, 3 = in from below, 4 = in from above, 5 = out downward, 6 = out upward |
| vibrato | boolean | Note has vibrato |
| harmonic | number | 0 = none, 1 = natural, 2 = artificial, 3 = pinch, 4 = tapped, 5 = semi, 6 = feedback |
| palm_mute | boolean | Palm mute applied |
| let_ring | boolean | Let ring applied |
| staccato | boolean | Staccato articulation |
| tapping | boolean | Right-hand tapping |
| dead_note | boolean | Muted/dead note (x) |
| ghost_note | boolean | Ghost note (parenthesised) |
| tie_origin | boolean | This note ties forward to the next beat |
| tie_destination | boolean | This note is the destination of a tie |
| tremolo_pick | number | Rapid-pick speed: 8, 16, or 32 (0 = none) |
| accentuated_note | boolean | Standard accent (>) |
| heavy_accentuated_note | boolean | Heavy accent (^) |
| bend | table? | Pitch-bend envelope. Shape: { type, value, points: [{position, value, vibrato}, …] }. Omitted when absent. |
| trill | table? | Trill data: { fret: number, duration: number }. Omitted when absent. |
| grace_note | table? | Grace note: { fret, dynamic, duration, on_beat }. Omitted when absent. |
| tremolo_bar | table? | Whammy-bar envelope (same shape as in beat.in_measure). Omitted when absent. |
plugin.on("note", function(e)
-- Skip ties to avoid re-triggering already-ringing notes.
if e.tie_destination then return end
-- Build a human-readable description of the note.
local parts = {
string.format("Track %d | M%d B%d | Str %d Fret %d",
e.track + 1, e.measure, e.beat, e.string, e.fret)
}
if e.hammer_on then parts[#parts+1] = "H" end
if e.pull_off then parts[#parts+1] = "P" end
if e.palm_mute then parts[#parts+1] = "PM" end
if e.let_ring then parts[#parts+1] = "LR" end
plugin.set_label_text(my_label, table.concat(parts, " "))
end)