@coderline/alphatab 1.6.0-alpha.1418 → 1.6.0-alpha.1421

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.6.0-alpha.1418 (develop, build 1418)
2
+ * alphaTab v1.6.0-alpha.1421 (develop, build 1421)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -14318,13 +14318,6 @@
14318
14318
  * @json_strict
14319
14319
  */
14320
14320
  class BackingTrack {
14321
- constructor() {
14322
- /**
14323
- * The number of milliseconds the audio should be shifted to align with the song.
14324
- * (e.g. negative values allow skipping potential silent parts at the start of the file and directly start with the first note).
14325
- */
14326
- this.padding = 0;
14327
- }
14328
14321
  }
14329
14322
 
14330
14323
  /**
@@ -14357,6 +14350,7 @@
14357
14350
  constructor() {
14358
14351
  this._hasAnacrusis = false;
14359
14352
  this._skipApplyLyrics = false;
14353
+ this._backingTrackPadding = 0;
14360
14354
  this._doubleBars = new Set();
14361
14355
  this._keySignatures = new Map();
14362
14356
  }
@@ -14589,7 +14583,7 @@
14589
14583
  assetId = c.innerText;
14590
14584
  break;
14591
14585
  case 'FramePadding':
14592
- backingTrack.padding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
14586
+ this._backingTrackPadding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
14593
14587
  break;
14594
14588
  }
14595
14589
  }
@@ -16803,6 +16797,7 @@
16803
16797
  masterBar.tempoAutomations.push(automation);
16804
16798
  break;
16805
16799
  case AutomationType.SyncPoint:
16800
+ automation.syncPointValue.millisecondOffset -= this._backingTrackPadding;
16806
16801
  masterBar.addSyncPoint(automation);
16807
16802
  break;
16808
16803
  }
@@ -22291,17 +22286,6 @@
22291
22286
  }
22292
22287
  }
22293
22288
 
22294
- /**
22295
- * Rerpresents a point to sync the alphaTab time axis with an external backing track.
22296
- */
22297
- class BackingTrackSyncPoint {
22298
- constructor(tick, data) {
22299
- this.tick = 0;
22300
- this.tick = tick;
22301
- this.data = data;
22302
- }
22303
- }
22304
-
22305
22289
  class MidiFileSequencerTempoChange {
22306
22290
  constructor(bpm, ticks, time) {
22307
22291
  this.bpm = bpm;
@@ -22309,10 +22293,12 @@
22309
22293
  this.time = time;
22310
22294
  }
22311
22295
  }
