Skip to main content

Beat visibility and note bounds

Only redraw overlays for beats the reader can actually see. Turn a note-picker UI on and off without paying the memory cost the rest of the time. Two orthogonal knobs sit next to boundsLookup and cover both jobs. api.isVisible(beat) reports whether a beat is inside the current viewport โ€” cheap to call from a played-beat handler. core.includeNoteBounds opts alphaTab into recording per-note rectangles, unlocking note-level hit-testing at the cost of a slightly larger bounds table. The readout below reports isVisible per beat; the overlay swaps between beat rectangles and per-note-head rectangles as includeNoteBounds flips on and off.

  • includeNoteBounds: on
  • includeNoteBounds: off
Current beat: โ€” โ€” visible: โ€”

isVisible(beat)โ€‹

api.isVisible(beat) returns true when the beat's bounds fall inside the currently rendered viewport. It reads from boundsLookup.findBeat(beat) under the hood โ€” a beat that never made it into the current render (partial render, off-screen bar, hidden track) returns false.

Wire it into a played-beat handler to gate expensive UI work:

  • Skip drawing a highlight overlay when the beat is not on-screen.
  • Suppress a camera-follow scroll pulse if the beat is already inside the visible area.
  • Tag a chord readout as "off-screen" when the user scrolled away from the cursor.

core.includeNoteBoundsโ€‹

Off by default. Turn it on and alphaTab captures the rectangle of every note head or tab number during render, populating beatBounds.notes and enabling boundsLookup.getNoteAtPos(...).

The trade-off:

  • Off โ€” beat-level hit-testing only. Smaller bounds table, faster render. Note-mouse events never fire.
  • On โ€” full note-level precision. The noteMouseDown / Move / Up events start firing. Slight memory and render-time cost proportional to note density.

Turn it on when your UI needs to click a specific note (audition, edit, popup a note-level annotation) โ€” not when a beat-level click is enough.

Show it in codeโ€‹

import * as alphaTab from '@coderline/alphatab';

const container = getAlphaTabContainer();
const chordReadout = document.getElementById('chord-readout');

const api = new alphaTab.AlphaTabApi(container, {
core: {
file: '/scores/song.gp',
includeNoteBounds: true,
},
player: { playerMode: alphaTab.PlayerMode.EnabledAutomatic },
});

api.playedBeatChanged.on((beat) => {
if (!api.isVisible(beat)) {
chordReadout.dataset.offscreen = 'true';
return;
}
chordReadout.dataset.offscreen = 'false';
chordReadout.textContent = beat.chord?.name ?? '';
});

api.noteMouseDown.on((note) => {
api.playNote(note);
});

getAlphaTabContainer() is a placeholder for the platform-specific step of obtaining the render target. Every other line runs unchanged on all three runtimes.

Where to look nextโ€‹