Skip to content

Commit 5789b59

Browse files
authored
feat: hide empty staves (#2443)
1 parent a1bd11d commit 5789b59

31 files changed

+805
-161
lines changed

packages/alphatab/.env

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
FORCE_COLOR=1
2-
NODE_OPTIONS=--expose-gc
3-
UPDATE_SNAPSHOT=true
2+
NODE_OPTIONS=--expose-gc

packages/alphatab/src/AlphaTabApiBase.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { AlphaTexImporter } from '@coderline/alphatab/importer/AlphaTexImporter';
1111
import { Logger } from '@coderline/alphatab/Logger';
1212
import { AlphaSynthMidiFileHandler } from '@coderline/alphatab/midi/AlphaSynthMidiFileHandler';
13-
import type { BeatTickLookupItem } from '@coderline/alphatab/midi/BeatTickLookup';
13+
import type { BeatTickLookupItem, IBeatVisibilityChecker } from '@coderline/alphatab/midi/BeatTickLookup';
1414
import type {
1515
MetaDataEvent,
1616
MetaEvent,
@@ -122,6 +122,22 @@ export interface PlaybackHighlightChangeEventArgs {
122122
highlightBlocks?: Bounds[];
123123
}
124124

125+
/**
126+
* @internal
127+
*/
128+
class BoundsLookupVisibilityChecker implements IBeatVisibilityChecker {
129+
public bounds: BoundsLookup | null = null;
130+
131+
public isVisible(beat: Beat): boolean {
132+
const bounds = this.bounds;
133+
if (!bounds) {
134+
return false;
135+
}
136+
137+
return bounds.findBeat(beat) !== null;
138+
}
139+
}
140+
125141
/**
126142
* This class represents the public API of alphaTab and provides all logic to display
127143
* a music sheet in any UI using the given {@link IUiFacade}
@@ -132,6 +148,7 @@ export class AlphaTabApiBase<TSettings> {
132148
private _startTime: number = 0;
133149
private _trackIndexes: number[] | null = null;
134150
private _trackIndexLookup: Set<number> | null = null;
151+
private readonly _beatVisibilityChecker = new BoundsLookupVisibilityChecker();
135152
private _isDestroyed: boolean = false;
136153
private _score: Score | null = null;
137154
private _tracks: Track[] = [];
@@ -2049,18 +2066,19 @@ export class AlphaTabApiBase<TSettings> {
20492066

20502067
const cache: MidiTickLookup | null = this._tickCache;
20512068
if (cache) {
2052-
const tracks = this._trackIndexLookup;
2053-
if (tracks != null && tracks.size > 0) {
2054-
const beat: MidiTickLookupFindBeatResult | null = cache.findBeat(tracks, tick, this._currentBeat);
2055-
if (beat) {
2056-
this._cursorUpdateBeat(
2057-
beat,
2058-
stop,
2059-
shouldScroll,
2060-
cursorSpeed,
2061-
forceUpdate || this.playerState === PlayerState.Paused
2062-
);
2063-
}
2069+
const beat: MidiTickLookupFindBeatResult | null = cache.findBeatWithChecker(
2070+
this._beatVisibilityChecker,
2071+
tick,
2072+
this._currentBeat
2073+
);
2074+
if (beat) {
2075+
this._cursorUpdateBeat(
2076+
beat,
2077+
stop,
2078+
shouldScroll,
2079+
cursorSpeed,
2080+
forceUpdate || this.playerState === PlayerState.Paused
2081+
);
20642082
}
20652083
}
20662084
}
@@ -3420,6 +3438,8 @@ export class AlphaTabApiBase<TSettings> {
34203438
return;
34213439
}
34223440

3441+
this._beatVisibilityChecker.bounds = this.boundsLookup;
3442+
34233443
this._currentBeat = null;
34243444
this._cursorUpdateTick(this._previousTick, false, 1, true, true);
34253445

packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export class RenderStylesheetSerializer {
5959
}
6060
}
6161
o.set("extendbarlines", obj.extendBarLines);
62+
o.set("hideemptystaves", obj.hideEmptyStaves);
63+
o.set("hideemptystavesinfirstsystem", obj.hideEmptyStavesInFirstSystem);
64+
o.set("showsinglestaffbrackets", obj.showSingleStaffBrackets);
6265
return o;
6366
}
6467
public static setProperty(obj: RenderStylesheet, property: string, v: unknown): boolean {
@@ -120,6 +123,15 @@ export class RenderStylesheetSerializer {
120123
case "extendbarlines":
121124
obj.extendBarLines = v! as boolean;
122125
return true;
126+
case "hideemptystaves":
127+
obj.hideEmptyStaves = v! as boolean;
128+
return true;
129+
case "hideemptystavesinfirstsystem":
130+
obj.hideEmptyStavesInFirstSystem = v! as boolean;
131+
return true;
132+
case "showsinglestaffbrackets":
133+
obj.showSingleStaffBrackets = v! as boolean;
134+
return true;
123135
}
124136
return false;
125137
}

packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,10 @@ export class AlphaTex1LanguageDefinitions {
190190
['firstsystemtracknameorientation', [[[[10, 17], 0, ['horizontal', 'vertical']]]]],
191191
['othersystemstracknameorientation', [[[[10, 17], 0, ['horizontal', 'vertical']]]]],
192192
['extendbarlines', null],
193-
['chorddiagramsinscore', [[[[10], 1, ['true', 'false']]]]]
193+
['chorddiagramsinscore', [[[[10], 1, ['true', 'false']]]]],
194+
['hideemptystaves', null],
195+
['hideemptystavesinfirstsystem', null],
196+
['showsinglestaffbrackets', null]
194197
]);
195198
public static readonly staffMetaDataSignatures = AlphaTex1LanguageDefinitions._signatures([
196199
['tuning', [[[[10, 17], 0, ['piano', 'none', 'voice']]], [[[10, 17], 5]]]],
@@ -530,6 +533,9 @@ export class AlphaTex1LanguageDefinitions {
530533
['othersystemstracknameorientation', null],
531534
['extendbarlines', null],
532535
['chorddiagramsinscore', null],
536+
['hideemptystaves', null],
537+
['hideemptystavesinfirstsystem', null],
538+
['showsinglestaffbrackets', null],
533539
[
534540
'tuning',
535541
[

packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,15 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
264264
? AlphaTex1LanguageHandler._booleanLikeValue(metaData.arguments!.arguments, 0)
265265
: true;
266266
return ApplyNodeResult.Applied;
267+
case 'hideemptystaves':
268+
score.stylesheet.hideEmptyStaves = true;
269+
return ApplyNodeResult.Applied;
270+
case 'hideemptystavesinfirstsystem':
271+
score.stylesheet.hideEmptyStavesInFirstSystem = true;
272+
return ApplyNodeResult.Applied;
273+
case 'showsinglestaffbrackets':
274+
score.stylesheet.showSingleStaffBrackets = true;
275+
return ApplyNodeResult.Applied;
267276

268277
default:
269278
return ApplyNodeResult.NotAppliedUnrecognizedMarker;
@@ -2497,6 +2506,18 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
24972506
if (stylesheet.globalDisplayChordDiagramsInScore) {
24982507
nodes.push(Atnf.meta('chordDiagramsInScore'));
24992508
}
2509+
2510+
if (stylesheet.hideEmptyStaves) {
2511+
nodes.push(Atnf.meta('hideEmptyStaves'));
2512+
}
2513+
2514+
if (stylesheet.hideEmptyStavesInFirstSystem) {
2515+
nodes.push(Atnf.meta('hideEmptyStavesInFirstSystem'));
2516+
}
2517+
2518+
if (stylesheet.showSingleStaffBrackets) {
2519+
nodes.push(Atnf.meta('showSingleStaffBrackets'));
2520+
}
25002521

25012522
// Unsupported:
25022523
// 'globaldisplaychorddiagramsontop',

packages/alphatab/src/midi/BeatTickLookup.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export class BeatTickLookupItem {
2121
}
2222
}
2323

24+
/**
25+
* Classes implementing this interface can help in checking whether beats are currently being
26+
* displayed so that they can be considered for a tick-search.
27+
* @public
28+
*/
29+
export interface IBeatVisibilityChecker {
30+
isVisible(beat: Beat): boolean;
31+
}
32+
2433
/**
2534
* Represents the time period, for which one or multiple {@link Beat}s are played
2635
* @public
@@ -96,4 +105,18 @@ export class BeatTickLookup {
96105
}
97106
return null;
98107
}
108+
109+
/**
110+
* Looks for the first visible beat which starts at this lookup so it can be used for cursor placement.
111+
* @param checker The custom checker to see if a beat is visible.
112+
* @returns The first beat which is visible according to the given tracks or null.
113+
*/
114+
getVisibleBeatAtStartWithChecker(checker: IBeatVisibilityChecker): Beat | null {
115+
for (const b of this.highlightedBeats) {
116+
if (b.playbackStart === this.start && checker.isVisible(b.beat)) {
117+
return b.beat;
118+
}
119+
}
120+
return null;
121+
}
99122
}

0 commit comments

Comments
 (0)