22312
- class BackingTrackSyncPointWithTime extends BackingTrackSyncPoint {
22313
- constructor(tick, data, time) {
22314
- super(tick, data);
22315
- this.time = time;
22296
+ class BackingTrackSyncPointWithTime {
22297
+ constructor(tick, time, modifiedTempo, millisecondOffset) {
22298
+ this.alphaTabTick = tick;
22299
+ this.alphaTabTime = time;
22300
+ this.modifiedTempo = modifiedTempo;
22301
+ this.millisecondOffset = millisecondOffset;
22316
22302
  }
22317
22303
  }
22318
22304
  class MidiSequencerState {
@@ -22418,7 +22404,7 @@
22418
22404
  this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
22419
22405
  this._mainState.modifiedTempo =
22420
22406
  this._mainState.syncPoints.length > 0
22421
- ? this._mainState.syncPoints[0].data.modifiedTempo
22407
+ ? this._mainState.syncPoints[0].modifiedTempo
22422
22408
  : this._mainState.currentTempo;
22423
22409
  if (this.isPlayingMain) {
22424
22410
  const metronomeVolume = this._synthesizer.metronomeVolume;
@@ -22590,25 +22576,52 @@
22590
22576
  mainUpdateSyncPoints(syncPoints) {
22591
22577
  const state = this._mainState;
22592
22578
  syncPoints.sort((a, b) => a.tick - b.tick); // just in case
22593
- state.syncPoints = new Array(syncPoints.length);
22579
+ state.syncPoints = [];
22594
22580
  if (syncPoints.length >= 0) {
22595
22581
  let bpm = 120;
22596
22582
  let absTick = 0;
22597
22583
  let absTime = 0.0;
22598
- let previousTick = 0;
22599
22584
  let tempoChangeIndex = 0;
22600
22585
  for (let i = 0; i < syncPoints.length; i++) {
22601
22586
  const p = syncPoints[i];
22602
- const deltaTick = p.tick - previousTick;
22603
- absTick += deltaTick;
22604
- absTime += deltaTick * (60000.0 / (bpm * state.division));
22605
- state.syncPoints[i] = new BackingTrackSyncPointWithTime(p.tick, p.data, absTime);
22606
- previousTick = p.tick;
22587
+ let deltaTick = 0;
22588
+ // remember state from previous sync point (or start). to handle linear interpolation
22589
+ let previousModifiedTempo;
22590
+ let previousMillisecondOffset;
22591
+ let previousTick;
22592
+ if (i === 0) {
22593
+ previousModifiedTempo = bpm;
22594
+ previousMillisecondOffset = 0;
22595
+ previousTick = 0;
22596
+ }
22597
+ else {
22598
+ const previousSyncPoint = syncPoints[i - 1];
22599
+ previousModifiedTempo = previousSyncPoint.data.modifiedTempo;
22600
+ previousMillisecondOffset = previousSyncPoint.data.millisecondOffset;
22601
+ previousTick = previousSyncPoint.tick;
22602
+ }
22603
+ // process time until sync point
22604
+ // here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
22605
+ // we have to calculate a interpolated sync point on the alphaTab time axis.
22606
+ // otherwise the linear interpolation later in the lookup will fail.
22607
+ // 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
22607
22608
  while (tempoChangeIndex < state.tempoChanges.length &&
22608
- state.tempoChanges[tempoChangeIndex].ticks <= absTick) {
22609
+ state.tempoChanges[tempoChangeIndex].ticks <= p.tick) {
22610
+ deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
22611
+ if (deltaTick > 0) {
22612
+ absTick += deltaTick;
22613
+ absTime += deltaTick * (60000.0 / (bpm * state.division));
22614
+ const millisPerTick = (p.data.millisecondOffset - previousMillisecondOffset) / (p.tick - previousTick);
22615
+ const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
22616
+ state.syncPoints.push(new BackingTrackSyncPointWithTime(absTick, absTime, previousModifiedTempo, interpolatedMillisecondOffset));
22617
+ }
22609
22618
  bpm = state.tempoChanges[tempoChangeIndex].bpm;
22610
22619
  tempoChangeIndex++;
22611
22620
  }
22621
+ deltaTick = p.tick - absTick;
22622
+ absTick += deltaTick;
22623
+ absTime += deltaTick * (60000.0 / (bpm * state.division));
22624
+ state.syncPoints.push(new BackingTrackSyncPointWithTime(p.tick, absTime, p.data.modifiedTempo, p.data.millisecondOffset));
22612
22625
  }
22613
22626
  }
22614
22627
  state.syncPointIndex = 0;
@@ -22622,7 +22635,7 @@
22622
22635
  this.updateCurrentTempo(state, timePosition);
22623
22636
  const lastTempoChange = state.tempoChanges[state.tempoChangeIndex];
22624
22637
  const timeDiff = timePosition - lastTempoChange.time;
22625
- const ticks = ((timeDiff / (60000.0 / (lastTempoChange.bpm * state.division))) | 0);
22638
+ const ticks = (timeDiff / (60000.0 / (lastTempoChange.bpm * state.division))) | 0;
22626
22639
  // we add 1 for possible rounding errors.(floating point issuses)
22627
22640
  return lastTempoChange.ticks + ticks + 1;
22628
22641
  }
@@ -22639,19 +22652,21 @@
22639
22652
  state.tempoChangeIndex = tempoChangeIndex;
22640
22653
  state.currentTempo = state.tempoChanges[state.tempoChangeIndex].bpm;
22641
22654
  }
22655
+ }
22656
+ updateSyncPoints(state, timePosition) {
22642
22657
  const syncPoints = state.syncPoints;
22643
22658
  if (syncPoints.length > 0) {
22644
22659
  let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
22645
- if (timePosition < syncPoints[syncPointIndex].data.millisecondOffset) {
22660
+ if (timePosition < syncPoints[syncPointIndex].millisecondOffset) {
22646
22661
  syncPointIndex = 0;
22647
22662
  }
22648
22663
  while (syncPointIndex + 1 < syncPoints.length &&
22649
- syncPoints[syncPointIndex + 1].data.millisecondOffset <= timePosition) {
22664
+ syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
22650
22665
  syncPointIndex++;
22651
22666
  }
22652
22667
  if (syncPointIndex !== state.syncPointIndex) {
22653
22668
  state.syncPointIndex = syncPointIndex;
22654
- state.modifiedTempo = syncPoints[syncPointIndex].data.modifiedTempo;
22669
+ state.modifiedTempo = syncPoints[syncPointIndex].modifiedTempo;
22655
22670
  }
22656
22671
  }
22657
22672
  else {
@@ -22664,21 +22679,21 @@
22664
22679
  if (timePosition < 0 || syncPoints.length === 0) {
22665
22680
  return timePosition;
22666
22681
  }
22667
- this.updateCurrentTempo(this._mainState, timePosition);
22682
+ this.updateSyncPoints(this._mainState, timePosition);
22668
22683
  const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22669
22684
  const currentSyncPoint = syncPoints[syncPointIndex];
22670
- const timeDiff = timePosition - currentSyncPoint.data.millisecondOffset;
22685
+ const timeDiff = timePosition - currentSyncPoint.millisecondOffset;
22671
22686
  let alphaTabTimeDiff;
22672
22687
  if (syncPointIndex + 1 < syncPoints.length) {
22673
22688
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22674
- const relativeTimeDiff = timeDiff / (nextSyncPoint.data.millisecondOffset - currentSyncPoint.data.millisecondOffset);
22675
- alphaTabTimeDiff = (nextSyncPoint.time - currentSyncPoint.time) * relativeTimeDiff;
22689
+ const relativeTimeDiff = timeDiff / (nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset);
22690
+ alphaTabTimeDiff = (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22676
22691
  }
22677
22692
  else {
22678
- const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.data.millisecondOffset);
22679
- alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.time) * relativeTimeDiff;
22693
+ const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.millisecondOffset);
22694
+ alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22680
22695
  }
22681
- return (currentSyncPoint.time + alphaTabTimeDiff) / this.playbackSpeed;
22696
+ return (currentSyncPoint.alphaTabTime + alphaTabTimeDiff) / this.playbackSpeed;
22682
22697
  }
22683
22698
  mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
22684
22699
  const mainState = this._mainState;
@@ -22688,25 +22703,27 @@
22688
22703
  }
22689
22704
  timePosition *= this.playbackSpeed;
22690
22705
  let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22691
- if (timePosition < syncPoints[syncPointIndex].time) {
22706
+ if (timePosition < syncPoints[syncPointIndex].alphaTabTime) {
22692
22707
  syncPointIndex = 0;
22693
22708
  }
22694
- while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].time <= timePosition) {
22709
+ while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].alphaTabTime <= timePosition) {
22695
22710
  syncPointIndex++;
22696
22711
  }
