Skip to content

Loop Boundary Overshoot Causes Audible Hiccup (Reactive Wrap Timing) #2569

@AvaTheArchitect

Description

@AvaTheArchitect

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

When I tested the Song "Rise" by Extreme once more on both next.js with webpack 1.8.1 and alphaTab 1.8.1 and also on vite by using the repo testing courtesy of LouisLam during a Loop test M4-M5 on track 1 guitar. I could see the cursor pause and/or hear a split second pause in the audio each and every time.

When using api.isLooping = true, AlphaTab overshoots the loop boundary by ~10–20 ticks before resetting to startTick.

This produces:
• Audible hiccup / micro pause at loop boundary
• Console spam: No beat found for tick XXXXX
• Occurs even with:
• No percussion
• No repeat expansion
• No custom cursor
• Workers disabled
• Minimal integration

This appears to be core playback engine behavior, not app-level logic.

Observed Behavior

Example loop:
startTick: 11520
endTick: 19200

Backward scan shows:
Last valid beat before endTick: 19190

Logs during playback:

Current Playback Tick: 19190
Current Playback Tick: 19203
Current Playback Tick: 19210
...
⚠️ No beat found for tick 19225
...
Jump back to 11520

There is a consistent 10–20 tick “dead zone” past the last valid beat before the internal loop reset occurs.

At high 179 BPM this produces a clear audible glitch.

See public video here:
https://youtu.be/uzPt0MyM1KA

Expected Behavior

When api.isLooping = true, playback should:
• Wrap cleanly at the last valid beat boundary
• Never enter an invalid tick region
• Avoid any audible pause or buffer stall

Looping should be proactive, not reactive.

Request

Would it be possible to:
1. Adjust internal looping to wrap at the last valid beat instead of at endTick?
2. Or implement a proactive boundary guard internally?
3. Or expose a lower-level loop boundary callback before the invalid region is entered?

If you need another stand alone repo for this I can get that for you.

Steps To Reproduce

Repo: https://github.com/AvaTheArchitect/alphatab-integration-lab (https://github.com/AvaTheArchitect/alphatab-integration-lab)



And/or


git clone https://github.com/louislam/its-mytabs
cd its-mytabs
git checkout dev-alphatab181
deno task setup
deno task start

  1. Load my repo or LouisLam Repo and add the Extreme Rise - GP5 file. My repo already has it but it can be tested and duplicated on the Vite system as well.
  2. Load "Rise" Gp5
  3. Loop or Manual Loop M4-M5 press play
  4. As seen in the video you will get the same pause at the end of the Loop.

Isolation Steps Performed

The hiccup persists after eliminating:
• ❌ Percussion track state changes
• ❌ Repeat expansion logic
• ❌ Custom cursor interpolation
• ❌ boundsLookup.findBeat() calls
• ❌ Overlay logic
• ❌ Worker thread scheduling (useWorkers = false)
• ❌ All playerPositionChanged listeners

Even with:

api.settings.player.enableCursor = false;
api.cursorHandler = null;
api.settings.core.useWorkers = false;

The hiccup still occurs when api.isLooping = true.

Note: My current alphaTab Labs repo does not have an actual Loop system on that version I don't think, so it might be easier to just load that file onto LouisLam repo for testing via the Top Tabs Button on the top of the screen.

Link to jsFiddle, CodePen, Project

No response

Version and Environment

Environment
User Agent: 
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) 
AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/144.0.0.0 Safari/537.36
initAlphaTab.ts:207 
Screen: 1036x851
	•	AlphaTab: 1.8.1 (Same for Webpack version)
	•	Rendering mode: SVG
	•	Tested with:
	•	Maestro-AI (custom integration)
	•	LouisLam alphaTab implementation
	•	Multiple GP5 files (e.g. Rise, No More Tears, Bass-only files)
	•	BPM where issue is most noticeable: ≥ 160 BPM
	•	Workers: Tested with both useWorkers = true and false
	•	Custom cursor: Disabled during isolation
	•	Internal cursor: Disabled during isolation

Platform

Web

Anything else?

Root Cause Hypothesis

AlphaTab’s internal looping logic is reactive:
1. Playback continues until tick >= endTick
2. Engine then resets to startTick
3. During this boundary window, playback enters a region where:
• No beat exists in tick cache
• Engine attempts beat lookup
• Buffer refill / sequencing causes stall

This causes the audible pause.

The internal wrap occurs after the boundary is hit, rather than proactively before entering the invalid region.

Verified Workaround (Proactive Manual Wrap)

Disabling internal looping and manually wrapping before boundary eliminates the hiccup completely:

const start = api.playbackRange.startTick;
const endExclusive = api.playbackRange.endTick;
const endGuard = endExclusive - 10; // pre-empt overshoot

api.isLooping = false;

api.playerPositionChanged.on(e => {
const t = e.currentTick ?? e.tickPosition;
if (t >= endGuard) {
api.tickPosition = start;
}
});

Result:
• Zero hiccup
• No console spam
• Perfect audio continuity
• Works with workers enabled or disabled

This suggests the issue is specifically within the internal loop boundary logic.

Additional Notes

This behavior is reproducible across:
• Multiple GP5 files
• Multiple apps
• Multiple integrations

It is independent of:
• File content
• Repeat structure
• Rendering load
• Worker threading

This appears to be core playback scheduling logic.

Metadata

Metadata

Assignees

Type

Projects

Status

No status

Relationships

None yet

Development

No branches or pull requests

Issue actions