Is there an existing issue for this?
Current Behavior
I did have a ticket a while back but it was closed out as we thought it was a custom cursor and not a bug issue, but New Discoveries have changed that!
In shuffle/swing (swung triplets) material, AlphaTab sometimes resolves or emits beat objects that are structurally earlier at later expanded playback ticks, causing click-to-seek and cursor placement inconsistencies. This is reproducible outside our app (native cursor drift visible in a clean third-party repo).
I used the same Stevie Ray Vaughan - Pride and Joy again but mostly borrowed from the LouisLam Vite Repo on v1.8.1 to confirm the native cursor and alphaTab issues:
Repro
• Use shuffle/triplet feel material (SRV-style shuffle pattern).
• Observe that some swung subdivisions behave inconsistently:
• clicking visually on a note may select a neighboring beat
• cursor can appear offset on specific swung beats
• Evidence includes screenshots from a third-party minimal repro (LouisLam repo) showing native cursor offset on specific swung notes.
Here are some images and then I am including a video as well!
Micro-pause: In M1 (first dyad), playback shows a brief “pause/hesitation” that aligns with the same shuffled-beat inconsistency (structurally earlier beat returned at later expanded ticks), suggesting a transient beat-duration/identity mismatch rather than a rendering/performance issue.
Now it flips from M1 to M2 from down strums being off to up strums being off and then in M3 back to down strums being off.
Why this can relate to “bucking”
In the LouisLam repo and Maestro repo both custom cursor and native cursor have a wobble or shuffle back thing going on that is sometimes more visible during the shuffle sections or heavy triplet shuffles than in other places;
When AlphaTab occasionally resolves a structurally earlier beat at a later expanded tick, the cursor pipeline can momentarily anchor to the “wrong” beat identity. That mismatch (beat identity vs expanded-time position) causes the cursor to re-anchor backward or snap-correct on the next update, which reads visually as a “buckle” or “kick back.” In shuffle/triplet feel, those tiny inconsistencies are amplified because beat spacing is compressed and the cursor crosses boundaries faster.
Technical observations:
• In certain cases, tickCache.findBeat(t) returns a beat b whose absolutePlaybackStart is ≤ current beat’s absolutePlaybackStart, even though t lies inside what the current beat “owns” in expanded playback time.
• This looks like a pickup/grace beat is being resolved “late” in expanded time.
• On affected beats, the inconsistency appears in:
• boundsLookup.getBeatAtPos(x,y) vs tickCache.findBeat(t)
• native cursor placement on some swung notes
What we changed in our app (workaround)
• We treat expanded playback ticks as timeline truth for seek/publish and avoid structural pointers for repeat passes.
• We added a renderer-level guard to reject structural regression beats during normal playback (except during explicit seek/bypass windows).
• This stabilizes our cursor, but it’s a workaround for what appears to be an AlphaTab engine mapping edge case.
Questions:
1. Is there an official/recommended API or method for mapping visual click position → correct playback tick/beat in shuffle/swing + repeat contexts?
2. Is the engine expected to ever return a beat with absolutePlaybackStart <= curBeat.absolutePlaybackStart when scanning forward in expanded time? If not, can this be fixed/normalized?
3. Any known issues with cursor/hitbox placement on swung triplet subdivisions (native cursor offset)?
Attachments
• Video showing the issue
• LouisLam repo screenshots (native cursor offset)
• Console traces showing structural-regression candidates being returned during forward scans
https://youtu.be/iW7wKUle_oc
Most stand out logs:
• The skip:
• [resolveNextBeatExpanded] skipped backward candidate {curAbs: 600, bAbs: 480, firstT: 640}
• The discard:
• [V105] structural regression discarded {incomingStart: 480, prevAbs: 600, tick: 640}
• The context:
• DUR_CHECK {beatStart: 600, expandedStart: 600, nextExpandedBeatStart: 1440, expandedDur: 840, ratio: '1.00'}
Pair A — Bad click-to-seek (shows “clicked beat != anchor beat used”)
• CLICK_SEEK beat {beatAbs: 2880 ...}
• cursor requestSnap
• Cursor log showing it’s still rendering/anchored on a different beat (this is the key):
Maestro ... Beat 2400 | ... tick: 2882 progress... denom...
“After click-to-seek selecting beatAbs=2880, the next cursor render still reports Beat 2400 while tick is already 2882 (inside 2880).”
Pair B — Good click-to-seek (clicked beat == anchor beat used)
• CLICK_SEEK beat {beatAbs: 3360 ...}
• cursor requestSnap
• Maestro ... Beat 3360 ...
• optionally [V105→Cursor] setBeat args {beatAbs: 3360 ...}
“On this beat, click-to-seek selects beatAbs=3360 and the cursor immediately anchors Beat 3360 on the next render.”
Here is the repo to test the Stevie Ray Vaughan - Pride and Joy
https://github.com/AvaTheArchitect/louislam-borrowed-labs
To run use:
deno task start
Expected Behavior
Expected Behavior:
- AlphaTab should provide a stable, repeat-pass-safe mapping from visual position → beat/tick under shuffle/swing timing.
- boundsLookup.getBeatAtPos() and tickCache.findBeat() should not resolve a structurally earlier beat at a later expanded tick inside the owned range of the current beat.
- Cursor placement should be consistent on swung subdivisions (no intermittent left/right offset changes between adjacent bars).
- I think these would also fix the micro pause I am seeing in playback at the start of M1, but that would be the other expected outcome of this as well.
Steps To Reproduce
-
Load the GP file containing the shuffle intro (SRV-style swung triplets).
-
Click to seek on all the notes in M1, M2 and then again in M3 and you will see the issues instantly
-
Start playback from the beginning (or from the pickup into M1), you will see the wobble or shuffling or sightly bucking cursor that is more evident as the song plays the first few rows of music.
-
Observe at the start of M1 (first dyad / first shuffle group) a brief micro-pause / hesitation during playback.
-
In the console, observe that tickCache.findBeat() resolves a structurally earlier beat at a later expanded tick inside the current beat’s range:
• resolveNextBeatExpanded skipped backward candidate {curAbs: 600, bAbs: 480, firstT: 640}
• [V105] structural regression discarded {incomingStart: 480, prevAbs: 600, tick: 640}
-
Repeat playback start several times (or restart playback after seeking). Sometimes playback appears to “stick” on the initial rest while the log shows repeated structural-regression discards (e.g., incomingStart: 600/480/1440 while prevAbs is larger like 1920).
Link to jsFiddle, CodePen, Project
No response
Version and Environment
AlphaTab v1.8.1
Webpack v1.8.1 on the Maestro repo
Vite also running on v1.8.1 borrowing from LouisLam Vite Repo
It's MyTabs v1.4.0
Chrome Browser for Testing on Desktop
Platform
Web
Anything else?
No response
Is there an existing issue for this?
Current Behavior
I did have a ticket a while back but it was closed out as we thought it was a custom cursor and not a bug issue, but New Discoveries have changed that!
In shuffle/swing (swung triplets) material, AlphaTab sometimes resolves or emits beat objects that are structurally earlier at later expanded playback ticks, causing click-to-seek and cursor placement inconsistencies. This is reproducible outside our app (native cursor drift visible in a clean third-party repo).
I used the same Stevie Ray Vaughan - Pride and Joy again but mostly borrowed from the LouisLam Vite Repo on v1.8.1 to confirm the native cursor and alphaTab issues:
Repro
• Use shuffle/triplet feel material (SRV-style shuffle pattern).
• Observe that some swung subdivisions behave inconsistently:
• clicking visually on a note may select a neighboring beat
• cursor can appear offset on specific swung beats
• Evidence includes screenshots from a third-party minimal repro (LouisLam repo) showing native cursor offset on specific swung notes.
Here are some images and then I am including a video as well!
Micro-pause: In M1 (first dyad), playback shows a brief “pause/hesitation” that aligns with the same shuffled-beat inconsistency (structurally earlier beat returned at later expanded ticks), suggesting a transient beat-duration/identity mismatch rather than a rendering/performance issue.
Now it flips from M1 to M2 from down strums being off to up strums being off and then in M3 back to down strums being off.
Why this can relate to “bucking”
In the LouisLam repo and Maestro repo both custom cursor and native cursor have a wobble or shuffle back thing going on that is sometimes more visible during the shuffle sections or heavy triplet shuffles than in other places;
When AlphaTab occasionally resolves a structurally earlier beat at a later expanded tick, the cursor pipeline can momentarily anchor to the “wrong” beat identity. That mismatch (beat identity vs expanded-time position) causes the cursor to re-anchor backward or snap-correct on the next update, which reads visually as a “buckle” or “kick back.” In shuffle/triplet feel, those tiny inconsistencies are amplified because beat spacing is compressed and the cursor crosses boundaries faster.
Technical observations:
• In certain cases, tickCache.findBeat(t) returns a beat b whose absolutePlaybackStart is ≤ current beat’s absolutePlaybackStart, even though t lies inside what the current beat “owns” in expanded playback time.
• This looks like a pickup/grace beat is being resolved “late” in expanded time.
• On affected beats, the inconsistency appears in:
• boundsLookup.getBeatAtPos(x,y) vs tickCache.findBeat(t)
• native cursor placement on some swung notes
What we changed in our app (workaround)
• We treat expanded playback ticks as timeline truth for seek/publish and avoid structural pointers for repeat passes.
• We added a renderer-level guard to reject structural regression beats during normal playback (except during explicit seek/bypass windows).
• This stabilizes our cursor, but it’s a workaround for what appears to be an AlphaTab engine mapping edge case.
Questions:
1. Is there an official/recommended API or method for mapping visual click position → correct playback tick/beat in shuffle/swing + repeat contexts?
2. Is the engine expected to ever return a beat with absolutePlaybackStart <= curBeat.absolutePlaybackStart when scanning forward in expanded time? If not, can this be fixed/normalized?
3. Any known issues with cursor/hitbox placement on swung triplet subdivisions (native cursor offset)?
Attachments
• Video showing the issue
• LouisLam repo screenshots (native cursor offset)
• Console traces showing structural-regression candidates being returned during forward scans
https://youtu.be/iW7wKUle_oc
Most stand out logs:
Pair A — Bad click-to-seek (shows “clicked beat != anchor beat used”)
• CLICK_SEEK beat {beatAbs: 2880 ...}
• cursor requestSnap
• Cursor log showing it’s still rendering/anchored on a different beat (this is the key):
Maestro ... Beat 2400 | ... tick: 2882 progress... denom...
“After click-to-seek selecting beatAbs=2880, the next cursor render still reports Beat 2400 while tick is already 2882 (inside 2880).”
Pair B — Good click-to-seek (clicked beat == anchor beat used)
“On this beat, click-to-seek selects beatAbs=3360 and the cursor immediately anchors Beat 3360 on the next render.”
Here is the repo to test the Stevie Ray Vaughan - Pride and Joy
https://github.com/AvaTheArchitect/louislam-borrowed-labs
To run use:
deno task start
Expected Behavior
Expected Behavior:
Steps To Reproduce
Load the GP file containing the shuffle intro (SRV-style swung triplets).
Start playback from the beginning (or from the pickup into M1), you will see the wobble or shuffling or sightly bucking cursor that is more evident as the song plays the first few rows of music.
Observe at the start of M1 (first dyad / first shuffle group) a brief micro-pause / hesitation during playback.
In the console, observe that tickCache.findBeat() resolves a structurally earlier beat at a later expanded tick inside the current beat’s range:
• resolveNextBeatExpanded skipped backward candidate {curAbs: 600, bAbs: 480, firstT: 640}
• [V105] structural regression discarded {incomingStart: 480, prevAbs: 600, tick: 640}
Repeat playback start several times (or restart playback after seeking). Sometimes playback appears to “stick” on the initial rest while the log shows repeated structural-regression discards (e.g., incomingStart: 600/480/1440 while prevAbs is larger like 1920).
Link to jsFiddle, CodePen, Project
No response
Version and Environment
Platform
Web
Anything else?
No response