22712
+ // NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
22713
+ // we ensure that we have a linear increase between two points
22697
22714
  const currentSyncPoint = syncPoints[syncPointIndex];
22698
- const alphaTabTimeDiff = timePosition - currentSyncPoint.time;
22715
+ const alphaTabTimeDiff = timePosition - currentSyncPoint.alphaTabTime;
22699
22716
  let backingTrackPos;
22700
22717
  if (syncPointIndex + 1 < syncPoints.length) {
22701
22718
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22702
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.time - currentSyncPoint.time);
22703
- const backingTrackDiff = nextSyncPoint.data.millisecondOffset - currentSyncPoint.data.millisecondOffset;
22704
- backingTrackPos = currentSyncPoint.data.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22719
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime);
22720
+ const backingTrackDiff = nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset;
22721
+ backingTrackPos = currentSyncPoint.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22705
22722
  }
22706
22723
  else {
22707
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.time);
22708
- const frameDiff = backingTrackLength - currentSyncPoint.data.millisecondOffset;
22709
- backingTrackPos = currentSyncPoint.data.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22724
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.alphaTabTime);
22725
+ const frameDiff = backingTrackLength - currentSyncPoint.millisecondOffset;
22726
+ backingTrackPos = currentSyncPoint.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22710
22727
  }
