@coderline/alphatab 1.3.0-alpha.870 → 1.3.0-alpha.873

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/alphaTab.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * alphaTab v1.3.0-alpha.870 (develop, build 870)
2
+ * alphaTab v1.3.0-alpha.873 (develop, build 873)
3
3
  *
4
4
  * Copyright © 2023, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -16810,38 +16810,61 @@
16810
16810
  }
16811
16811
 
16812
16812
  /**
16813
- * Represents the time period, for which a {@link Beat} is played.
16813
+ * Represents the time period, for which one or multiple {@link Beat}s are played
16814
16814
  */
16815
16815
  class BeatTickLookup {
16816
- constructor() {
16816
+ /**
16817
+ * Gets the tick duration of this lookup.
16818
+ */
16819
+ get duration() {
16820
+ return this.end - this.start;
16821
+ }
16822
+ constructor(start, end) {
16817
16823
  this._highlightedBeats = new Map();
16818
16824
  /**
16819
- * Gets or sets the index of the lookup within the parent MasterBarTickLookup.
16820
- */
16821
- this.index = 0;
16822
- /**
16823
- * Gets or sets the start time in midi ticks at which the given beat is played.
16824
- */
16825
- this.start = 0;
16826
- /**
16827
- * Gets or sets the end time in midi ticks at which the given beat is played.
16825
+ * Gets or sets a list of all beats that should be highlighted when
16826
+ * the beat of this lookup starts playing. This might not mean
16827
+ * the beats start at this position.
16828
16828
  */
16829
- this.end = 0;
16829
+ this.highlightedBeats = [];
16830
16830
  /**
16831
- * Gets or sets whether the beat is the placeholder beat for an empty bar.
16831
+ * Gets the next BeatTickLookup which comes after this one and is in the same
16832
+ * MasterBarTickLookup.
16832
16833
  */
16833
- this.isEmptyBar = false;
16834
+ this.nextBeat = null;
16834
16835
  /**
16835
- * Gets or sets a list of all beats that should be highlighted when
16836
- * the beat of this lookup starts playing.
16836
+ * Gets the preivous BeatTickLookup which comes before this one and is in the same
16837
+ * MasterBarTickLookup.
16837
16838
  */
16838
- this.beatsToHighlight = [];
16839
+ this.previousBeat = null;
16840
+ this.start = start;
16841
+ this.end = end;
16839
16842
  }
16843
+ /**
16844
+ * Marks the given beat as highlighed as part of this lookup.
16845
+ * @param beat The beat to add.
16846
+ */
16840
16847
  highlightBeat(beat) {
16848
+ if (beat.isEmpty) {
16849
+ return;
16850
+ }
16841
16851
  if (!this._highlightedBeats.has(beat.id)) {
16842
16852
  this._highlightedBeats.set(beat.id, true);
16843
- this.beatsToHighlight.push(beat);
16853
+ this.highlightedBeats.push(beat);
16854
+ }
16855
+ }
16856
+ /**
16857
+ * Looks for the first visible beat which starts at this lookup so it can be used for cursor placement.
16858
+ * @param visibleTracks The visible tracks.
16859
+ * @returns The first beat which is visible according to the given tracks or null.
16860
+ */
16861
+ getVisibleBeatAtStart(visibleTracks) {
16862
+ for (const b of this.highlightedBeats) {
16863
+ if (b.playbackStart == this.start && visibleTracks.has(b.voice.bar.staff.track.index)) {
16864
+ return b;
16865
+ }
16844
16866
  }
16867
+ return null;
16845
16868
  }
16846
16869
  }
16847
16870
 
@@ -16862,32 +16885,288 @@
16862
16885
  * Gets or sets the current tempo when the MasterBar is played.
16863
16886
  */
16864
16887
  this.tempo = 0;
16865
- /**
16866
- * Gets or sets the list of {@link BeatTickLookup} object which define the durations
16867
- * for all {@link Beats} played within the period of this MasterBar.
16868
- */
16869
- this.beats = [];
16888
+ this.firstBeat = null;
16889
+ this.lastBeat = null;
16870
16890
  /**
16871
16891
  * Gets or sets the {@link MasterBarTickLookup} of the next masterbar in the {@link Score}
16872
16892
  */
16873
16893
  this.nextMasterBar = null;
16874
16894
  }
16875
16895
  /**
16876
- * Performs the neccessary finalization steps after all information was written.
16896
+ * Inserts `newNextBeat` after `currentBeat` in the linked list of items and updates.
16897
+ * the `firstBeat` and `lastBeat` respectively too.
16898
+ * @param currentBeat The item in which to insert the new item afterwards
16899
+ * @param newBeat The new item to insert
16877
16900
  */
