Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Tick Lookup mechanism for cursor placement and highlighting. #1328

Merged
merged 1 commit into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src.csharp/AlphaTab.Test/Test/Globals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public Expector(T actual)
public NotExpector<T> Not => new(_actual);
public Expector<T> Be => this;

public void Equal(object expected, string? message = null)
public void Equal(object? expected, string? message = null)
{
if (expected is int i && _actual is double)
{
Expand Down
2 changes: 1 addition & 1 deletion src.csharp/AlphaTab/Collections/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace AlphaTab.Collections
{
internal class List<T> : System.Collections.Generic.List<T>
internal class List<T> : System.Collections.Generic.List<T>, Iterable<T>
{
public List(double size)
: this(new T[(int)size])
Expand Down
2 changes: 2 additions & 0 deletions src.csharp/AlphaTab/Core/EcmaScript/Set.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public Set()
_data = new HashSet<T>();
}

public double Size => _data.Count;

public Set(IEnumerable<T> values)
{
_data = new HashSet<T>(values);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public class Set<T> : Iterable<T> {
_set = HashSet()
}

public val size : Double
get() = _set.size.toDouble()

public constructor(values: Iterable<T>?) {
_set = values?.toHashSet() ?: HashSet()
}
Expand Down
41 changes: 19 additions & 22 deletions src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class SelectionInfo {
export class AlphaTabApiBase<TSettings> {
private _startTime: number = 0;
private _trackIndexes: number[] | null = null;
private _trackIndexLookup: Set<number> | null = null;
private _isDestroyed: boolean = false;
/**
* Gets the UI facade to use for interacting with the user interface.
Expand Down Expand Up @@ -288,6 +289,7 @@ export class AlphaTabApiBase<TSettings> {
for (let track of tracks) {
this._trackIndexes.push(track.index);
}
this._trackIndexLookup = new Set<number>(this._trackIndexes)
this.onScoreLoaded(score);
this.loadMidiForScore();
this.render();
Expand All @@ -297,6 +299,7 @@ export class AlphaTabApiBase<TSettings> {
for (let track of tracks) {
this._trackIndexes.push(track.index);
}
this._trackIndexLookup = new Set<number>(this._trackIndexes)
this.render();
}
}
Expand Down Expand Up @@ -819,7 +822,7 @@ export class AlphaTabApiBase<TSettings> {
let currentBeat = this._currentBeat;
let tickCache = this._tickCache;
if (currentBeat && tickCache) {
this.player!.tickPosition = tickCache.getBeatStart(currentBeat.currentBeat);
this.player!.tickPosition = tickCache.getBeatStart(currentBeat.beat);
}
}
});
Expand All @@ -835,8 +838,8 @@ export class AlphaTabApiBase<TSettings> {
private cursorUpdateTick(tick: number, stop: boolean, shouldScroll: boolean = false): void {
let cache: MidiTickLookup | null = this._tickCache;
if (cache) {
let tracks: Track[] = this.tracks;
if (tracks.length > 0) {
let tracks = this._trackIndexLookup;
if (tracks != null && tracks.size > 0) {
let beat: MidiTickLookupFindBeatResult | null = cache.findBeat(tracks, tick, this._currentBeat);
if (beat) {
this.cursorUpdateBeat(beat, stop, shouldScroll);
Expand All @@ -854,10 +857,10 @@ export class AlphaTabApiBase<TSettings> {
shouldScroll: boolean,
forceUpdate: boolean = false
): void {
const beat: Beat = lookupResult.currentBeat;
const nextBeat: Beat | null = lookupResult.nextBeat;
const beat: Beat = lookupResult.beat;
const nextBeat: Beat | null = lookupResult.nextBeat?.beat ?? null;
const duration: number = lookupResult.duration;
const beatsToHighlight = lookupResult.beatsToHighlight;
const beatsToHighlight = lookupResult.beatLookup.highlightedBeats;

if (!beat) {
return;
Expand All @@ -871,7 +874,7 @@ export class AlphaTabApiBase<TSettings> {
let previousState: PlayerState | null = this._previousStateForCursor;
if (
!forceUpdate &&
beat === previousBeat?.currentBeat &&
beat === previousBeat?.beat &&
cache === previousCache &&
previousState === this._playerState
) {
Expand Down Expand Up @@ -1026,21 +1029,15 @@ export class AlphaTabApiBase<TSettings> {
if (nextBeat) {
// if we are moving within the same bar or to the next bar
// transition to the next beat, otherwise transition to the end of the bar.
let nextBeatBoundings: BeatBounds | null = cache.findBeat(nextBeat);
if (
(nextBeat.voice.bar.index === beat.voice.bar.index && nextBeat.index > beat.index) ||
nextBeat.voice.bar.index === beat.voice.bar.index + 1
nextBeatBoundings &&
nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds ===
barBoundings.staveGroupBounds
) {
let nextBeatBoundings: BeatBounds | null = cache.findBeat(nextBeat);
if (
nextBeatBoundings &&
nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds ===
barBoundings.staveGroupBounds
) {
nextBeatX = nextBeatBoundings.visualBounds.x;
}
nextBeatX = nextBeatBoundings.visualBounds.x;
}
}

// we need to put the transition to an own animation frame
// otherwise the stop animation above is not applied.
this.uiFacade.beginInvoke(() => {
Expand Down Expand Up @@ -1219,11 +1216,11 @@ export class AlphaTabApiBase<TSettings> {
return;
}
if (range) {
const startBeat = this._tickCache.findBeat(this.tracks, range.startTick);
const endBeat = this._tickCache.findBeat(this.tracks, range.endTick);
const startBeat = this._tickCache.findBeat(this._trackIndexLookup!, range.startTick);
const endBeat = this._tickCache.findBeat(this._trackIndexLookup!, range.endTick);
if (startBeat && endBeat) {
const selectionStart = new SelectionInfo(startBeat.currentBeat);
const selectionEnd = new SelectionInfo(endBeat.currentBeat);
const selectionStart = new SelectionInfo(startBeat.beat);
const selectionEnd = new SelectionInfo(endBeat.beat);
this.cursorSelectRange(selectionStart, selectionEnd);
}
} else {
Expand Down
63 changes: 44 additions & 19 deletions src/midi/BeatTickLookup.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,77 @@
import { Beat } from '@src/model/Beat';
import { MasterBarTickLookup } from '@src/midi/MasterBarTickLookup';

/**
* Represents the time period, for which a {@link Beat} is played.
* Represents the time period, for which one or multiple {@link Beat}s are played
*/
export class BeatTickLookup {
private _highlightedBeats: Map<number, boolean> = new Map();

/**
* Gets or sets the index of the lookup within the parent MasterBarTickLookup.
* Gets or sets the start time in midi ticks at which the given beat is played.
*/
public index: number = 0;
public start: number;

/**
* Gets or sets the parent MasterBarTickLookup to which this beat lookup belongs to.
* Gets or sets the end time in midi ticks at which the given beat is played.
*/
public masterBar!: MasterBarTickLookup;
public end: number;

/**
* Gets or sets the start time in midi ticks at which the given beat is played.
* Gets or sets a list of all beats that should be highlighted when
* the beat of this lookup starts playing. This might not mean
* the beats start at this position.
*/
public start: number = 0;
public highlightedBeats: Beat[] = [];

/**
* Gets or sets the end time in midi ticks at which the given beat is played.
* Gets the next BeatTickLookup which comes after this one and is in the same
* MasterBarTickLookup.
*/
public end: number = 0;
public nextBeat: BeatTickLookup | null = null;

/**
* Gets or sets the beat which is played.
* Gets the preivous BeatTickLookup which comes before this one and is in the same
* MasterBarTickLookup.
*/
public beat!: Beat;
public previousBeat: BeatTickLookup | null = null;

/**
* Gets or sets whether the beat is the placeholder beat for an empty bar.
* Gets the tick duration of this lookup.
*/
public isEmptyBar: boolean = false;
public get duration(): number {
return this.end - this.start;
}

public constructor(start:number, end:number) {
this.start = start;
this.end = end;
}

/**
* Gets or sets a list of all beats that should be highlighted when
* the beat of this lookup starts playing.
* Marks the given beat as highlighed as part of this lookup.
* @param beat The beat to add.
*/
public beatsToHighlight: Beat[] = [];

public highlightBeat(beat: Beat): void {
if(beat.isEmpty) {
return;
}
if (!this._highlightedBeats.has(beat.id)) {
this._highlightedBeats.set(beat.id, true);
this.beatsToHighlight.push(beat);
this.highlightedBeats.push(beat);
}
}

/**
* Looks for the first visible beat which starts at this lookup so it can be used for cursor placement.
* @param visibleTracks The visible tracks.
* @returns The first beat which is visible according to the given tracks or null.
*/
public getVisibleBeatAtStart(visibleTracks: Set<number>): Beat | null {
for (const b of this.highlightedBeats) {
if (b.playbackStart == this.start && visibleTracks.has(b.voice.bar.staff.track.index)) {
return b;
}
}
return null;
}
}
Loading