@coderline/alphatab 1.6.0-alpha.1428 → 1.6.0-alpha.1432

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.6.0-alpha.1428 (develop, build 1428)
2
+ * alphaTab v1.6.0-alpha.1432 (develop, build 1432)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -1006,6 +1006,26 @@ class Bar {
1006
1006
  get isEmpty() {
1007
1007
  return this._isEmpty;
1008
1008
  }
1009
+ /**
1010
+ * Whether this bar has any changes applied which are not related to the voices in it.
1011
+ * (e.g. new key signatures)
1012
+ */
1013
+ get hasChanges() {
1014
+ if (this.index === 0) {
1015
+ return true;
1016
+ }
1017
+ const hasChangesToPrevious = this.keySignature !== this.previousBar.keySignature ||
1018
+ this.keySignatureType !== this.previousBar.keySignatureType ||
1019
+ this.clef !== this.previousBar.clef ||
1020
+ this.clefOttava !== this.previousBar.clefOttava;
1021
+ if (hasChangesToPrevious) {
1022
+ return true;
1023
+ }
1024
+ return (this.simileMark !== SimileMark.None ||
1025
+ this.sustainPedals.length > 0 ||
1026
+ this.barLineLeft !== BarLineStyle.Automatic ||
1027
+ this.barLineRight !== BarLineStyle.Automatic);
1028
+ }
1009
1029
  /**
1010
1030
  * Whether this bar is empty or has only rests.
1011
1031
  */
@@ -1411,12 +1431,6 @@ class SyncPointData {
1411
1431
  * Indicates for which repeat occurence this sync point is valid (e.g. 0 on the first time played, 1 on the second time played)
1412
1432
  */
1413
1433
  this.barOccurence = 0;
1414
- /**
1415
- * The modified tempo at which the cursor should move (aka. the tempo played within the external audio track).
1416
- * This information is used together with normal tempo changes to calculate how much faster/slower the
1417
- * cursor playback is performed to align with the audio track.
1418
- */
1419
- this.modifiedTempo = 0;
1420
1434
  /**
1421
1435
  * The audio offset marking the position within the audio track in milliseconds.
1422
1436
  * This information is used to regularly sync (or on seeking) to match a given external audio time axis with the internal time axis.
@@ -2625,6 +2639,33 @@ class MasterBar {
2625
2639
  */
2626
2640
  this.directions = null;
2627
2641
  }
2642
+ /**
2643
+ * Whether the masterbar is has any changes applied to it (e.g. tempo changes, time signature changes etc)
2644
+ * The first bar is always considered changed due to initial setup of values. It does not consider
2645
+ * elements like whether the tempo really changes to the previous bar.
2646
+ */
2647
+ get hasChanges() {
2648
+ if (this.index === 0) {
2649
+ return false;
2650
+ }
2651
+ const hasChangesToPrevious = this.timeSignatureCommon !== this.previousMasterBar.timeSignatureCommon ||
2652
+ this.timeSignatureNumerator !== this.previousMasterBar.timeSignatureNumerator ||
2653
+ this.timeSignatureDenominator !== this.previousMasterBar.timeSignatureDenominator ||
2654
+ this.tripletFeel !== this.previousMasterBar.tripletFeel;
2655
+ if (hasChangesToPrevious) {
2656
+ return true;
2657
+ }
2658
+ return (this.alternateEndings !== 0 ||
2659
+ this.isRepeatStart ||
2660
+ this.isRepeatEnd ||
2661
+ this.isFreeTime ||
2662
+ this.isSectionStart ||
2663
+ this.tempoAutomations.length > 0 ||
2664
+ this.syncPoints && this.syncPoints.length > 0 ||
2665
+ (this.fermata !== null && this.fermata.size > 0) ||
2666
+ (this.directions !== null && this.directions.size > 0) ||
2667
+ this.isAnacrusis);
2668
+ }
2628
2669
  /**
2629
2670
  * The key signature used on all bars.
2630
2671
  * @deprecated Use key signatures on bar level
@@ -3566,6 +3607,43 @@ class ModelUtils {
3566
3607
  }
3567
3608
  }
3568
3609
  }
3610
+ /**
3611
+ * Trims any empty bars at the end of the song.
3612
+ * @param score
3613
+ */
3614
+ static trimEmptyBarsAtEnd(score) {
3615
+ while (score.masterBars.length > 1) {
3616
+ const barIndex = score.masterBars.length - 1;
3617
+ const masterBar = score.masterBars[barIndex];
3618
+ if (masterBar.hasChanges) {
3619
+ return;
3620
+ }
3621
+ for (const track of score.tracks) {
3622
+ for (const staff of track.staves) {
3623
+ if (barIndex < staff.bars.length) {
3624
+ const bar = staff.bars[barIndex];
3625
+ if (!bar.isEmpty || bar.hasChanges) {
3626
+ // found a non-empty bar, stop whole cleanup
3627
+ return;
3628
+ }
3629
+ }
3630
+ }
3631
+ }
3632
+ // if we reach here, all found bars are empty, remove the bar
3633
+ for (const track of score.tracks) {
3634
+ for (const staff of track.staves) {
3635
+ if (barIndex < staff.bars.length) {
3636
+ const bar = staff.bars[barIndex];
3637
+ staff.bars.pop();
3638
+ // unlink
3639
+ bar.previousBar.nextBar = null;
3640
+ }
3641
+ }
3642
+ }
3643
+ score.masterBars.pop();
3644
+ masterBar.previousMasterBar.nextMasterBar = null;
3645
+ }
3646
+ }
3569
3647
  }