22711
22728
  return backingTrackPos;
22712
22729
  }
@@ -28588,7 +28605,9 @@
28588
28605
  hasSamplesForPercussion(key) {
28589
28606
  return this.synthesizer.hasSamplesForPercussion(key);
28590
28607
  }
28591
- loadBackingTrack(_score, _syncPoints) {
28608
+ loadBackingTrack(_score) {
28609
+ }
28610
+ updateSyncPoints(_syncPoints) {
28592
28611
  }
28593
28612
  }
28594
28613
  /**
@@ -32485,15 +32504,9 @@
32485
32504
  return null;
32486
32505
  }
32487
32506
  const o = new Map();
32488
- o.set("padding", obj.padding);
32489
32507
  return o;
32490
32508
  }
32491
32509
  static setProperty(obj, property, v) {
32492
- switch (property) {
32493
- case "padding":
32494
- obj.padding = v;
32495
- return true;
32496
- }
32497
32510
  return false;
32498
32511
  }
32499
32512
  }
@@ -34956,7 +34969,13 @@
34956
34969
  * Gets the list of tempo changes within the tick lookup.
34957
34970
  */
34958
34971
  this.tempoChanges = [];
34972
+ /**
34973
+ * The first beat in the bar.
34974
+ */
34959
34975
  this.firstBeat = null;
34976
+ /**
34977
+ * The last beat in the bar.
34978
+ */
34960
34979
  this.lastBeat = null;
34961
34980
  /**
34962
34981
  * Gets or sets the {@link MasterBarTickLookup} of the next masterbar in the {@link Score}
@@ -35370,14 +35389,13 @@
35370
35389
  constructor() {
35371
35390
  this._currentMasterBar = null;
35372
35391
  /**
35373
- * Gets a dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35392
+ * A dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35374
35393
  * This lookup only contains the first time a MasterBar is played. For a whole sequence of the song refer to {@link MasterBars}.
35375
35394
  * @internal
35376
35395
  */
35377
35396
  this.masterBarLookup = new Map();
35378
35397
  /**
35379
- * Gets a list of all {@link MasterBarTickLookup} sorted by time.
35380
- * @internal
35398
+ * A list of all {@link MasterBarTickLookup} sorted by time.
35381
35399
  */
35382
35400
  this.masterBars = [];