16878
- finish() {
16879
- this.beats.sort((a, b) => {
16880
- return a.start - b.start;
16881
- });
16901
+ insertAfter(currentBeat, newBeat) {
16902
+ if (this.firstBeat == null || currentBeat == null || this.lastBeat == null) {
16903
+ this.firstBeat = newBeat;
16904
+ this.lastBeat = newBeat;
16905
+ }
16906
+ else {
16907
+ // link new node into sequence
16908
+ newBeat.nextBeat = currentBeat.nextBeat;
16909
+ newBeat.previousBeat = currentBeat;
16910
+ // update this node accordinly
16911
+ if (currentBeat.nextBeat) {
16912
+ currentBeat.nextBeat.previousBeat = newBeat;
16913
+ }
16914
+ currentBeat.nextBeat = newBeat;
16915
+ if (currentBeat == this.lastBeat) {
16916
+ this.lastBeat = newBeat;
16917
+ }
16918
+ }
16882
16919
  }
16883
16920
  /**
16884
- * Adds a new {@link BeatTickLookup} to the list of played beats during this MasterBar period.
16885
- * @param beat
16886
- */
16887
- addBeat(beat) {
16888
- beat.masterBar = this;
16889
- beat.index = this.beats.length;
16890
- this.beats.push(beat);
16921
+ * Inserts `newNextBeat` before `currentBeat` in the linked list of items and updates.
16922
+ * the `firstBeat` and `lastBeat` respectively too.
16923
+ * @param currentBeat The item in which to insert the new item afterwards
16924
+ * @param newBeat The new item to insert
16925
+ */
16926
+ insertBefore(currentBeat, newBeat) {
16927
+ if (this.firstBeat == null || currentBeat == null || this.lastBeat == null) {
16928
+ this.firstBeat = newBeat;
16929
+ this.lastBeat = newBeat;
16930
+ }
16931
+ else {
16932
+ // link new node into sequence
16933
+ newBeat.previousBeat = currentBeat.previousBeat;
16934
+ newBeat.nextBeat = currentBeat;
16935
+ // update this node accordingly
16936
+ if (currentBeat.previousBeat) {
16937
+ currentBeat.previousBeat.nextBeat = newBeat;
16938
+ }
16939
+ currentBeat.previousBeat = newBeat;
16940
+ if (currentBeat == this.firstBeat) {
16941
+ this.firstBeat = newBeat;
16942
+ }
16943
+ }
16944
+ }
16945
+ /**
16946
+ * Adds a new beat to this masterbar following the slicing logic required by the MidiTickLookup.
16947
+ * @returns The first item of the chain which was affected.
16948
+ */
16949
+ addBeat(beat, start, duration) {
16950
+ const end = start + duration;
16951
+ // We have following scenarios we cover overall on inserts
16952
+ // Technically it would be possible to merge some code paths and work with loops
16953
+ // to handle all scenarios in a shorter piece of code.
16954
+ // but this would make the core a lot harder to understand an less readable
16955
+ // and maintainable for the different scenarios.
16956
+ // we keep them separate here for that purpose and sacrifice some bytes of code for that.
16957
+ // Variant A (initial Insert)
16958
+ // | New |
16959
+ // Result A
16960
+ // | New |
16961
+ // Variant B (insert at end, start matches)
16962
+ // | L1 | L2 |
16963
+ // | New |
16964
+ // Result B
16965
+ // | L1 | L2 | N1 |
16966
+ // Variant C (insert at end, with gap)
16967
+ // | L1 | L2 |
16968
+ // | New |
16969
+ // Result C
16970
+ // | L1 | L2 | N1 |
16971
+ // Variant D (Starts before, ends exactly):
16972
+ // | L1 | L2 |
16973
+ // | New |
16974
+ // Result D:
16975
+ // | N1 | L1 | L2 |
16976
+ // Variant E (Starts before, with gap):
16977
+ // | L1 | L2 |
16978
+ // | New |
16979
+ // Result E:
16980
+ // | N1 | L1 | L2 |
16981
+ // Variant F (starts before, overlaps partially):
16982
+ // | L1 | L2 |
16983
+ // | New |
16984
+ // Result F:
16985
+ // | N1 | N2 | L1 | L2 |
16986
+ // Variant G (starts before, ends the same):
16987
+ // | L1 | L2 |
16988
+ // | New |
16989
+ // Result G:
16990
+ // | N1 | L1 | L2 |
16991
+ // Variant H (starts before, ends after L1):
16992
+ // | L1 | L2 |
16993
+ // | New |
16994
+ // Result H:
16995
+ // Step 1 (only slice L1):
16996
+ // | N1 | L1 | L2 |
16997
+ // Step 2 (call recursively with start time of 'new' adjusted):
16998
+ // | New |
16999
+ // | N1 | L1 | N2 | L2 |
17000
+ // Variant I (starts in the middle, ends exactly)
17001
+ // | L1 | L2 |
17002
+ // | New |
17003
+ // Result I
17004
+ // | N1 | L1 | L2 |
17005
+ // Variant J (starts in the middle, ends before)
17006
+ // | L1 | L2 |
17007
+ // | New |
17008
+ // Result J
17009
+ // |N1| N2 |L1 | L2 |
17010
+ // Variant K (starts in the middle, ends after L1)
17011
+ // | L1 | L2 |
17012
+ // | New |
17013
+ // Result K
17014
+ // Step 1 (only slice L1):
17015
+ // | N1 | L1 | L2 |
17016
+ // Step 2 (call recursively with start time of 'new' adjusted):
17017
+ // | New |
17018
+ // | N1 | L1 | L2 |
17019
+ // Variant L (starts exactly, ends exactly)
17020
+ // | L1 | L2 |
17021
+ // | New |
17022
+ // Result L
17023
+ // | L1 | L2 |
17024
+ // Variant M (starts exactly, ends before)
17025
+ // | L1 | L2 |
17026
+ // | New |
17027
+ // Result M
17028
+ // | N1 | L1 | L2 |
17029
+ // Variant N (starts exactly, ends after L1)
17030
+ // | L1 | L2 |
17031
+ // | New |
17032
+ // Result N
17033
+ // Step 1 (only update L1):
17034
+ // | L1 | L2 |
17035
+ // Step 2 (call recursively with start time of 'new' adjusted):
17036
+ // | New |
17037
+ // | L 1 | L2 |
17038
+ // Variant A
17039
+ if (this.firstBeat == null) {
17040
+ const n1 = new BeatTickLookup(start, end);
17041
+ n1.highlightBeat(beat);
17042
+ this.insertAfter(this.firstBeat, n1);
17043
+ }
17044
+ // Variant B
17045
+ // Variant C
17046
+ else if (start >= this.lastBeat.end) {
17047
+ // using the end here allows merge of B & C
17048
+ const n1 = new BeatTickLookup(this.lastBeat.end, end);
17049
+ n1.highlightBeat(beat);
17050
+ this.insertAfter(this.lastBeat, n1);
17051
+ }
17052
+ else {
17053
+ let l1 = null;
17054
+ if (start < this.firstBeat.start) {
17055
+ l1 = this.firstBeat;
17056
+ }
17057
+ else {
17058
+ let current = this.firstBeat;
17059
+ while (current != null) {
17060
+ // find item where we fall into
17061
+ if (start >= current.start && start < current.end) {
17062
+ l1 = current;
17063
+ break;
17064
+ }
17065
+ current = current.nextBeat;
17066
+ }
17067
+ if (l1 === null) {
17068
+ // should not be possible
17069
+ throw new AlphaTabError(exports.AlphaTabErrorType.General, "Error on building lookup, unknown variant");
17070
+ }
17071
+ }
17072
+ // those scenarios should only happen if we insert before the
17073
+ // first item (e.g. for grace notes starting < 0)
17074
+ if (start < l1.start) {
17075
+ // Variant D
17076
+ // Variant E
17077
+ if (end == l1.start) {
17078
+ // using firstBeat.start here allows merge of D & E
17079
+ const n1 = new BeatTickLookup(start, l1.start);
17080
+ n1.highlightBeat(beat);
17081
+ this.insertBefore(this.firstBeat, n1);
17082
+ }
17083
+ // Variant F
17084
+ else if (end < l1.end) {
17085
+ const n1 = new BeatTickLookup(start, l1.start);
17086
+ n1.highlightBeat(beat);
17087
+ this.insertBefore(l1, n1);
17088
+ const n2 = new BeatTickLookup(l1.start, end);
17089
+ for (const b of l1.highlightedBeats) {
17090
+ n2.highlightBeat(b);
17091
+ }
17092
+ n2.highlightBeat(beat);
17093
+ this.insertBefore(l1, n2);
17094
+ l1.start = end;
17095
+ }
17096
+ // Variant G
17097
+ else if (end == l1.end) {
17098
+ const n1 = new BeatTickLookup(start, l1.start);
17099
+ n1.highlightBeat(beat);
17100
+ l1.highlightBeat(beat);
17101
+ this.insertBefore(l1, n1);
17102
+ }
17103
+ // Variant H
17104
+ else /* end > this.firstBeat.end */ {
17105
+ const n1 = new BeatTickLookup(start, l1.start);
17106
+ n1.highlightBeat(beat);
17107
+ l1.highlightBeat(beat);
17108
+ this.insertBefore(l1, n1);
17109
+ this.addBeat(beat, l1.end, end - l1.end);
17110
+ }
17111
+ }
17112
+ else if (start > l1.start) {
17113
+ // variant I
17114
+ if (end == l1.end) {
17115
+ const n1 = new BeatTickLookup(l1.start, start);
17116
+ for (const b of l1.highlightedBeats) {
17117
+ n1.highlightBeat(b);
17118
+ }
17119
+ l1.start = start;
17120
+ l1.highlightBeat(beat);
17121
+ this.insertBefore(l1, n1);
17122
+ }
17123
+ // Variant J
17124
+ else if (end < l1.end) {
17125
+ const n1 = new BeatTickLookup(l1.start, start);
17126
+ this.insertBefore(l1, n1);
17127
+ const n2 = new BeatTickLookup(start, end);
17128
+ this.insertBefore(l1, n2);
17129
+ for (const b of l1.highlightedBeats) {
17130
+ n1.highlightBeat(b);
17131
+ n2.highlightBeat(b);
17132
+ }
17133
+ n2.highlightBeat(beat);
17134
+ l1.start = end;
17135
+ }
17136
+ // Variant K
17137
+ else /* end > l1.end */ {
17138
+ const n1 = new BeatTickLookup(l1.start, start);
17139
+ for (const b of l1.highlightedBeats) {
17140
+ n1.highlightBeat(b);
17141
+ }
17142
+ l1.start = start;
17143
+ l1.highlightBeat(beat);
17144
+ this.insertBefore(l1, n1);
17145
+ this.addBeat(beat, l1.end, end - l1.end);
17146
+ }
17147
+ }
17148
+ else /* start == l1.start */ {
17149
+ // Variant L
17150
+ if (end === l1.end) {
17151
+ l1.highlightBeat(beat);
17152
+ }
17153
+ // Variant M
17154
+ else if (end < l1.end) {
17155
+ const n1 = new BeatTickLookup(l1.start, end);
17156
+ for (const b of l1.highlightedBeats) {
17157
+ n1.highlightBeat(b);
17158
+ }
17159
+ n1.highlightBeat(beat);
17160
+ l1.start = end;
17161
+ this.insertBefore(l1, n1);
17162
+ }
17163
+ // variant N
17164
+ else /* end > l1.end */ {
17165
+ l1.highlightBeat(beat);
17166
+ this.addBeat(beat, l1.end, end - l1.end);
17167
+ }
17168
+ }
17169
+ }
16891
17170
  }
16892
17171
  }
16893
17172
 
@@ -16896,37 +17175,60 @@
16896
17175
  * @see MidiTickLookup.FindBeat
16897
17176
  */
16898
17177
  class MidiTickLookupFindBeatResult {
16899
- constructor() {
17178
+ get start() {
17179
+ return this.masterBar.start + this.beatLookup.start;
17180
+ }
17181
+ get end() {
17182
+ return this.start + this.tickDuration;
17183
+ }
17184
+ constructor(masterBar) {
16900
17185
  /**
16901
- * Gets or sets the duration in milliseconds how long this beat is playing.
17186
+ * Gets or sets the beat that will be played next.
16902
17187
  */
16903
- this.duration = 0;
17188
+ this.nextBeat = null;
16904
17189
  /**
16905
- * Gets or sets the duration in midi ticks for how long this tick lookup is valid
16906
- * starting at the `currentBeatLookup.start`
17190
+ * Gets or sets the duration in midi ticks how long this lookup is valid.
16907
17191
  */
16908
17192
  this.tickDuration = 0;
16909
17193
  /**
16910
- * Gets or sets the beat lookup for the next beat.
17194
+ * Gets or sets the duration in milliseconds how long this lookup is valid.
16911
17195
  */
16912
- this.nextBeatLookup = null;
16913
- }
16914
- /**
16915
- * Gets or sets the beat that is currently played.
16916
- */
16917
- get currentBeat() {
16918
- return this.currentBeatLookup.beat;
16919
- }
16920
- /**
16921
- * Gets or sets the beat that will be played next.
16922
- */
16923
- get nextBeat() {
16924
- var _a, _b;
16925
- return (_b = (_a = this.nextBeatLookup) === null || _a === void 0 ? void 0 : _a.beat) !== null && _b !== void 0 ? _b : null;
17196
+ this.duration = 0;
17197
+ this.masterBar = masterBar;
16926
17198
  }
16927
17199
  }
16928
17200
  /**
16929
17201
  * This class holds all information about when {@link MasterBar}s and {@link Beat}s are played.
17202
+ *
17203
+ * On top level it is organized into {@link MasterBarTickLookup} objects indicating the
17204
+ * master bar start and end times. This information is used to highlight the currently played bars
17205
+ * and it gives access to the played beats in this masterbar and their times.
17206
+ *
17207
+ * The {@link BeatTickLookup} are then the slices into which the masterbar is separated by the voices and beats
17208
+ * of all tracks. An example how things are organized:
17209
+ *
17210
+ * Time (eighths): | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17211
+ *
17212
+ * Track 1: | B1 | B2 | B3 | B4 | B5 | B6 |
17213
+ * Track 2: | B7 | B7 | B9 | B10| B11| B12|
17214
+ * Track 3: | B13 |
17215
+ *
17216
+ * Lookup: | L1 | L2 | L3 | L4 | L5 | L6 | L7 | L8 |
17217
+ * Active Beats:
17218
+ * - L1 B1,B7,B13
17219
+ * - L2 B2,B7,B13
17220
+ * - L3 B3,B7,B13
17221
+ * - L4 B4,B7,B13
17222
+ * - L5 B5,B9,B13
17223
+ * - L6 B5,B10,B13
17224
+ * - L7 B6,B11,B13
17225
+ * - L8 B6,B12,B13
17226
+ *
17227
+ * Then during playback we build out of this list {@link MidiTickLookupFindBeatResult} objects which are sepcific
17228
+ * to the visible tracks displayed. This is required because if only Track 2 is displayed we cannot use the the
17229
+ * Lookup L1 alone to determine the start and end of the beat cursor. In this case we will derive a
17230
+ * MidiTickLookupFindBeatResult which holds for Time 01 the lookup L1 as start and L3 as end. This will be used
17231
+ * both for the cursor and beat highlighting.
16930
17232
  */
16931
17233
  class MidiTickLookup {
16932
17234
  constructor() {
@@ -16943,139 +17245,153 @@
16943
17245
  */
16944
17246
  this.masterBars = [];
16945
17247
  }
16946
- /**
16947
- * Performs the neccessary finalization steps after all information was written.
16948
- * @internal
16949
- */
16950
- finish() {
16951
- let previous = null;
16952
- let activeBeats = [];
16953
- for (let bar of this.masterBars) {
16954
- bar.finish();
16955
- if (previous) {
16956
- previous.nextMasterBar = bar;
16957
- }
16958
- for (const beat of bar.beats) {
16959
- // 1. calculate newly which beats are still active
16960
- const newActiveBeats = [];
16961
- // TODO: only create new list if current position changed
16962
- for (let activeBeat of activeBeats) {
16963
- if (activeBeat.end > beat.start) {
16964
- newActiveBeats.push(activeBeat);
16965
- // 2. remember for current beat which active beats to highlight
16966
- beat.highlightBeat(activeBeat.beat);
16967
- // 3. ensure that active beat highlights current beat if they match the range
16968
- if (beat.start <= activeBeat.start) {
16969
- activeBeat.highlightBeat(beat.beat);
16970
- }
16971
- }
16972
- }
16973
- newActiveBeats.push(beat);
16974
- activeBeats = newActiveBeats;
16975
- }
16976
- previous = bar;
16977
- }
16978
- }
16979
17248
  /**
16980
17249
  * Finds the currently played beat given a list of tracks and the current time.
16981
- * @param tracks The tracks in which to search the played beat for.
17250
+ * @param trackLookup The tracks indices in which to search the played beat for.
16982
17251
  * @param tick The current time in midi ticks.
16983
17252
  * @returns The information about the current beat or null if no beat could be found.
16984
17253
  */
16985
- findBeat(tracks, tick, currentBeatHint = null) {
16986
- const trackLookup = new Map();
16987
- for (const track of tracks) {
16988
- trackLookup.set(track.index, true);
16989
- }
17254
+ findBeat(trackLookup, tick, currentBeatHint = null) {
16990
17255
  let result = null;
16991
17256
  if (currentBeatHint) {
16992
17257
  result = this.findBeatFast(trackLookup, currentBeatHint, tick);
16993
17258
  }
16994
17259
  if (!result) {
16995
- result = this.findBeatSlow(trackLookup, tick);
17260
+ result = this.findBeatSlow(trackLookup, currentBeatHint, tick, false);
16996
17261
  }
16997
17262
  return result;
16998
17263
  }
16999
17264
  findBeatFast(trackLookup, currentBeatHint, tick) {
17000
- const end = currentBeatHint.currentBeatLookup.start + currentBeatHint.tickDuration;
17001
- if (tick >= currentBeatHint.currentBeatLookup.start && tick < end) {
17002
- // still same beat?
17265
+ // still within current lookup.
17266
+ if (tick >= currentBeatHint.start && tick < currentBeatHint.end) {
17003
17267
  return currentBeatHint;
17004
17268
  }
17005
- else if (currentBeatHint.nextBeatLookup &&
17006
- tick >= currentBeatHint.nextBeatLookup.start &&
17007
- tick < currentBeatHint.nextBeatLookup.end) {
17008
- // maybe next beat?
17009
- return this.createResult(currentBeatHint.nextBeatLookup, trackLookup);
17269
+ // already on the next beat?
17270
+ else if (currentBeatHint.nextBeat &&
17271
+ tick >= currentBeatHint.nextBeat.start &&
17272
+ tick < currentBeatHint.nextBeat.end) {
17273
+ const next = currentBeatHint.nextBeat;
17274
+ // fill next in chain
17275
+ this.fillNextBeat(next, trackLookup);
17276
+ return next;
17010
17277
  }
17011
17278
  // likely a loop or manual seek, need to fallback to slow path
17012
17279
  return null;
17013
17280
  }
17014
- findBeatSlow(trackLookup, tick) {
17281
+ fillNextBeat(current, trackLookup) {
17282
+ current.nextBeat = this.findBeatInMasterBar(current.masterBar, current.beatLookup.nextBeat, current.end, trackLookup, false, true);
17283
+ if (current.nextBeat == null) {
17284
+ current.nextBeat = this.findBeatSlow(trackLookup, current, current.end, true);
17285
+ }
17286
+ // if we have the next beat take the difference between the times as duration
17287
+ if (current.nextBeat) {
17288
+ current.tickDuration = current.nextBeat.start - current.start;
17289
+ current.duration = MidiUtils.ticksToMillis(current.tickDuration, current.masterBar.tempo);
17290
+ }
17291
+ }
17292
+ findBeatSlow(trackLookup, currentBeatHint, tick, isNextSearch) {
17015
17293
  // get all beats within the masterbar
17016
- const masterBar = this.findMasterBar(tick);
17294
+ let masterBar = null;
17295
+ if (currentBeatHint != null) {
17296
+ // same masterbar?
17297
+ if (currentBeatHint.masterBar.start <= tick &&
17298
+ currentBeatHint.masterBar.end > tick) {
17299
+ masterBar = currentBeatHint.masterBar;
17300
+ }
17301
+ // next masterbar
17302
+ else if (currentBeatHint.masterBar.nextMasterBar &&
17303
+ currentBeatHint.masterBar.nextMasterBar.start <= tick &&
17304
+ currentBeatHint.masterBar.nextMasterBar.end > tick) {
17305
+ masterBar = currentBeatHint.masterBar.nextMasterBar;
17306
+ }
17307
+ }
17308
+ // slowest lookup
17309
+ if (!masterBar) {
17310
+ masterBar = this.findMasterBar(tick);
17311
+ }
17312
+ // no match
17017
17313
  if (!masterBar) {
17018
17314
  return null;
17019
17315
  }
17020
- let beat = null;
17021
- let beats = masterBar.beats;
17022
- for (let b = 0; b < beats.length; b++) {
17023
- // is the current beat played on the given tick?
17024
- let currentBeat = beats[b];
17025
- // skip non relevant beats
17026
- if (!trackLookup.has(currentBeat.beat.voice.bar.staff.track.index)) {
17027
- continue;
17316
+ // scan through beats and find first one which has a beat visible
17317
+ while (masterBar) {
17318
+ if (masterBar.firstBeat) {
17319
+ let beat = this.findBeatInMasterBar(masterBar, masterBar.firstBeat, tick, trackLookup, true, isNextSearch);
17320
+ if (beat) {
17321
+ return beat;
17322
+ }
17028
17323
  }
17029
- if (currentBeat.start <= tick && tick < currentBeat.end) {
17030
- // take the latest played beat we can find. (most right)
17031
- if (!beat || beat.start < currentBeat.start) {
17032
- beat = beats[b];
17324
+ masterBar = masterBar.nextMasterBar;
17325
+ }
17326
+ return null;
17327
+ }
17328
+ /**
17329
+ * Finds the beat at a given tick position within the known master bar.
17330
+ * @param masterBar
17331
+ * @param currentStartLookup
17332
+ * @param tick
17333
+ * @param visibleTracks
17334
+ * @param fillNext
17335
+ * @returns
17336
+ */
17337
+ findBeatInMasterBar(masterBar, currentStartLookup, tick, visibleTracks, fillNext, isNextSeach) {
17338
+ var _a;
17339
+ if (!currentStartLookup) {
17340
+ return null;
17341
+ }
17342
+ let startBeatLookup = null;
17343
+ let startBeat = null;
17344
+ const relativeTick = tick - masterBar.start;
17345
+ while (currentStartLookup != null && startBeat == null) {
17346
+ if (currentStartLookup.start <= relativeTick && relativeTick < currentStartLookup.end) {
17347
+ startBeatLookup = currentStartLookup;
17348
+ startBeat = currentStartLookup.getVisibleBeatAtStart(visibleTracks);
17349
+ // found the matching beat lookup but none of the beats are visible
17350
+ // in this case scan further to the next lookup which has any visible beat
17351
+ if (!startBeat) {
17352
+ if (isNextSeach) {
17353
+ while (currentStartLookup != null) {
17354
+ startBeat = currentStartLookup.getVisibleBeatAtStart(visibleTracks);
17355
+ if (startBeat) {
17356
+ startBeatLookup = currentStartLookup;
17357
+ break;
17358
+ }
17359
+ currentStartLookup = currentStartLookup.nextBeat;
17360
+ }
17361
+ }
17362
+ else {
17363
+ while (currentStartLookup != null) {
17364
+ startBeat = currentStartLookup.getVisibleBeatAtStart(visibleTracks);
17365
+ if (startBeat) {
17366
+ startBeatLookup = currentStartLookup;
17367
+ break;
17368
+ }
17369
+ currentStartLookup = currentStartLookup.previousBeat;
17370
+ }
17371
+ }
17033
17372
  }
17034
17373
  }
17035
- else if (currentBeat.end > tick) {
17374
+ else if (currentStartLookup.end > relativeTick) {
17036
17375
  break;
17037
17376
  }
17377
+ currentStartLookup = (_a = currentStartLookup === null || currentStartLookup === void 0 ? void 0 : currentStartLookup.nextBeat) !== null && _a !== void 0 ? _a : null;
17038
17378
  }
17039
- if (!beat) {
17379
+ if (startBeat == null) {
17040
17380
  return null;
17041
17381
  }
17042
- return this.createResult(beat, trackLookup);
17043
- }
17044
- createResult(beat, trackLookup) {
17045
- // search for next relevant beat in masterbar
17046
- const nextBeat = this.findNextBeat(beat, trackLookup);
17047
- const result = new MidiTickLookupFindBeatResult();
17048
- result.currentBeatLookup = beat;
17049
- result.nextBeatLookup = nextBeat;
17050
- result.tickDuration = !nextBeat ? beat.end - beat.start : nextBeat.start - beat.start;
17051
- result.duration = MidiUtils.ticksToMillis(result.tickDuration, beat.masterBar.tempo);
17052
- result.beatsToHighlight = beat.beatsToHighlight;
17382
+ const result = this.createResult(masterBar, startBeatLookup, startBeat, fillNext, visibleTracks);
17053
17383
  return result;
17054
17384
  }
17055
- findNextBeat(beat, trackLookup) {
17056
- const masterBar = beat.masterBar;
17057
- let beats = masterBar.beats;
17058
- // search for next relevant beat in masterbar
17059
- let nextBeat = null;
17060
- for (let b = beat.index + 1; b < beats.length; b++) {
17061
- const currentBeat = beats[b];
17062
- if (currentBeat.start > beat.start && trackLookup.has(currentBeat.beat.voice.bar.staff.track.index)) {
17063
- nextBeat = currentBeat;
17064
- break;
17065
- }
17066
- }
17067
- // first relevant beat in next bar
17068
- if (!nextBeat && masterBar.nextMasterBar) {
17069
- beats = masterBar.nextMasterBar.beats;
17070
- for (let b = 0; b < beats.length; b++) {
17071
- const currentBeat = beats[b];
17072
- if (trackLookup.has(currentBeat.beat.voice.bar.staff.track.index)) {
17073
- nextBeat = currentBeat;
17074
- break;
17075
- }
17076
- }
17385
+ createResult(masterBar, beatLookup, beat, fillNext, visibleTracks) {
17386
+ const result = new MidiTickLookupFindBeatResult(masterBar);
17387
+ result.beat = beat;
17388
+ result.beatLookup = beatLookup;
17389
+ result.tickDuration = beatLookup.end - beatLookup.start;
17390
+ if (fillNext) {
17391
+ this.fillNextBeat(result, visibleTracks);
17077
17392
  }
17078
- return nextBeat;
17393
+ result.duration = MidiUtils.ticksToMillis(result.tickDuration, masterBar.tempo);
17394
+ return result;
17079
17395
  }
17080
17396
  findMasterBar(tick) {
17081
17397
  const bars = this.masterBars;
@@ -17139,18 +17455,17 @@
17139
17455
  */
17140
17456
  addMasterBar(masterBar) {
17141
17457
  this.masterBars.push(masterBar);
17458
+ if (this._currentMasterBar) {
17459
+ this._currentMasterBar.nextMasterBar = masterBar;
17460
+ }
17142
17461
  this._currentMasterBar = masterBar;
17143
17462
  if (!this.masterBarLookup.has(masterBar.masterBar.index)) {
17144
17463
  this.masterBarLookup.set(masterBar.masterBar.index, masterBar);
17145
17464
  }
17146
17465
  }
17147
- /**
17148
- * Adds the given {@link BeatTickLookup} to the current {@link MidiTickLookup}.
17149
- * @param beat The lookup to add.
17150
- */
17151
- addBeat(beat) {
17466
+ addBeat(beat, start, duration) {
17152
17467
  var _a;
17153
- (_a = this._currentMasterBar) === null || _a === void 0 ? void 0 : _a.addBeat(beat);
17468
+ (_a = this._currentMasterBar) === null || _a === void 0 ? void 0 : _a.addBeat(beat, start, duration);
17154
17469
  }
17155
17470
  }
17156
17471
 
@@ -19024,7 +19339,6 @@
19024
19339
  */
19025
19340
  constructor(score, settings, handler) {
19026
19341
  this._currentTempo = 0;
19027
- this._currentBarRepeatLookup = null;
19028
19342
  this._programsPerChannel = new Map();
19029
19343
  /**
19030
19344
  * Gets a lookup object which can be used to quickly find beats and bars
@@ -19080,7 +19394,6 @@
19080
19394
  for (const track of this._score.tracks) {
19081
19395
  this._handler.finishTrack(track.index, controller.currentTick);
19082
19396
  }
19083
- this.tickLookup.finish();
19084
19397
  Logger.debug('Midi', 'Midi generation done');
19085
19398
  }
19086
19399
  generateTrack(track) {
@@ -19154,7 +19467,6 @@
19154
19467
  }
19155
19468
  generateBar(bar, barStartTick) {
19156
19469
  let playbackBar = this.getPlaybackBar(bar);
19157
- this._currentBarRepeatLookup = null;
19158
19470
  for (const v of playbackBar.voices) {
19159
19471
  this.generateVoice(v, barStartTick, bar);
19160
19472
  }
@@ -19207,29 +19519,16 @@
19207
19519
  }
19208
19520
  }
19209
19521
  }
19210
- const beatLookup = new BeatTickLookup();
19211
- beatLookup.start = barStartTick + beatStart;
19212
19522
  const realTickOffset = !beat.nextBeat
19213
19523
  ? audioDuration
19214
19524
  : beat.nextBeat.absolutePlaybackStart - beat.absolutePlaybackStart;
19215
- beatLookup.end = barStartTick + beatStart;
19216
- beatLookup.highlightBeat(beat);
19217
- beatLookup.end += realTickOffset > audioDuration ? realTickOffset : audioDuration;
19218
19525
  // in case of normal playback register playback
19219
19526
  if (realBar === beat.voice.bar) {
19220
- beatLookup.beat = beat;
19221
- this.tickLookup.addBeat(beatLookup);
19527
+ this.tickLookup.addBeat(beat, beatStart, realTickOffset > audioDuration ? realTickOffset : audioDuration);
19222
19528
  }
19223
19529
  else {
19224
- beatLookup.isEmptyBar = true;
19225
- beatLookup.beat = realBar.voices[0].beats[0];
19226
- if (!this._currentBarRepeatLookup) {
19227
- this._currentBarRepeatLookup = beatLookup;
19228
- this.tickLookup.addBeat(this._currentBarRepeatLookup);
19229
- }
19230
- else {
19231
- this._currentBarRepeatLookup.end = beatLookup.end;
19232
- }
19530
+ // in case of simile marks where we repeat we also register
19531
+ this.tickLookup.addBeat(beat, 0, realTickOffset > audioDuration ? realTickOffset : audioDuration);
19233
19532
  }
19234
19533
  const track = beat.voice.bar.staff.track;
19235
19534
  for (const automation of beat.automations) {
@@ -25008,6 +25307,7 @@
25008
25307
  constructor(uiFacade, settings) {
25009
25308
  this._startTime = 0;
25010
25309
  this._trackIndexes = null;
25310
+ this._trackIndexLookup = null;
25011
25311
  this._isDestroyed = false;
25012
25312
  /**
25013
25313
  * Gets the score holding all information about the song being rendered.
@@ -25231,6 +25531,7 @@
25231
25531
  for (let track of tracks) {
25232
25532
  this._trackIndexes.push(track.index);
25233
25533
  }
25534
+ this._trackIndexLookup = new Set(this._trackIndexes);
25234
25535
  this.onScoreLoaded(score);
25235
25536
  this.loadMidiForScore();
25236
25537
  this.render();
@@ -25241,6 +25542,7 @@
25241
25542
  for (let track of tracks) {
25242
25543
  this._trackIndexes.push(track.index);
25243
25544
  }
25545
+ this._trackIndexLookup = new Set(this._trackIndexes);
25244
25546
  this.render();
25245
25547
  }
25246
25548
  }
@@ -25687,7 +25989,7 @@
25687
25989
  let currentBeat = this._currentBeat;
25688
25990
  let tickCache = this._tickCache;
25689
25991
  if (currentBeat && tickCache) {
25690
- this.player.tickPosition = tickCache.getBeatStart(currentBeat.currentBeat);
25992
+ this.player.tickPosition = tickCache.getBeatStart(currentBeat.beat);
25691
25993
  }
25692
25994
  }
25693
25995
  });
@@ -25702,8 +26004,8 @@
25702
26004
  cursorUpdateTick(tick, stop, shouldScroll = false) {
25703
26005
  let cache = this._tickCache;
25704
26006
  if (cache) {
25705
- let tracks = this.tracks;
25706
- if (tracks.length > 0) {
26007
+ let tracks = this._trackIndexLookup;
26008
+ if (tracks != null && tracks.size > 0) {
25707
26009
  let beat = cache.findBeat(tracks, tick, this._currentBeat);
25708
26010
  if (beat) {
25709
26011
  this.cursorUpdateBeat(beat, stop, shouldScroll);
@@ -25715,10 +26017,11 @@
25715
26017
  * updates the cursors to highlight the specified beat
25716
26018
  */
25717
26019
  cursorUpdateBeat(lookupResult, stop, shouldScroll, forceUpdate = false) {
25718
- const beat = lookupResult.currentBeat;
25719
- const nextBeat = lookupResult.nextBeat;
26020
+ var _a, _b;
26021
+ const beat = lookupResult.beat;
26022
+ const nextBeat = (_b = (_a = lookupResult.nextBeat) === null || _a === void 0 ? void 0 : _a.beat) !== null && _b !== void 0 ? _b : null;
25720
26023
  const duration = lookupResult.duration;
25721
- const beatsToHighlight = lookupResult.beatsToHighlight;
26024
+ const beatsToHighlight = lookupResult.beatLookup.highlightedBeats;
25722
26025
  if (!beat) {
25723
26026
  return;
25724
26027
  }
@@ -25730,7 +26033,7 @@
25730
26033
  let previousCache = this._previousCursorCache;
25731
26034
  let previousState = this._previousStateForCursor;
25732
26035
  if (!forceUpdate &&
25733
- beat === (previousBeat === null || previousBeat === void 0 ? void 0 : previousBeat.currentBeat) &&
26036
+ beat === (previousBeat === null || previousBeat === void 0 ? void 0 : previousBeat.beat) &&
25734
26037
  cache === previousCache &&
25735
26038
  previousState === this._playerState) {
25736
26039
  return;
@@ -25843,14 +26146,11 @@
25843
26146
  if (nextBeat) {
25844
26147
  // if we are moving within the same bar or to the next bar
25845
26148
  // transition to the next beat, otherwise transition to the end of the bar.
25846
- if ((nextBeat.voice.bar.index === beat.voice.bar.index && nextBeat.index > beat.index) ||
25847
- nextBeat.voice.bar.index === beat.voice.bar.index + 1) {
25848
- let nextBeatBoundings = cache.findBeat(nextBeat);
25849
- if (nextBeatBoundings &&
25850
- nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds ===
25851
- barBoundings.staveGroupBounds) {
25852
- nextBeatX = nextBeatBoundings.visualBounds.x;
25853
- }
26149
+ let nextBeatBoundings = cache.findBeat(nextBeat);
26150
+ if (nextBeatBoundings &&
26151
+ nextBeatBoundings.barBounds.masterBarBounds.staveGroupBounds ===
26152
+ barBoundings.staveGroupBounds) {
26153
+ nextBeatX = nextBeatBoundings.visualBounds.x;
25854
26154
  }
25855
26155
  }
25856
26156
  // we need to put the transition to an own animation frame
@@ -25991,11 +26291,11 @@
25991
26291
  return;
25992
26292
  }
25993
26293
  if (range) {
25994
- const startBeat = this._tickCache.findBeat(this.tracks, range.startTick);
25995
- const endBeat = this._tickCache.findBeat(this.tracks, range.endTick);
26294
+ const startBeat = this._tickCache.findBeat(this._trackIndexLookup, range.startTick);
26295
+ const endBeat = this._tickCache.findBeat(this._trackIndexLookup, range.endTick);
25996
26296
  if (startBeat && endBeat) {
25997
- const selectionStart = new SelectionInfo(startBeat.currentBeat);
25998
- const selectionEnd = new SelectionInfo(endBeat.currentBeat);
26297
+ const selectionStart = new SelectionInfo(startBeat.beat);
26298
+ const selectionEnd = new SelectionInfo(endBeat.beat);
25999
26299
  this.cursorSelectRange(selectionStart, selectionEnd);
26000
26300
  }
26001
26301
  }
@@ -42499,8 +42799,8 @@
42499
42799
  // </auto-generated>
42500
42800
  class VersionInfo {
42501
42801
  }
42502
- VersionInfo.version = '1.3.0-alpha.870';
42503
- VersionInfo.date = '2023-12-26T01:21:15.490Z';
42802
+ VersionInfo.version = '1.3.0-alpha.873';
42803
+ VersionInfo.date = '2023-12-29T01:13:10.298Z';
42504
42804
 
42505
42805
  var index$5 = /*#__PURE__*/Object.freeze({
42506
42806
  __proto__: null,