Skip to main content

Bounds lookup

Live annotations pinned above a beat, a lasso selector across a phrase, a live-comment marker that follows the notation through re-layouts โ€” any UI that has to line up with the rendered score reads from the same lookup. After every render, api.boundsLookup exposes a hierarchical index of the rendered geometry โ€” staff systems, master bars, bars, beats, notes โ€” with pixel-accurate rectangles at every level. Use it to hit-test a mouse position, place an overlay on a beat, or draw a custom marker on top of the notation.

  • Visual Bounds
  • Real Bounds
|
  • Staff System
  • Master Bars
  • Bars
  • Beats
  • Beat Time
  • Notes

The hierarchyโ€‹

boundsLookup.staffSystems is the root. Each level below narrows the region:

  • staffSystems โ€” the rows of staves flowing down the page.
  • bars(1) โ€” a master bar across every track's staves.
  • bars(2) โ€” a single track's slice of that master bar.
  • beats โ€” the beats inside that bar.
  • notes โ€” the individual note heads or tab numbers. Only populated when core.includeNoteBounds is true.

Every level exposes two rectangles: visualBounds (the inked area) and realBounds (the inked area plus surrounding whitespace up to neighboring elements). Note bounds additionally carry noteHeadBounds.

Look up a positionโ€‹

getBeatAtPos(x, y) returns the beat under a screen coordinate. getNoteAtPos(beat, x, y) narrows it to the note head or tab number when per-note bounds are enabled. findBeat(beat) goes the other direction โ€” give it a Beat, get its rectangle back.

Draw an overlayโ€‹

Wait for postRenderFinished, read boundsLookup, find the target beat, and place an absolutely positioned element on top of the render container.

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

const container = getAlphaTabContainer();
const api = new alphaTab.AlphaTabApi(container, {
core: { file: '/scores/song.gp' },
});

api.postRenderFinished.on(() => {
const lookup = api.boundsLookup;
if (!lookup) {
return;
}
const firstBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0];
const bounds = lookup.findBeat(firstBeat);
if (!bounds) {
return;
}

const marker = document.createElement('div');
marker.style.position = 'absolute';
marker.style.left = `${bounds.visualBounds.x}px`;
marker.style.top = `${bounds.visualBounds.y}px`;
marker.style.width = `${bounds.visualBounds.w}px`;
marker.style.height = `${bounds.visualBounds.h}px`;
marker.style.background = 'rgba(66, 109, 157, 0.25)';
marker.style.pointerEvents = 'none';
container.appendChild(marker);
});

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โ€‹