35383
35401
  /**
@@ -35751,6 +35769,17 @@
35751
35769
  }
35752
35770
  }
35753
35771
 
35772
+ /**
35773
+ * Rerpresents a point to sync the alphaTab time axis with an external backing track.
35774
+ */
35775
+ class BackingTrackSyncPoint {
35776
+ constructor(tick, data) {
35777
+ this.tick = 0;
35778
+ this.tick = tick;
35779
+ this.data = data;
35780
+ }
35781
+ }
35782
+
35754
35783
  class MidiNoteDuration {
35755
35784
  constructor() {
35756
35785
  this.noteOnly = 0;
@@ -35820,62 +35849,22 @@
35820
35849
  this.generateTrack(track);
35821
35850
  }
35822
35851
  Logger.debug('Midi', 'Begin midi generation');
35823
- const controller = new MidiPlaybackController(this._score);
35824
- let previousMasterBar = null;
35825
- let currentTempo = this._score.tempo;
35826
- // store the previous played bar for repeats
35827
- const barOccurence = new Map();
35828
- while (!controller.finished) {
35829
- const index = controller.index;
35830
- const bar = this._score.masterBars[index];
35831
- const currentTick = controller.currentTick;
35832
- controller.processCurrent();
35833
- if (controller.shouldPlay) {
35834
- let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35835
- occurence++;
35836
- barOccurence.set(index, occurence);
35837
- this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35838
- if (bar.tempoAutomations.length > 0) {
35839
- currentTempo = bar.tempoAutomations[0].value;
35840
- }
35841
- for (const track of this._score.tracks) {
35842
- for (const staff of track.staves) {
35843
- if (index < staff.bars.length) {
35844
- this.generateBar(staff.bars[index], currentTick, currentTempo);
35845
- }
35852
+ this.syncPoints = [];
35853
+ MidiFileGenerator.playThroughSong(this._score, this.syncPoints, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
35854
+ this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35855
+ }, (index, currentTick, currentTempo) => {
35856
+ for (const track of this._score.tracks) {
35857
+ for (const staff of track.staves) {
35858
+ if (index < staff.bars.length) {
35859
+ this.generateBar(staff.bars[index], currentTick, currentTempo);
35846
35860
  }
35847
35861
  }
35848
- if (bar.tempoAutomations.length > 0) {
35849
- currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35850
- }
35851
35862
  }
35852
- controller.moveNext();
35853
- previousMasterBar = bar;
35854
- }
35855
- // here we interpolate the sync point which marks the end of the sync.
35856
- // Sync points define new tempos at certain positions.
35857
- // looking from the last sync point to the end we do not assume the end where the audio ends,
35858
- // but where it ends according to the BPM and the remaining ticks.
35859
- if (this.syncPoints.length > 0) {
35860
- const lastSyncPoint = this.syncPoints[this.syncPoints.length - 1];
35861
- const endTick = controller.currentTick;
35862
- const remainingTicks = endTick - lastSyncPoint.tick;
35863
- if (remainingTicks > 0) {
35864
- const syncPointData = new SyncPointData();
35865
- // last occurence of the last bar
35866
- syncPointData.barOccurence = barOccurence.get(this._score.masterBars.length - 1);
35867
- // same tempo as last point
35868
- syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35869
- // interpolated end from last syncPoint
35870
- syncPointData.millisecondOffset =
35871
- lastSyncPoint.data.millisecondOffset +
35872
- MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35873
- this.syncPoints.push(new BackingTrackSyncPoint(endTick, syncPointData));
35863
+ }, endTick => {
35864
+ for (const track of this._score.tracks) {
35865
+ this._handler.finishTrack(track.index, endTick);
35874
35866
  }
35875
- }
35876
- for (const track of this._score.tracks) {
35877
- this._handler.finishTrack(track.index, controller.currentTick);
35878
- }
35867
+ });
35879
35868
  Logger.debug('Midi', 'Midi generation done');
35880
35869
  }
35881
35870
  generateTrack(track) {
@@ -35920,6 +35909,81 @@
35920
35909
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
35921
35910
  this.addProgramChange(track, 0, channel, playbackInfo.program);
35922
35911
  }
35912
+ /**
35913
+ * Generates the sync points for the given score without re-generating the midi itself.
35914
+ * @remarks
35915
+ * Use this method if a re-generation of the sync points after modification is required.
35916
+ * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
35917
+ * need to be considered for synchronization.
35918
+ * @param score The song for which to regenerate the sync points.
35919
+ * @returns The generated sync points for usage in the backing track playback.
35920
+ */
35921
+ static generateSyncPoints(score) {
35922
+ const syncPoints = [];
35923
+ MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
35924
+ }, (_barIndex, _currentTick, _currentTempo) => {
35925
+ }, _endTick => {
35926
+ });
35927
+ return syncPoints;
35928
+ }
35929
+ static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
35930
+ const controller = new MidiPlaybackController(score);
35931
+ let currentTempo = score.tempo;
35932
+ let previousMasterBar = null;
35933
+ // store the previous played bar for repeats
35934
+ const barOccurence = new Map();
35935
+ while (!controller.finished) {
35936
+ const index = controller.index;
35937
+ const bar = score.masterBars[index];
35938
+ const currentTick = controller.currentTick;
35939
+ controller.processCurrent();
35940
+ if (controller.shouldPlay) {
35941
+ let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35942
+ occurence++;
35943
+ barOccurence.set(index, occurence);
35944
+ generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35945
+ const barSyncPoints = bar.syncPoints;
35946
+ if (barSyncPoints) {
35947
+ for (const syncPoint of barSyncPoints) {
35948
+ if (syncPoint.syncPointValue.barOccurence === occurence) {
35949
+ const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
35950
+ syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
35951
+ }
35952
+ }
35953
+ }
35954
+ if (bar.tempoAutomations.length > 0) {
35955
+ currentTempo = bar.tempoAutomations[0].value;
35956
+ }
35957
+ generateTracks(index, currentTick, currentTempo);
35958
+ if (bar.tempoAutomations.length > 0) {
35959
+ currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35960
+ }
35961
+ }
35962
+ controller.moveNext();
35963
+ previousMasterBar = bar;
35964
+ }
35965
+ // here we interpolate the sync point which marks the end of the sync.
35966
+ // Sync points define new tempos at certain positions.
35967
+ // looking from the last sync point to the end we do not assume the end where the audio ends,
35968
+ // but where it ends according to the BPM and the remaining ticks.
35969
+ if (syncPoints.length > 0) {
35970
+ const lastSyncPoint = syncPoints[syncPoints.length - 1];
35971
+ const remainingTicks = controller.currentTick - lastSyncPoint.tick;
35972
+ if (remainingTicks > 0) {
35973
+ const syncPointData = new SyncPointData();
35974
+ // last occurence of the last bar
35975
+ syncPointData.barOccurence = barOccurence.get(score.masterBars.length - 1);
35976
+ // same tempo as last point
35977
+ syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35978
+ // interpolated end from last syncPoint
35979
+ syncPointData.millisecondOffset =
35980
+ lastSyncPoint.data.millisecondOffset +
35981
+ MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35982
+ syncPoints.push(new BackingTrackSyncPoint(controller.currentTick, syncPointData));
35983
+ }
35984
+ }
35985
+ finish(controller.currentTick);
35986
+ }
35923
35987
  static toChannelShort(data) {
35924
35988
  const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
35925
35989
  return Math.max(value, -1) + 1;
@@ -35951,15 +36015,6 @@
35951
36015
  else {
35952
36016
  masterBarLookup.tempoChanges.push(new MasterBarTickLookupTempoChange(currentTick, currentTempo));
35953
36017
  }
35954
- const syncPoints = masterBar.syncPoints;
35955
- if (syncPoints) {
35956
- for (const syncPoint of syncPoints) {
35957
- if (syncPoint.syncPointValue.barOccurence === barOccurence) {
35958
- const tick = currentTick + masterBarDuration * syncPoint.ratioPosition;
35959
- this.syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
35960
- }
35961
- }
35962
- }
35963
36018
  masterBarLookup.masterBar = masterBar;
35964
36019
  masterBarLookup.start = currentTick;
35965
36020
  masterBarLookup.end = masterBarLookup.start + masterBarDuration;
@@ -38256,21 +38311,23 @@
38256
38311
  this._backingTrackOutput.seekTo(this.sequencer.mainTimePositionToBackingTrack(timePosition, this._backingTrackOutput.backingTrackDuration));
38257
38312
  }
