Skip to content

Commit 55f493c

Browse files
committed
fix(rendering): Correct cursor movement on repeats
1 parent 09ea7fe commit 55f493c

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

src/AlphaTabApiBase.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { AlphaSynthMidiFileHandler } from '@src/midi/AlphaSynthMidiFileHandler';
22
import { MidiFileGenerator } from '@src/midi/MidiFileGenerator';
33

44
import { MidiFile } from '@src/midi/MidiFile';
5-
import { MidiTickLookup, MidiTickLookupFindBeatResult } from '@src/midi/MidiTickLookup';
5+
import {
6+
MidiTickLookup,
7+
MidiTickLookupFindBeatResult,
8+
MidiTickLookupFindBeatResultCursorMode
9+
} from '@src/midi/MidiTickLookup';
610
import { IAlphaSynth } from '@src/synth/IAlphaSynth';
711
import { PlaybackRange } from '@src/synth/PlaybackRange';
812
import { PlayerState } from '@src/synth/PlayerState';
@@ -837,7 +841,7 @@ export class AlphaTabApiBase<TSettings> {
837841
this.player.positionChanged.on(e => {
838842
this._previousTick = e.currentTick;
839843
this.uiFacade.beginInvoke(() => {
840-
this.cursorUpdateTick(e.currentTick, false);
844+
this.cursorUpdateTick(e.currentTick, false, false, e.isSeek);
841845
});
842846
});
843847
this.player.stateChanged.on(e => {
@@ -859,14 +863,14 @@ export class AlphaTabApiBase<TSettings> {
859863
* @param stop
860864
* @param shouldScroll whether we should scroll to the bar (if scrolling is active)
861865
*/
862-
private cursorUpdateTick(tick: number, stop: boolean, shouldScroll: boolean = false): void {
866+
private cursorUpdateTick(tick: number, stop: boolean, shouldScroll: boolean = false, forceUpdate:boolean = false): void {
863867
let cache: MidiTickLookup | null = this._tickCache;
864868
if (cache) {
865869
let tracks = this._trackIndexLookup;
866870
if (tracks != null && tracks.size > 0) {
867871
let beat: MidiTickLookupFindBeatResult | null = cache.findBeat(tracks, tick, this._currentBeat);
868872
if (beat) {
869-
this.cursorUpdateBeat(beat, stop, shouldScroll);
873+
this.cursorUpdateBeat(beat, stop, shouldScroll, forceUpdate || this.playerState === PlayerState.Paused);
870874
}
871875
}
872876
}
@@ -900,7 +904,8 @@ export class AlphaTabApiBase<TSettings> {
900904
!forceUpdate &&
901905
beat === previousBeat?.beat &&
902906
cache === previousCache &&
903-
previousState === this._playerState
907+
previousState === this._playerState &&
908+
previousBeat?.start === lookupResult.start
904909
) {
905910
return;
906911
}
@@ -924,7 +929,8 @@ export class AlphaTabApiBase<TSettings> {
924929
beatsToHighlight,
925930
cache!,
926931
beatBoundings!,
927-
shouldScroll
932+
shouldScroll,
933+
lookupResult.cursorMode
928934
);
929935
});
930936
}
@@ -1010,7 +1016,8 @@ export class AlphaTabApiBase<TSettings> {
10101016
beatsToHighlight: BeatTickLookupItem[],
10111017
cache: BoundsLookup,
10121018
beatBoundings: BeatBounds,
1013-
shouldScroll: boolean
1019+
shouldScroll: boolean,
1020+
cursorMode: MidiTickLookupFindBeatResultCursorMode
10141021
) {
10151022
const barCursor = this._barCursor;
10161023
const beatCursor = this._beatCursor;
@@ -1050,7 +1057,7 @@ export class AlphaTabApiBase<TSettings> {
10501057
if (this.settings.player.enableAnimatedBeatCursor) {
10511058
let nextBeatX: number = barBoundings.visualBounds.x + barBoundings.visualBounds.w;
10521059
// get position of next beat on same system
1053-
if (nextBeat) {
1060+
if (nextBeat && cursorMode == MidiTickLookupFindBeatResultCursorMode.ToNextBext) {
10541061
// if we are moving within the same bar or to the next bar
10551062
// transition to the next beat, otherwise transition to the end of the bar.
10561063
let nextBeatBoundings: BeatBounds | null = cache.findBeat(nextBeat);

src/midi/MidiTickLookup.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ import { MidiUtils } from '@src/midi/MidiUtils';
44
import { Beat } from '@src/model/Beat';
55
import { MasterBar } from '@src/model/MasterBar';
66

7+
/**
8+
* Describes how a cursor should be moving.
9+
*/
10+
export enum MidiTickLookupFindBeatResultCursorMode {
11+
/**
12+
* Unknown/Undetermined mode. Should not happen on user level.
13+
*/
14+
Unknown,
15+
16+
/**
17+
* The cursor should animate to the next beat.
18+
*/
19+
ToNextBext,
20+
21+
/**
22+
* The cursor should animate to the end of the bar (typically on repeats and jumps)
23+
*/
24+
ToEndOfBar
25+
}
26+
727
/**
828
* Represents the results of searching the currently played beat.
929
* @see MidiTickLookup.FindBeat
@@ -40,6 +60,11 @@ export class MidiTickLookupFindBeatResult {
4060
*/
4161
public duration: number = 0;
4262

63+
/**
64+
* The mode how the cursor should be handled.
65+
*/
66+
public cursorMode: MidiTickLookupFindBeatResultCursorMode = MidiTickLookupFindBeatResultCursorMode.Unknown;
67+
4368
public get start(): number {
4469
return this.masterBar.start + this.beatLookup.start;
4570
}
@@ -57,7 +82,7 @@ export class MidiTickLookupFindBeatResult {
5782
if (this.masterBar.tempoChanges.length === 1) {
5883
this.duration = MidiUtils.ticksToMillis(this.tickDuration, this.masterBar.tempoChanges[0].tempo);
5984
} else {
60-
// Performance Note: I still wonder if we cannot calculate these slices efficiently ahead-of-time.
85+
// Performance Note: I still wonder if we cannot calculate these slices efficiently ahead-of-time.
6186
// the sub-slicing in the lookup across beats makes it a bit tricky to do on-the-fly
6287
// but maybe on finalizing the tick lookup?
6388

@@ -77,7 +102,7 @@ export class MidiTickLookupFindBeatResult {
77102
// next change is after beat, we can stop looking at changes
78103
else if (change.tick > endTick) {
79104
break;
80-
}
105+
}
81106
// change while beat is playing
82107
else {
83108
millis += MidiUtils.ticksToMillis(change.tick - currentTick, currentTempo);
@@ -87,7 +112,7 @@ export class MidiTickLookupFindBeatResult {
87112
}
88113

89114
// last slice
90-
if(endTick > currentTick) {
115+
if (endTick > currentTick) {
91116
millis += MidiUtils.ticksToMillis(endTick - currentTick, currentTempo);
92117
}
93118

@@ -199,7 +224,6 @@ export class MidiTickLookup {
199224
current.beatLookup.nextBeat,
200225
current.end,
201226
trackLookup,
202-
false,
203227
true
204228
);
205229

@@ -210,14 +234,29 @@ export class MidiTickLookup {
210234
// if we have the next beat take the difference between the times as duration
211235
if (current.nextBeat) {
212236
current.tickDuration = current.nextBeat.start - current.start;
237+
current.cursorMode = MidiTickLookupFindBeatResultCursorMode.ToNextBext;
213238
current.calculateDuration();
214239
}
215240

216241
// no next beat, animate to the end of the bar (could be an incomplete bar)
217242
if (!current.nextBeat) {
218243
current.tickDuration = current.masterBar.end - current.start;
244+
current.cursorMode = MidiTickLookupFindBeatResultCursorMode.ToEndOfBar;
219245
current.calculateDuration();
220246
}
247+
248+
// if the next beat is not directly the next master bar (e.g. jumping back or forth)
249+
// we report no next beat and animate to the end
250+
if (
251+
current.nextBeat &&
252+
current.nextBeat.masterBar.masterBar.index != current.masterBar.masterBar.index + 1 &&
253+
(
254+
current.nextBeat.masterBar.masterBar.index != current.masterBar.masterBar.index ||
255+
current.nextBeat.beat.playbackStart <= current.beat.playbackStart
256+
)
257+
) {
258+
current.cursorMode = MidiTickLookupFindBeatResultCursorMode.ToEndOfBar;
259+
}
221260
}
222261

223262
private findBeatSlow(
@@ -261,7 +300,6 @@ export class MidiTickLookup {
261300
masterBar.firstBeat,
262301
tick,
263302
trackLookup,
264-
true,
265303
isNextSearch
266304
);
267305

@@ -282,16 +320,15 @@ export class MidiTickLookup {
282320
* @param currentStartLookup
283321
* @param tick
284322
* @param visibleTracks
285-
* @param fillNext
323+
* @param isNextSearch
286324
* @returns
287325
*/
288326
private findBeatInMasterBar(
289327
masterBar: MasterBarTickLookup,
290328
currentStartLookup: BeatTickLookup | null,
291329
tick: number,
292330
visibleTracks: Set<number>,
293-
fillNext: boolean,
294-
isNextSeach: boolean
331+
isNextSearch: boolean
295332
): MidiTickLookupFindBeatResult | null {
296333
if (!currentStartLookup) {
297334
return null;
@@ -310,7 +347,7 @@ export class MidiTickLookup {
310347
// found the matching beat lookup but none of the beats are visible
311348
// in this case scan further to the next lookup which has any visible beat
312349
if (!startBeat) {
313-
if (isNextSeach) {
350+
if (isNextSearch) {
314351
let currentMasterBar: MasterBarTickLookup | null = masterBar;
315352
while (currentMasterBar != null && startBeat == null) {
316353
while (currentStartLookup != null) {
@@ -363,7 +400,7 @@ export class MidiTickLookup {
363400
return null;
364401
}
365402

366-
const result = this.createResult(masterBar, startBeatLookup!, startBeat, fillNext, visibleTracks);
403+
const result = this.createResult(masterBar, startBeatLookup!, startBeat, isNextSearch, visibleTracks);
367404

368405
return result;
369406
}
@@ -372,7 +409,7 @@ export class MidiTickLookup {
372409
masterBar: MasterBarTickLookup,
373410
beatLookup: BeatTickLookup,
374411
beat: Beat,
375-
fillNext: boolean,
412+
isNextSearch: boolean,
376413
visibleTracks: Set<number>
377414
) {
378415
const result = new MidiTickLookupFindBeatResult(masterBar);
@@ -382,7 +419,7 @@ export class MidiTickLookup {
382419

383420
result.tickDuration = beatLookup!.end - beatLookup!.start;
384421

385-
if (fillNext) {
422+
if (!isNextSearch) {
386423
this.fillNextBeat(result, visibleTracks);
387424
}
388425

0 commit comments

Comments
 (0)