3570
3648
  ModelUtils.TuningLetters = new Set([
3571
3649
  0x43 /* C */, 0x44 /* D */, 0x45 /* E */, 0x46 /* F */, 0x47 /* G */, 0x41 /* A */, 0x42 /* B */, 0x63 /* c */,
@@ -5843,7 +5921,6 @@ class SyncPointDataCloner {
5843
5921
  static clone(original) {
5844
5922
  const clone = new SyncPointData();
5845
5923
  clone.barOccurence = original.barOccurence;
5846
- clone.modifiedTempo = original.modifiedTempo;
5847
5924
  clone.millisecondOffset = original.millisecondOffset;
5848
5925
  return clone;
5849
5926
  }
@@ -7313,7 +7390,6 @@ class Score {
7313
7390
  automation.ratioPosition = Math.min(1, Math.max(0, syncPoint.barPosition));
7314
7391
  automation.type = AutomationType.SyncPoint;
7315
7392
  automation.syncPointValue = new SyncPointData();
7316
- automation.syncPointValue.modifiedTempo = syncPoint.modifiedTempo;
7317
7393
  automation.syncPointValue.millisecondOffset = syncPoint.millisecondOffset;
7318
7394
  automation.syncPointValue.barOccurence = syncPoint.barOccurence;
7319
7395
  if (syncPoint.barIndex < this.masterBars.length) {
@@ -7346,8 +7422,7 @@ class Score {
7346
7422
  barIndex: masterBar.index,
7347
7423
  barOccurence: syncPoint.syncPointValue.barOccurence,
7348
7424
  barPosition: syncPoint.ratioPosition,
7349
- millisecondOffset: syncPoint.syncPointValue.millisecondOffset,
7350
- modifiedTempo: syncPoint.syncPointValue.modifiedTempo
7425
+ millisecondOffset: syncPoint.syncPointValue.millisecondOffset
7351
7426
  });
7352
7427
  }
7353
7428
  }
@@ -8686,6 +8761,7 @@ class AlphaTexImporter extends ScoreImporter {
8686
8761
  this._slurs = new Map();
8687
8762
  this._articulationValueToIndex = new Map();
8688
8763
  this._accidentalMode = AlphaTexAccidentalMode.Explicit;
8764
+ this._syncPoints = [];
8689
8765
  this.logErrors = false;
8690
8766
  }
8691
8767
  get name() {
@@ -8726,10 +8802,16 @@ class AlphaTexImporter extends ScoreImporter {
8726
8802
  if (!anyMetaRead && !anyBarsRead) {
8727
8803
  throw new UnsupportedFormatError('No alphaTex data found');
8728
8804
  }
8805
+ if (this._sy === AlphaTexSymbols.Dot) {
8806
+ this._sy = this.newSy();
8807
+ this.syncPoints();
8808
+ }
8729
8809
  }
8730
8810
  ModelUtils.consolidate(this._score);
8731
8811
  this._score.finish(this.settings);
8812
+ ModelUtils.trimEmptyBarsAtEnd(this._score);
8732
8813
  this._score.rebuildRepeatGroups();
8814
+ this._score.applyFlatSyncPoints(this._syncPoints);
8733
8815
  for (const [track, lyrics] of this._lyrics) {
8734
8816
  this._score.tracks[track].applyLyrics(lyrics);
8735
8817
  }
@@ -8746,6 +8828,47 @@ class AlphaTexImporter extends ScoreImporter {
8746
8828
  throw e;
8747
8829
  }
8748
8830
  }
8831
+ syncPoints() {
8832
+ while (this._sy !== AlphaTexSymbols.Eof) {
8833
+ this.syncPoint();
8834
+ }
8835
+ }
8836
+ syncPoint() {
8837
+ // \sync BarIndex Occurence MillisecondOffset
8838
+ // \sync BarIndex Occurence MillisecondOffset RatioPosition
8839
+ if (this._sy !== AlphaTexSymbols.MetaCommand || this._syData !== 'sync') {
8840
+ this.error('syncPoint', AlphaTexSymbols.MetaCommand, true);
8841
+ }
8842
+ this._sy = this.newSy();
8843
+ if (this._sy !== AlphaTexSymbols.Number) {
8844
+ this.error('syncPointBarIndex', AlphaTexSymbols.Number, true);
8845
+ }
8846
+ const barIndex = this._syData;
8847
+ this._sy = this.newSy();
8848
+ if (this._sy !== AlphaTexSymbols.Number) {
8849
+ this.error('syncPointBarOccurence', AlphaTexSymbols.Number, true);
8850
+ }
8851
+ const barOccurence = this._syData;
8852
+ this._sy = this.newSy();
8853
+ if (this._sy !== AlphaTexSymbols.Number) {
8854
+ this.error('syncPointBarMillis', AlphaTexSymbols.Number, true);
8855
+ }
8856
+ const millisecondOffset = this._syData;
8857
+ this._allowFloat = true;
8858
+ this._sy = this.newSy();
8859
+ this._allowFloat = false;
8860
+ let barPosition = 0;
8861
+ if (this._sy === AlphaTexSymbols.Number) {
8862
+ barPosition = this._syData;
8863
+ this._sy = this.newSy();
8864
+ }
8865
+ this._syncPoints.push({
8866
+ barIndex,
8867
+ barOccurence,
8868
+ barPosition,
8869
+ millisecondOffset
8870
+ });
8871
+ }
8749
8872
  error(nonterm, expected, wrongSymbol = true) {
8750
8873
  let receivedSymbol;
8751
8874
  let showSyData = false;
@@ -14714,9 +14837,6 @@ class GpifParser {
14714
14837
  case 'BarOccurrence':
14715
14838
  syncPointValue.barOccurence = GpifParser.parseIntSafe(vc.innerText, 0);
14716
14839
  break;
14717
- case 'ModifiedTempo':
14718
- syncPointValue.modifiedTempo = GpifParser.parseFloatSafe(vc.innerText, 0);
14719
- break;
14720
14840
  case 'FrameOffset':
14721
14841
  const frameOffset = GpifParser.parseFloatSafe(vc.innerText, 0);
14722
14842
  syncPointValue.millisecondOffset = (frameOffset / GpifParser.SampleRate) * 1000;
@@ -22339,6 +22459,61 @@ class SynthEvent {
22339
22459
  }
22340
22460
  }
22341
22461
 
22462
+ /**
22463
+ * Rerpresents a point to sync the alphaTab time axis with an external backing track.
22464
+ */
22465
+ class BackingTrackSyncPoint {
22466
+ constructor() {
22467
+ /**
22468
+ * The index of the masterbar to which this sync point belongs to.
22469
+ * @remarks
22470
+ * This property is purely informative for external use like in editors.
22471
+ * It has no impact to the synchronization itself.
22472
+ */
22473
+ this.masterBarIndex = 0;
22474
+ /**
22475
+ * The occurence of the masterbar to which this sync point belongs to. The occurence
22476
+ * is 0-based and increases with every repeated play of a masterbar (e.g. on repeats or jumps).
22477
+ * @remarks
22478
+ * This property is purely informative for external use like in editors.
22479
+ * It has no impact to the synchronization itself.
22480
+ */
22481
+ this.masterBarOccurence = 0;
22482
+ /**
22483
+ * The BPM the synthesizer has at the exact tick position of this sync point.
22484
+ */
22485
+ this.synthBpm = 0;
22486
+ /**
22487
+ * The millisecond time position of the synthesizer when this sync point is reached.
22488
+ */
22489
+ this.synthTime = 0;
22490
+ /**
22491
+ * The midi tick position of the synthesizer when this sync point is reached.
22492
+ */
22493
+ this.synthTick = 0;
22494
+ /**
22495
+ * The millisecond time in the external media marking the synchronization point.
22496
+ */
22497
+ this.syncTime = 0;
22498
+ /**
22499
+ * The BPM the song will have virtually after this sync point to align the external media time axis
22500
+ * with the one from the synthesizer.
22501
+ */
22502
+ this.syncBpm = 0;
22503
+ }
22504
+ /**
22505
+ * Updates the synchronization BPM that will apply after this sync point.
22506
+ * @param nextSyncPointSynthTime The synthesizer time of the next sync point after this one.
22507
+ * @param nextSyncPointSyncTime The synchronization time of the next sync point after this one.
22508
+ */
22509
+ updateSyncBpm(nextSyncPointSynthTime, nextSyncPointSyncTime) {
22510
+ const synthDuration = nextSyncPointSynthTime - this.synthTime;
22511
+ const syncedDuration = nextSyncPointSyncTime - this.syncTime;
22512
+ const modifiedTempo = (synthDuration / syncedDuration) * this.synthBpm;
22513
+ this.syncBpm = modifiedTempo;
22514
+ }
22515
+ }
22516
+
22342
22517
  class MidiFileSequencerTempoChange {
22343
22518
  constructor(bpm, ticks, time) {
22344
22519
  this.bpm = bpm;
@@ -22346,14 +22521,6 @@ class MidiFileSequencerTempoChange {
22346
22521
  this.time = time;
22347
22522
  }
22348
22523
  }
22349
- class BackingTrackSyncPointWithTime {
22350
- constructor(tick, time, modifiedTempo, millisecondOffset) {
22351
- this.alphaTabTick = tick;
22352
- this.alphaTabTime = time;
22353
- this.modifiedTempo = modifiedTempo;
22354
- this.millisecondOffset = millisecondOffset;
22355
- }
22356
- }
22357
22524
  class MidiSequencerState {
22358
22525
  constructor() {
22359
22526
  this.tempoChanges = [];
@@ -22457,7 +22624,7 @@ class MidiFileSequencer {
22457
22624
  this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
22458
22625
  this._mainState.modifiedTempo =
22459
22626
  this._mainState.syncPoints.length > 0
22460
- ? this._mainState.syncPoints[0].modifiedTempo
22627
+ ? this._mainState.syncPoints[0].syncBpm
22461
22628
  : this._mainState.currentTempo;
22462
22629
  if (this.isPlayingMain) {
22463
22630
  const metronomeVolume = this._synthesizer.metronomeVolume;
@@ -22628,7 +22795,7 @@ class MidiFileSequencer {
22628
22795
  }
22629
22796
  mainUpdateSyncPoints(syncPoints) {
22630
22797
  const state = this._mainState;
22631
- syncPoints.sort((a, b) => a.tick - b.tick); // just in case
22798
+ syncPoints.sort((a, b) => a.synthTick - b.synthTick); // just in case
22632
22799
  state.syncPoints = [];
22633
22800
  if (syncPoints.length >= 0) {
22634
22801
  let bpm = 120;
@@ -22638,6 +22805,8 @@ class MidiFileSequencer {
22638
22805
  for (let i = 0; i < syncPoints.length; i++) {
22639
22806
  const p = syncPoints[i];
22640
22807
  let deltaTick = 0;
22808
+ // TODO: merge interpolation into MidiFileGenerator where we already play through
22809
+ // the time axis.
22641
22810
  // remember state from previous sync point (or start). to handle linear interpolation
22642
22811
  let previousModifiedTempo;
22643
22812
  let previousMillisecondOffset;
@@ -22649,9 +22818,9 @@ class MidiFileSequencer {
22649
22818
  }
22650
22819
  else {
22651
22820
  const previousSyncPoint = syncPoints[i - 1];
22652
- previousModifiedTempo = previousSyncPoint.data.modifiedTempo;
22653
- previousMillisecondOffset = previousSyncPoint.data.millisecondOffset;
22654
- previousTick = previousSyncPoint.tick;
22821
+ previousModifiedTempo = previousSyncPoint.syncBpm;
22822
+ previousMillisecondOffset = previousSyncPoint.syncTime;
22823
+ previousTick = previousSyncPoint.synthTick;
22655
22824
  }
22656
22825
  // process time until sync point
22657
22826
  // here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
@@ -22659,27 +22828,31 @@ class MidiFileSequencer {
22659
22828
  // otherwise the linear interpolation later in the lookup will fail.
22660
22829
  // goal is to have always a linear increase between two points, no matter if the time axis is sliced by tempo changes or sync points
22661
22830
  while (tempoChangeIndex < state.tempoChanges.length &&
22662
- state.tempoChanges[tempoChangeIndex].ticks <= p.tick) {
22831
+ state.tempoChanges[tempoChangeIndex].ticks <= p.synthTick) {
22663
22832
  deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
22664
22833
  if (deltaTick > 0) {
22665
22834
  absTick += deltaTick;
22666
22835
  absTime += deltaTick * (60000.0 / (bpm * state.division));
22667
- const millisPerTick = (p.data.millisecondOffset - previousMillisecondOffset) / (p.tick - previousTick);
22836
+ const millisPerTick = (p.syncTime - previousMillisecondOffset) / (p.synthTick - previousTick);
22668
22837
  const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
22669
- state.syncPoints.push(new BackingTrackSyncPointWithTime(absTick, absTime, previousModifiedTempo, interpolatedMillisecondOffset));
22838
+ const syncPoint = new BackingTrackSyncPoint();
22839
+ syncPoint.synthTick = absTick;
22840
+ syncPoint.synthBpm = bpm;
22841
+ syncPoint.synthTime = absTime;
22842
+ syncPoint.syncTime = interpolatedMillisecondOffset;
22843
+ syncPoint.syncBpm = previousModifiedTempo;
22670
22844
  }
22671
22845
  bpm = state.tempoChanges[tempoChangeIndex].bpm;
22672
22846
  tempoChangeIndex++;
22673
22847
  }
22674
- deltaTick = p.tick - absTick;
22848
+ deltaTick = p.synthTick - absTick;
22675
22849
  absTick += deltaTick;
22676
22850
  absTime += deltaTick * (60000.0 / (bpm * state.division));
22677
- state.syncPoints.push(new BackingTrackSyncPointWithTime(p.tick, absTime, p.data.modifiedTempo, p.data.millisecondOffset));
22851
+ state.syncPoints.push(p);
22678
22852
  }
22679
22853
  }
22680
22854
  state.syncPointIndex = 0;
22681
- state.modifiedTempo =
22682
- state.syncPoints.length > 0 ? state.syncPoints[0].modifiedTempo : state.currentTempo;
22855
+ state.modifiedTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
22683
22856
  }
22684
22857
  currentTimePositionToTickPosition(timePosition) {
22685
22858
  const state = this._currentState;
@@ -22712,16 +22885,15 @@ class MidiFileSequencer {
22712
22885
  const syncPoints = state.syncPoints;
22713
22886
  if (syncPoints.length > 0) {
22714
22887
  let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
22715
- if (timePosition < syncPoints[syncPointIndex].millisecondOffset) {
22888
+ if (timePosition < syncPoints[syncPointIndex].syncTime) {
22716
22889
  syncPointIndex = 0;
22717
22890
  }
22718
- while (syncPointIndex + 1 < syncPoints.length &&
22719
- syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
22891
+ while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].syncTime <= timePosition) {
22720
22892
  syncPointIndex++;
22721
22893
  }
22722
22894
  if (syncPointIndex !== state.syncPointIndex) {
22723
22895
  state.syncPointIndex = syncPointIndex;
22724
- state.modifiedTempo = syncPoints[syncPointIndex].modifiedTempo;
22896
+ state.modifiedTempo = syncPoints[syncPointIndex].syncBpm;
22725
22897
  }
22726
22898
  }
22727
22899
  else {
@@ -22737,18 +22909,18 @@ class MidiFileSequencer {
22737
22909
  this.updateSyncPoints(this._mainState, timePosition);
22738
22910
  const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22739
22911
  const currentSyncPoint = syncPoints[syncPointIndex];
22740
- const timeDiff = timePosition - currentSyncPoint.millisecondOffset;
22912
+ const timeDiff = timePosition - currentSyncPoint.syncTime;
22741
22913
  let alphaTabTimeDiff;
22742
22914
  if (syncPointIndex + 1 < syncPoints.length) {
22743
22915
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22744
- const relativeTimeDiff = timeDiff / (nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset);
22745
- alphaTabTimeDiff = (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22916
+ const relativeTimeDiff = timeDiff / (nextSyncPoint.syncTime - currentSyncPoint.syncTime);
22917
+ alphaTabTimeDiff = (nextSyncPoint.synthTime - currentSyncPoint.synthTime) * relativeTimeDiff;
22746
22918
  }
22747
22919
  else {
22748
- const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.millisecondOffset);
22749
- alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22920
+ const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.syncTime);
22921
+ alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.synthTime) * relativeTimeDiff;
22750
22922
  }
22751
- return (currentSyncPoint.alphaTabTime + alphaTabTimeDiff) / this.playbackSpeed;
22923
+ return (currentSyncPoint.synthTime + alphaTabTimeDiff) / this.playbackSpeed;
22752
22924
  }
22753
22925
  mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
22754
22926
  const mainState = this._mainState;
@@ -22758,27 +22930,27 @@ class MidiFileSequencer {
22758
22930
  }
22759
22931
  timePosition *= this.playbackSpeed;
22760
22932
  let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22761
- if (timePosition < syncPoints[syncPointIndex].alphaTabTime) {
22933
+ if (timePosition < syncPoints[syncPointIndex].synthTime) {
22762
22934
  syncPointIndex = 0;
22763
22935
  }
22764
- while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].alphaTabTime <= timePosition) {
22936
+ while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].synthTime <= timePosition) {
22765
22937
  syncPointIndex++;
22766
22938
  }
22767
22939
  // NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
22768
22940
  // we ensure that we have a linear increase between two points
22769
22941
  const currentSyncPoint = syncPoints[syncPointIndex];
22770
- const alphaTabTimeDiff = timePosition - currentSyncPoint.alphaTabTime;
22942
+ const alphaTabTimeDiff = timePosition - currentSyncPoint.synthTime;
22771
22943
  let backingTrackPos;
22772
22944
  if (syncPointIndex + 1 < syncPoints.length) {
22773
22945
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22774
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime);
22775
- const backingTrackDiff = nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset;
22776
- backingTrackPos = currentSyncPoint.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22946
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.synthTime - currentSyncPoint.synthTime);
22947
+ const backingTrackDiff = nextSyncPoint.syncTime - currentSyncPoint.syncTime;
22948
+ backingTrackPos = currentSyncPoint.syncTime + backingTrackDiff * relativeAlphaTabTimeDiff;
22777
22949
  }
22778
22950
  else {
22779
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.alphaTabTime);
22780
- const frameDiff = backingTrackLength - currentSyncPoint.millisecondOffset;
22781
- backingTrackPos = currentSyncPoint.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22951
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.synthTime);
22952
+ const frameDiff = backingTrackLength - currentSyncPoint.syncTime;
22953
+ backingTrackPos = currentSyncPoint.syncTime + frameDiff * relativeAlphaTabTimeDiff;
22782
22954
  }
22783
22955
  return backingTrackPos;
22784
22956
  }
@@ -31050,7 +31222,6 @@ class SyncPointDataSerializer {
31050
31222
  }
31051
31223
  const o = new Map();
31052
31224
  o.set("baroccurence", obj.barOccurence);
31053
- o.set("modifiedtempo", obj.modifiedTempo);
31054
31225
  o.set("millisecondoffset", obj.millisecondOffset);
31055
31226
  return o;
31056
31227
  }
@@ -31059,9 +31230,6 @@ class SyncPointDataSerializer {
31059
31230
  case "baroccurence":
31060
31231
  obj.barOccurence = v;
31061
31232
  return true;
31062
- case "modifiedtempo":
31063
- obj.modifiedTempo = v;
31064
- return true;
31065
31233
  case "millisecondoffset":
31066
31234
  obj.millisecondOffset = v;
31067
31235
  return true;
@@ -33066,7 +33234,7 @@ class AlphaSynthWorkerSynthOutput {
33066
33234
  addSamples(samples) {
33067
33235
  this._worker.postMessage({
33068
33236
  cmd: 'alphaSynth.output.addSamples',
33069
- samples: samples
33237
+ samples: Environment.prepareForPostMessage(samples)
33070
33238
  });
33071
33239
  }
33072
33240
  play() {
@@ -33262,7 +33430,7 @@ class AlphaSynthWebWorker {
33262
33430
  onSoundFontLoadFailed(e) {
33263
33431
  this._main.postMessage({
33264
33432
  cmd: 'alphaSynth.soundFontLoadFailed',
33265
- error: this.serializeException(e)
33433
+ error: this.serializeException(Environment.prepareForPostMessage(e))
33266
33434
  });
33267
33435
  }
33268
33436
  serializeException(e) {
@@ -33293,7 +33461,7 @@ class AlphaSynthWebWorker {
33293
33461
  onMidiLoadFailed(e) {
33294
33462
  this._main.postMessage({
33295
33463
  cmd: 'alphaSynth.midiLoaded',
33296
- error: this.serializeException(e)
33464
+ error: this.serializeException(Environment.prepareForPostMessage(e))
33297
33465
  });
33298
33466
  }
33299
33467
  onReadyForPlayback() {
@@ -35824,17 +35992,6 @@ class MidiTickLookup {
35824
35992
  }
35825
35993
  }
35826
35994
 
35827
- /**
35828
- * Rerpresents a point to sync the alphaTab time axis with an external backing track.
35829
- */
35830
- class BackingTrackSyncPoint {
35831
- constructor(tick, data) {
35832
- this.tick = 0;
35833
- this.tick = tick;
35834
- this.data = data;
35835
- }
35836
- }
35837
-
35838
35995
  class MidiNoteDuration {
35839
35996
  constructor() {
35840
35997
  this.noteOnly = 0;
@@ -35855,6 +36012,14 @@ class RasgueadoInfo {
35855
36012
  this.brushInfos = [];
35856
36013
  }
35857
36014
  }
36015
+ class PlayThroughContext {
36016
+ constructor() {
36017
+ this.synthTick = 0;
36018
+ this.synthTime = 0;
36019
+ this.currentTempo = 0;
36020
+ this.automationToSyncPoint = new Map();
36021
+ }
36022
+ }
35858
36023
  /**
35859
36024
  * This generator creates a midi file using a score.
35860
36025
  */
@@ -35981,9 +36146,22 @@ class MidiFileGenerator {
35981
36146
  });
35982
36147
  return syncPoints;
35983
36148
  }
36149
+ /**
36150
+ * @internal
36151
+ */
36152
+ static buildModifiedTempoLookup(score) {
36153
+ const syncPoints = [];
36154
+ const context = MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36155
+ }, (_barIndex, _currentTick, _currentTempo) => {
36156
+ }, _endTick => {
36157
+ });
36158
+ return context.automationToSyncPoint;
36159
+ }
35984
36160
  static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
35985
36161
  const controller = new MidiPlaybackController(score);
35986
- let currentTempo = score.tempo;
36162
+ const playContext = new PlayThroughContext();
36163
+ playContext.currentTempo = score.tempo;
36164
+ playContext.syncPoints = syncPoints;
35987
36165
  let previousMasterBar = null;
35988
36166
  // store the previous played bar for repeats
35989
36167
  const barOccurence = new Map();
@@ -35996,23 +36174,11 @@ class MidiFileGenerator {
35996
36174
  let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35997
36175
  occurence++;
35998
36176
  barOccurence.set(index, occurence);
35999
- generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
36000
- const barSyncPoints = bar.syncPoints;
36001
- if (barSyncPoints) {
36002
- for (const syncPoint of barSyncPoints) {
36003
- if (syncPoint.syncPointValue.barOccurence === occurence) {
36004
- const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
36005
- syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
36006
- }
36007
- }
36008
- }
36009
- if (bar.tempoAutomations.length > 0) {
36010
- currentTempo = bar.tempoAutomations[0].value;
36011
- }
36012
- generateTracks(index, currentTick, currentTempo);
36013
- if (bar.tempoAutomations.length > 0) {
36014
- currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
36015
- }
36177
+ generateMasterBar(bar, previousMasterBar, currentTick, playContext.currentTempo, occurence);
36178
+ const trackTempo = bar.tempoAutomations.length > 0 ? bar.tempoAutomations[0].value : playContext.currentTempo;
36179
+ generateTracks(index, currentTick, trackTempo);
36180
+ playContext.synthTick = currentTick;
36181
+ MidiFileGenerator.processBarTime(bar, occurence, playContext);
36016
36182
  }
36017
36183
  controller.moveNext();
36018
36184
  previousMasterBar = bar;
@@ -36023,21 +36189,119 @@ class MidiFileGenerator {
36023
36189
  // but where it ends according to the BPM and the remaining ticks.
36024
36190
  if (syncPoints.length > 0) {
36025
36191
  const lastSyncPoint = syncPoints[syncPoints.length - 1];
36026
- const remainingTicks = controller.currentTick - lastSyncPoint.tick;
36192
+ const remainingTicks = controller.currentTick - lastSyncPoint.synthTick;
36027
36193
  if (remainingTicks > 0) {
36028
- const syncPointData = new SyncPointData();
36029
- // last occurence of the last bar
36030
- syncPointData.barOccurence = barOccurence.get(score.masterBars.length - 1);
36031
- // same tempo as last point
36032
- syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
36033
- // interpolated end from last syncPoint
36034
- syncPointData.millisecondOffset =
36035
- lastSyncPoint.data.millisecondOffset +
36036
- MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
36037
- syncPoints.push(new BackingTrackSyncPoint(controller.currentTick, syncPointData));
36194
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36195
+ backingTrackSyncPoint.masterBarIndex = previousMasterBar.index;
36196
+ backingTrackSyncPoint.masterBarOccurence = barOccurence.get(previousMasterBar.index) - 1;
36197
+ backingTrackSyncPoint.synthTick = controller.currentTick;
36198
+ backingTrackSyncPoint.synthBpm = playContext.currentTempo;
36199
+ // we need to assume some BPM for the last interpolated point.
36200
+ // if we have more than just a start point, we keep the BPM before the last manual sync point
36201
+ // otherwise we have no customized sync BPM known and keep the synthesizer one.
36202
+ backingTrackSyncPoint.syncBpm =
36203
+ syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
36204
+ backingTrackSyncPoint.synthTime =
36205
+ lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
36206
+ backingTrackSyncPoint.syncTime =
36207
+ lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
36208
+ // update the previous sync point according to the new time
36209
+ lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
36210
+ syncPoints.push(backingTrackSyncPoint);
36038
36211
  }
36039
36212
  }
36040
36213
  finish(controller.currentTick);
36214
+ return playContext;
36215
+ }
36216
+ static processBarTime(bar, occurence, context) {
36217
+ const duration = bar.calculateDuration();
36218
+ const barSyncPoints = bar.syncPoints;
36219
+ const barStartTick = context.synthTick;
36220
+ if (barSyncPoints) {
36221
+ MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
36222
+ }
36223
+ else {
36224
+ MidiFileGenerator.processBarTimeNoSyncPoints(bar, context);
36225
+ }
36226
+ // don't forget the part after the last tempo change
36227
+ const endTick = barStartTick + duration;
36228
+ const tickOffset = endTick - context.synthTick;
36229
+ if (tickOffset > 0) {
36230
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36231
+ context.synthTick = endTick;
36232
+ }
36233
+ }
36234
+ static processBarTimeWithSyncPoints(bar, occurence, context) {
36235
+ const barStartTick = context.synthTick;
36236
+ const duration = bar.calculateDuration();
36237
+ let tempoChangeIndex = 0;
36238
+ let tickOffset;
36239
+ for (const syncPoint of bar.syncPoints) {
36240
+ if (syncPoint.syncPointValue.barOccurence !== occurence) {
36241
+ continue;
36242
+ }
36243
+ const syncPointTick = barStartTick + syncPoint.ratioPosition * duration;
36244
+ // first process all tempo changes until this sync point
36245
+ while (tempoChangeIndex < bar.tempoAutomations.length &&
36246
+ bar.tempoAutomations[tempoChangeIndex].ratioPosition <= syncPoint.ratioPosition) {
36247
+ const tempoChange = bar.tempoAutomations[tempoChangeIndex];
36248
+ const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
36249
+ tickOffset = absoluteTick - context.synthTick;
36250
+ if (tickOffset > 0) {
36251
+ context.synthTick = absoluteTick;
36252
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36253
+ }
36254
+ context.currentTempo = tempoChange.value;
36255
+ tempoChangeIndex++;
36256
+ }
36257
+ // process time until sync point
36258
+ tickOffset = syncPointTick - context.synthTick;
36259
+ if (tickOffset > 0) {
36260
+ context.synthTick = syncPointTick;
36261
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36262
+ }
36263
+ // update the previous sync point according to the new time
36264
+ if (context.syncPoints.length > 0) {
36265
+ context.syncPoints[context.syncPoints.length - 1].updateSyncBpm(context.synthTime, syncPoint.syncPointValue.millisecondOffset);
36266
+ }
36267
+ // create the new sync point
36268
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36269
+ backingTrackSyncPoint.masterBarIndex = bar.index;
36270
+ backingTrackSyncPoint.masterBarOccurence = occurence;
36271
+ backingTrackSyncPoint.synthTick = syncPointTick;
36272
+ backingTrackSyncPoint.synthBpm = context.currentTempo;
36273
+ backingTrackSyncPoint.synthTime = context.synthTime;
36274
+ backingTrackSyncPoint.syncTime = syncPoint.syncPointValue.millisecondOffset;
36275
+ backingTrackSyncPoint.syncBpm = 0 /* calculated by next sync point */;
36276
+ context.syncPoints.push(backingTrackSyncPoint);
36277
+ context.automationToSyncPoint.set(syncPoint, backingTrackSyncPoint);
36278
+ }
36279
+ // process remaining tempo changes after all sync points
36280
+ while (tempoChangeIndex < bar.tempoAutomations.length) {
36281
+ const tempoChange = bar.tempoAutomations[tempoChangeIndex];
36282
+ const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
36283
+ tickOffset = absoluteTick - context.synthTick;
36284
+ if (tickOffset > 0) {
36285
+ context.synthTick = absoluteTick;
36286
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36287
+ }
36288
+ context.currentTempo = tempoChange.value;
36289
+ tempoChangeIndex++;
36290
+ }
36291
+ }
36292
+ static processBarTimeNoSyncPoints(bar, context) {
36293
+ // walk through the tempo changes
36294
+ const barStartTick = context.synthTick;
36295
+ const duration = bar.calculateDuration();
36296
+ for (const changes of bar.tempoAutomations) {
36297
+ const absoluteTick = barStartTick + changes.ratioPosition * duration;
36298
+ const tickOffset = absoluteTick - context.synthTick;
36299
+ if (tickOffset > 0) {
36300
+ context.synthTick = absoluteTick;
36301
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36302
+ }
36303
+ context.currentTempo = changes.value;
36304
+ }
36041
36305
  }
36042
36306
  static toChannelShort(data) {
36043
36307
  const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
@@ -42322,6 +42586,18 @@ class FileLoadError extends AlphaTabError {
42322
42586
  * available importers
42323
42587
  */
42324
42588
  class ScoreLoader {
42589
+ /**
42590
+ * Loads the given alphaTex string.
42591
+ * @param tex The alphaTex string.
42592
+ * @param settings The settings to use for parsing.
42593
+ * @returns The parsed {@see Score}.
42594
+ */
42595
+ static loadAlphaTex(tex, settings) {
42596
+ const parser = new AlphaTexImporter();
42597
+ parser.logErrors = true;
42598
+ parser.initFromString(tex, settings ?? new Settings());
42599
+ return parser.readScore();
42600
+ }
42325
42601
  /**
42326
42602
  * Loads a score asynchronously from the given datasource
42327
42603
  * @param path the source path to load the binary file from
@@ -43172,7 +43448,7 @@ class AlphaSynthWebWorkerApi {
43172
43448
  this._midiEventsPlayedFilter = value;
43173
43449
  this._synth.postMessage({
43174
43450
  cmd: 'alphaSynth.setMidiEventsPlayedFilter',
43175
- value: value
43451
+ value: Environment.prepareForPostMessage(value)
43176
43452
  });
43177
43453
  }
43178
43454
  get playbackSpeed() {
@@ -43237,7 +43513,7 @@ class AlphaSynthWebWorkerApi {
43237
43513
  this._playbackRange = value;
43238
43514
  this._synth.postMessage({
43239
43515
  cmd: 'alphaSynth.setPlaybackRange',
43240
- value: value
43516
+ value: Environment.prepareForPostMessage(value)
43241
43517
  });
43242
43518
  }
43243
43519
  constructor(player, settings) {
@@ -43331,13 +43607,13 @@ class AlphaSynthWebWorkerApi {
43331
43607
  playOneTimeMidiFile(midi) {
43332
43608
  this._synth.postMessage({
43333
43609
  cmd: 'alphaSynth.playOneTimeMidiFile',
43334
- midi: JsonConverter.midiFileToJsObject(midi)
43610
+ midi: JsonConverter.midiFileToJsObject(Environment.prepareForPostMessage(midi))
43335
43611
  });
43336
43612
  }
43337
43613
  loadSoundFont(data, append) {
43338
43614
  this._synth.postMessage({
43339
43615
  cmd: 'alphaSynth.loadSoundFontBytes',
43340
- data: data,
43616
+ data: Environment.prepareForPostMessage(data),
43341
43617
  append: append
43342
43618
  });
43343
43619
  }
@@ -43349,13 +43625,13 @@ class AlphaSynthWebWorkerApi {
43349
43625
  loadMidiFile(midi) {
43350
43626
  this._synth.postMessage({
43351
43627
  cmd: 'alphaSynth.loadMidi',
43352
- midi: JsonConverter.midiFileToJsObject(midi)
43628
+ midi: JsonConverter.midiFileToJsObject(Environment.prepareForPostMessage(midi))
43353
43629
  });
43354
43630
  }
43355
43631
  applyTranspositionPitches(transpositionPitches) {
43356
43632
  this._synth.postMessage({
43357
43633
  cmd: 'alphaSynth.applyTranspositionPitches',
43358
- transpositionPitches: JSON.stringify(Array.from(transpositionPitches.entries()))
43634
+ transpositionPitches: JSON.stringify(Array.from(Environment.prepareForPostMessage(transpositionPitches).entries()))
43359
43635
  });
43360
43636
  }
43361
43637
  setChannelTranspositionPitch(channel, semitones) {
@@ -43527,7 +43803,7 @@ class AlphaTabWorkerScoreRenderer {
43527
43803
  });
43528
43804
  }
43529
43805
  serializeSettingsForWorker(settings) {
43530
- const jsObject = JsonConverter.settingsToJsObject(settings);
43806
+ const jsObject = JsonConverter.settingsToJsObject(Environment.prepareForPostMessage(settings));
43531
43807
  // cut out player settings, they are only needed on UI thread side
43532
43808
  jsObject.delete('player');
43533
43809
  return jsObject;
@@ -43585,11 +43861,11 @@ class AlphaTabWorkerScoreRenderer {
43585
43861
  }
43586
43862
  }
43587
43863
  renderScore(score, trackIndexes) {
43588
- const jsObject = score == null ? null : JsonConverter.scoreToJsObject(score);
43864
+ const jsObject = score == null ? null : JsonConverter.scoreToJsObject(Environment.prepareForPostMessage(score));
43589
43865
  this._worker.postMessage({
43590
43866
  cmd: 'alphaTab.renderScore',
43591
43867
  score: jsObject,
43592
- trackIndexes: trackIndexes,
43868
+ trackIndexes: Environment.prepareForPostMessage(trackIndexes),
43593
43869
  fontSizes: FontSizes.FontSizeLookupTables
43594
43870
  });
43595
43871
  }
@@ -43780,7 +44056,7 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
43780
44056
  addSamples(f) {
43781
44057
  this._worklet?.port.postMessage({
43782
44058
  cmd: AlphaSynthWorkerSynthOutput.CmdOutputAddSamples,
43783
- samples: f
44059
+ samples: Environment.prepareForPostMessage(f)
43784
44060
  });
43785
44061
  }
43786
44062
  resetSamples() {
@@ -61000,9 +61276,9 @@ class VersionInfo {
61000
61276
  print(`build date: ${VersionInfo.date}`);
61001
61277
  }
61002
61278
  }
61003
- VersionInfo.version = '1.6.0-alpha.1428';
61004
- VersionInfo.date = '2025-05-29T00:44:50.954Z';
61005
- VersionInfo.commit = 'fa81a14da248f229511867324cee663ea70a72b3';
61279
+ VersionInfo.version = '1.6.0-alpha.1432';
61280
+ VersionInfo.date = '2025-05-31T02:06:00.256Z';
61281
+ VersionInfo.commit = '91e41f1b2cf0b786a1ca2053f51cf3f370da4bc6';
61006
61282
 
61007
61283
  /**
61008
61284
  * A factory for custom layout engines.
@@ -61547,6 +61823,29 @@ class Environment {
61547
61823
  print(`Screen Size: ${window.screen.width}x${window.screen.height}`);
61548
61824
  }
61549
61825
  }
61826
+ /**
61827
+ * Prepares the given object to be sent to workers. Web Frameworks like Vue might
61828
+ * create proxy objects for all objects used. This code handles the necessary unwrapping.
61829
+ * @internal
61830
+ * @target web
61831
+ */
61832
+ static prepareForPostMessage(object) {
61833
+ if (!object) {
61834
+ return object;
61835
+ }
61836
+ // Vue toRaw:
61837
+ // https://github.com/vuejs/core/blob/e7381761cc7971c0d40ae0a0a72687a500fd8db3/packages/reactivity/src/reactive.ts#L378-L381
61838
+ if (typeof object === 'object') {
61839
+ const unwrapped = object.__v_raw;
61840
+ if (unwrapped) {
61841
+ return Environment.prepareForPostMessage(unwrapped);
61842
+ }
61843
+ }
61844
+ // Solidjs unwrap: the symbol required to access the raw object is unfortunately hidden and we cannot unwrap it without importing
61845
+ // import { unwrap } from "solid-js/store"
61846
+ // alternative for users is to replace this method during runtime.
61847
+ return object;
61848
+ }
61550
61849
  }
61551
61850
  Environment.StaffIdBeforeSlashAlways = 'before-slash-always';
61552
61851
  Environment.StaffIdBeforeScoreAlways = 'before-score-always';
@@ -61818,6 +62117,7 @@ class CoreSettings {
61818
62117
 
61819
62118
  const _barrel$7 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
61820
62119
  __proto__: null,
62120
+ AlphaTexImporter,
61821
62121
  ScoreImporter,
61822
62122
  ScoreLoader,
61823
62123
  UnsupportedFormatError
@@ -62652,6 +62952,7 @@ class GpifWriter {
62652
62952
  : undefined;
62653
62953
  const millisecondPadding = initialSyncPoint ? initialSyncPoint.syncPointValue.millisecondOffset : 0;
62654
62954
  this.backingTrackFramePadding = (-1 * ((millisecondPadding / 1000) * GpifWriter.SampleRate)) | 0;
62955
+ const modifiedTempoLookup = new Lazy(() => MidiFileGenerator.buildModifiedTempoLookup(score));
62655
62956
  for (const mb of score.masterBars) {
62656
62957
  for (const automation of mb.tempoAutomations) {
62657
62958
  const tempoAutomation = automations.addElement('Automation');
@@ -62676,7 +62977,7 @@ class GpifWriter {
62676
62977
  const value = syncPointAutomation.addElement('Value');
62677
62978
  value.addElement('BarIndex').innerText = mb.index.toString();
62678
62979
  value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
62679
- value.addElement('ModifiedTempo').innerText = syncPoint.syncPointValue.modifiedTempo.toString();
62980
+ value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
62680
62981
  value.addElement('OriginalTempo').innerText = score.tempo.toString();
62681
62982
  const frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
62682
62983
  GpifWriter.SampleRate) |