38258
38313
  }
38259
- loadBackingTrack(score, syncPoints) {
38314
+ loadBackingTrack(score) {
38260
38315
  const backingTrackInfo = score.backingTrack;
38261
38316
  if (backingTrackInfo) {
38262
38317
  this._backingTrackOutput.loadBackingTrack(backingTrackInfo);
38263
- this.sequencer.mainUpdateSyncPoints(syncPoints);
38264
38318
  this.timePosition = 0;
38265
38319
  }
38266
38320
  }
38321
+ updateSyncPoints(syncPoints) {
38322
+ this.sequencer.mainUpdateSyncPoints(syncPoints);
38323
+ this.tickPosition = this.tickPosition;
38324
+ }
38267
38325
  }
38268
38326
 
38269
38327
  class ExternalMediaSynthOutput {
38270
38328
  constructor() {
38271
38329
  // fake rate
38272
38330
  this.sampleRate = 44100;
38273
- this._padding = 0;
38274
38331
  this._seekPosition = 0;
38275
38332
  this.ready = new EventEmitter();
38276
38333
  this.samplesPlayed = new EventEmitterOfT();
@@ -38313,20 +38370,19 @@
38313
38370
  seekTo(time) {
38314
38371
  const handler = this.handler;
38315
38372
  if (handler) {
38316
- handler.seekTo(time - this._padding);
38373
+ handler.seekTo(time);
38317
38374
  }
38318
38375
  else {
38319
- this._seekPosition = time - this._padding;
38376
+ this._seekPosition = time;
38320
38377
  }
38321
38378
  }
38322
- loadBackingTrack(backingTrack) {
38323
- this._padding = backingTrack.padding;
38379
+ loadBackingTrack(_backingTrack) {
38324
38380
  }
38325
38381
  open(_bufferTimeInMilliseconds) {
38326
38382
  this.ready.trigger();
38327
38383
  }
38328
38384
  updatePosition(currentTime) {
38329
- this.timeUpdate.trigger(currentTime + this._padding);
38385
+ this.timeUpdate.trigger(currentTime);
38330
38386
  }
38331
38387
  play() {
38332
38388
  this.handler?.play();
@@ -38591,9 +38647,14 @@
38591
38647
  this._instance.loadMidiFile(midi);
38592
38648
  }
38593
38649
  }
38594
- loadBackingTrack(score, syncPoints) {
38650
+ loadBackingTrack(score) {
38595
38651
  if (this._instance) {
38596
- this._instance.loadBackingTrack(score, syncPoints);
38652
+ this._instance.loadBackingTrack(score);
38653
+ }
38654
+ }
38655
+ updateSyncPoints(syncPoints) {
38656
+ if (this._instance) {
38657
+ this._instance.updateSyncPoints(syncPoints);
38597
38658
  }
38598
38659
  }
38599
38660
  applyTranspositionPitches(transpositionPitches) {
@@ -39922,7 +39983,7 @@
39922
39983
  }
39923
39984
  appendRenderResult(result, isLast) {
39924
39985
  // resizing the canvas and wrapper elements at the end is enough
39925
- // it avoids flickering on resizes and re-renders.
39986
+ // it avoids flickering on resizes and re-renders.
39926
39987
  // the individual partials are anyhow sized correctly
39927
39988
  if (isLast) {
39928
39989
  this.canvasElement.width = result.totalWidth;
@@ -40684,9 +40745,23 @@
40684
40745
  this.onMidiLoad(midiFile);
40685
40746
  const player = this._player;
40686
40747
  player.loadMidiFile(midiFile);
40687
- player.loadBackingTrack(score, generator.syncPoints);
40748
+ player.loadBackingTrack(score);
40749
+ player.updateSyncPoints(generator.syncPoints);
40688
40750
  player.applyTranspositionPitches(generator.transpositionPitches);
40689
40751
  }
40752
+ /**
40753
+ * Triggers an update of the sync points for the current score after modification within the data model
40754
+ * @category Methods - Player
40755
+ * @since 1.6.0
40756
+ */
40757
+ updateSyncPoints() {
40758
+ if (!this.score) {
40759
+ return;
40760
+ }
40761
+ const score = this.score;
40762
+ const player = this._player;
40763
+ player.updateSyncPoints(MidiFileGenerator.generateSyncPoints(score));
40764
+ }
40690
40765
  /**
40691
40766
  * Changes the volume of the given tracks.
40692
40767
  * @param tracks The tracks for which the volume should be changed.
@@ -41245,8 +41320,9 @@
41245
41320
  // it can happen that the cursor reaches the target position slightly too early (especially on backing tracks)
41246
41321
  // to avoid the cursor stopping, causing a wierd look, we animate the cursor to the double position in double time.
41247
41322
  // beatCursor!.transitionToX((duration / cursorSpeed), nextBeatX);
41248
- const doubleEndBeatX = startBeatX + (nextBeatX - startBeatX) * 2;
41249
- beatCursor.transitionToX((duration / cursorSpeed) * 2, doubleEndBeatX);
41323
+ const factor = cursorMode === MidiTickLookupFindBeatResultCursorMode.ToNextBext ? 2 : 1;
41324
+ const doubleEndBeatX = startBeatX + (nextBeatX - startBeatX) * factor;
41325
+ beatCursor.transitionToX((duration / cursorSpeed) * factor, doubleEndBeatX);
41250
41326
  });
41251
41327
  }
41252
41328
  else {
@@ -43354,6 +43430,8 @@
43354
43430
  }
43355
43431
  loadBackingTrack(_score) {
43356
43432
  }
43433
+ updateSyncPoints(_syncPoints) {
43434
+ }
43357
43435
  }
43358
43436
 
43359
43437
  /**
@@ -43728,7 +43806,6 @@
43728
43806
  constructor() {
43729
43807
  // fake rate
43730
43808
  this.sampleRate = 44100;
43731
- this._padding = 0;
43732
43809
  this._updateInterval = 0;
43733
43810
  this.ready = new EventEmitter();
43734
43811
  this.samplesPlayed = new EventEmitterOfT();
@@ -43752,13 +43829,12 @@
43752
43829
  this.audioElement.volume = value;
43753
43830
  }
43754
43831
  seekTo(time) {
43755
- this.audioElement.currentTime = time / 1000 - this._padding;
43832
+ this.audioElement.currentTime = time / 1000;
43756
43833
  }
43757
43834
  loadBackingTrack(backingTrack) {
43758
43835
  if (this.audioElement?.src) {
43759
43836
  URL.revokeObjectURL(this.audioElement.src);
43760
43837
  }
43761
- this._padding = backingTrack.padding / 1000;
43762
43838
  const blob = new Blob([backingTrack.rawAudioFile]);
43763
43839
  // https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
43764
43840
  // Step 8. resets the playbackRate, we need to remember and restore it.
@@ -43777,7 +43853,7 @@
43777
43853
  this.ready.trigger();
43778
43854
  }
43779
43855
  updatePosition() {
43780
- const timePos = (this.audioElement.currentTime + this._padding) * 1000;
43856
+ const timePos = this.audioElement.currentTime * 1000;
43781
43857
  this.timeUpdate.trigger(timePos);
43782
43858
  }
43783
43859
  play() {
@@ -43996,6 +44072,7 @@
43996
44072
  webFont.usages--;
43997
44073
  if (webFont.usages <= 0) {
43998
44074
  webFont.element.remove();
44075
+ BrowserUiFacade._registeredWebFonts.delete(webFont.hash);
43999
44076
  }
44000
44077
  }
44001
44078
  createCanvasElement() {
@@ -44152,6 +44229,7 @@
44152
44229
  checker.checkForFontAvailability();
44153
44230
  settings.display.resources.smuflFont = new Font(familyName, Environment.MusicFontSize, FontStyle.Plain, FontWeight.Regular);
44154
44231
  const webFont = {
44232
+ hash,
44155
44233
  element: styleElement,
44156
44234
  fontSuffix,
44157
44235
  usages: 1,
@@ -60863,9 +60941,9 @@
60863
60941
  print(`build date: ${VersionInfo.date}`);
60864
60942
  }
60865
60943
  }
60866
- VersionInfo.version = '1.6.0-alpha.1418';
60867
- VersionInfo.date = '2025-05-21T02:08:19.492Z';
60868
- VersionInfo.commit = '1f6342282295a9544ebc36ddd38d73d220d2caab';
60944
+ VersionInfo.version = '1.6.0-alpha.1421';
60945
+ VersionInfo.date = '2025-05-22T19:50:36.984Z';
60946
+ VersionInfo.commit = '0b4cd7b2a2c0d244d65c0c0c5254fe4814976bc4';
60869
60947
 
60870
60948
  /**
60871
60949
  * A factory for custom layout engines.