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

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.1420 (develop, build 1420)
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
  }
@@ -22642,16 +22655,16 @@
22642
22655
  const syncPoints = state.syncPoints;
22643
22656
  if (syncPoints.length > 0) {
22644
22657
  let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
22645
- if (timePosition < syncPoints[syncPointIndex].data.millisecondOffset) {
22658
+ if (timePosition < syncPoints[syncPointIndex].millisecondOffset) {
22646
22659
  syncPointIndex = 0;
22647
22660
  }
22648
22661
  while (syncPointIndex + 1 < syncPoints.length &&
22649
- syncPoints[syncPointIndex + 1].data.millisecondOffset <= timePosition) {
22662
+ syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
22650
22663
  syncPointIndex++;
22651
22664
  }
22652
22665
  if (syncPointIndex !== state.syncPointIndex) {
22653
22666
  state.syncPointIndex = syncPointIndex;
22654
- state.modifiedTempo = syncPoints[syncPointIndex].data.modifiedTempo;
22667
+ state.modifiedTempo = syncPoints[syncPointIndex].modifiedTempo;
22655
22668
  }
22656
22669
  }
22657
22670
  else {
@@ -22667,18 +22680,18 @@
22667
22680
  this.updateCurrentTempo(this._mainState, timePosition);
22668
22681
  const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22669
22682
  const currentSyncPoint = syncPoints[syncPointIndex];
22670
- const timeDiff = timePosition - currentSyncPoint.data.millisecondOffset;
22683
+ const timeDiff = timePosition - currentSyncPoint.millisecondOffset;
22671
22684
  let alphaTabTimeDiff;
22672
22685
  if (syncPointIndex + 1 < syncPoints.length) {
22673
22686
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22674
- const relativeTimeDiff = timeDiff / (nextSyncPoint.data.millisecondOffset - currentSyncPoint.data.millisecondOffset);
22675
- alphaTabTimeDiff = (nextSyncPoint.time - currentSyncPoint.time) * relativeTimeDiff;
22687
+ const relativeTimeDiff = timeDiff / (nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset);
22688
+ alphaTabTimeDiff = (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22676
22689
  }
22677
22690
  else {
22678
- const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.data.millisecondOffset);
22679
- alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.time) * relativeTimeDiff;
22691
+ const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.millisecondOffset);
22692
+ alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22680
22693
  }
22681
- return (currentSyncPoint.time + alphaTabTimeDiff) / this.playbackSpeed;
22694
+ return (currentSyncPoint.alphaTabTime + alphaTabTimeDiff) / this.playbackSpeed;
22682
22695
  }
22683
22696
  mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
22684
22697
  const mainState = this._mainState;
@@ -22688,25 +22701,27 @@
22688
22701
  }
22689
22702
  timePosition *= this.playbackSpeed;
22690
22703
  let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22691
- if (timePosition < syncPoints[syncPointIndex].time) {
22704
+ if (timePosition < syncPoints[syncPointIndex].alphaTabTime) {
22692
22705
  syncPointIndex = 0;
22693
22706
  }
22694
- while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].time <= timePosition) {
22707
+ while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].alphaTabTime <= timePosition) {
22695
22708
  syncPointIndex++;
22696
22709
  }
22710
+ // NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
22711
+ // we ensure that we have a linear increase between two points
22697
22712
  const currentSyncPoint = syncPoints[syncPointIndex];
22698
- const alphaTabTimeDiff = timePosition - currentSyncPoint.time;
22713
+ const alphaTabTimeDiff = timePosition - currentSyncPoint.alphaTabTime;
22699
22714
  let backingTrackPos;
22700
22715
  if (syncPointIndex + 1 < syncPoints.length) {
22701
22716
  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;
22717
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime);
22718
+ const backingTrackDiff = nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset;
22719
+ backingTrackPos = currentSyncPoint.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22705
22720
  }
22706
22721
  else {
22707
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.time);
22708
- const frameDiff = backingTrackLength - currentSyncPoint.data.millisecondOffset;
22709
- backingTrackPos = currentSyncPoint.data.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22722
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.alphaTabTime);
22723
+ const frameDiff = backingTrackLength - currentSyncPoint.millisecondOffset;
22724
+ backingTrackPos = currentSyncPoint.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22710
22725
  }
22711
22726
  return backingTrackPos;
22712
22727
  }
@@ -28588,7 +28603,9 @@
28588
28603
  hasSamplesForPercussion(key) {
28589
28604
  return this.synthesizer.hasSamplesForPercussion(key);
28590
28605
  }
28591
- loadBackingTrack(_score, _syncPoints) {
28606
+ loadBackingTrack(_score) {
28607
+ }
28608
+ updateSyncPoints(_syncPoints) {
28592
28609
  }
28593
28610
  }
28594
28611
  /**
@@ -32485,15 +32502,9 @@
32485
32502
  return null;
32486
32503
  }
32487
32504
  const o = new Map();
32488
- o.set("padding", obj.padding);
32489
32505
  return o;
32490
32506
  }
32491
32507
  static setProperty(obj, property, v) {
32492
- switch (property) {
32493
- case "padding":
32494
- obj.padding = v;
32495
- return true;
32496
- }
32497
32508
  return false;
32498
32509
  }
32499
32510
  }
@@ -34956,7 +34967,13 @@
34956
34967
  * Gets the list of tempo changes within the tick lookup.
34957
34968
  */
34958
34969
  this.tempoChanges = [];
34970
+ /**
34971
+ * The first beat in the bar.
34972
+ */
34959
34973
  this.firstBeat = null;
34974
+ /**
34975
+ * The last beat in the bar.
34976
+ */
34960
34977
  this.lastBeat = null;
34961
34978
  /**
34962
34979
  * Gets or sets the {@link MasterBarTickLookup} of the next masterbar in the {@link Score}
@@ -35370,14 +35387,13 @@
35370
35387
  constructor() {
35371
35388
  this._currentMasterBar = null;
35372
35389
  /**
35373
- * Gets a dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35390
+ * A dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35374
35391
  * This lookup only contains the first time a MasterBar is played. For a whole sequence of the song refer to {@link MasterBars}.
35375
35392
  * @internal
35376
35393
  */
35377
35394
  this.masterBarLookup = new Map();
35378
35395
  /**
35379
- * Gets a list of all {@link MasterBarTickLookup} sorted by time.
35380
- * @internal
35396
+ * A list of all {@link MasterBarTickLookup} sorted by time.
35381
35397
  */
35382
35398
  this.masterBars = [];
35383
35399
  /**
@@ -35751,6 +35767,17 @@
35751
35767
  }
35752
35768
  }
35753
35769
 
35770
+ /**
35771
+ * Rerpresents a point to sync the alphaTab time axis with an external backing track.
35772
+ */
35773
+ class BackingTrackSyncPoint {
35774
+ constructor(tick, data) {
35775
+ this.tick = 0;
35776
+ this.tick = tick;
35777
+ this.data = data;
35778
+ }
35779
+ }
35780
+
35754
35781
  class MidiNoteDuration {
35755
35782
  constructor() {
35756
35783
  this.noteOnly = 0;
@@ -35820,62 +35847,22 @@
35820
35847
  this.generateTrack(track);
35821
35848
  }
35822
35849
  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
- }
35850
+ this.syncPoints = [];
35851
+ MidiFileGenerator.playThroughSong(this._score, this.syncPoints, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
35852
+ this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35853
+ }, (index, currentTick, currentTempo) => {
35854
+ for (const track of this._score.tracks) {
35855
+ for (const staff of track.staves) {
35856
+ if (index < staff.bars.length) {
35857
+ this.generateBar(staff.bars[index], currentTick, currentTempo);
35846
35858
  }
35847
35859
  }
35848
- if (bar.tempoAutomations.length > 0) {
35849
- currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35850
- }
35851
35860
  }
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));
35861
+ }, endTick => {
35862
+ for (const track of this._score.tracks) {
35863
+ this._handler.finishTrack(track.index, endTick);
35874
35864
  }
35875
- }
35876
- for (const track of this._score.tracks) {
35877
- this._handler.finishTrack(track.index, controller.currentTick);
35878
- }
35865
+ });
35879
35866
  Logger.debug('Midi', 'Midi generation done');
35880
35867
  }
35881
35868
  generateTrack(track) {
@@ -35920,6 +35907,81 @@
35920
35907
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
35921
35908
  this.addProgramChange(track, 0, channel, playbackInfo.program);
35922
35909
  }
35910
+ /**
35911
+ * Generates the sync points for the given score without re-generating the midi itself.
35912
+ * @remarks
35913
+ * Use this method if a re-generation of the sync points after modification is required.
35914
+ * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
35915
+ * need to be considered for synchronization.
35916
+ * @param score The song for which to regenerate the sync points.
35917
+ * @returns The generated sync points for usage in the backing track playback.
35918
+ */
35919
+ static generateSyncPoints(score) {
35920
+ const syncPoints = [];
35921
+ MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
35922
+ }, (_barIndex, _currentTick, _currentTempo) => {
35923
+ }, _endTick => {
35924
+ });
35925
+ return syncPoints;
35926
+ }
35927
+ static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
35928
+ const controller = new MidiPlaybackController(score);
35929
+ let currentTempo = score.tempo;
35930
+ let previousMasterBar = null;
35931
+ // store the previous played bar for repeats
35932
+ const barOccurence = new Map();
35933
+ while (!controller.finished) {
35934
+ const index = controller.index;
35935
+ const bar = score.masterBars[index];
35936
+ const currentTick = controller.currentTick;
35937
+ controller.processCurrent();
35938
+ if (controller.shouldPlay) {
35939
+ let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35940
+ occurence++;
35941
+ barOccurence.set(index, occurence);
35942
+ generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35943
+ const barSyncPoints = bar.syncPoints;
35944
+ if (barSyncPoints) {
35945
+ for (const syncPoint of barSyncPoints) {
35946
+ if (syncPoint.syncPointValue.barOccurence === occurence) {
35947
+ const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
35948
+ syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
35949
+ }
35950
+ }
35951
+ }
35952
+ if (bar.tempoAutomations.length > 0) {
35953
+ currentTempo = bar.tempoAutomations[0].value;
35954
+ }
35955
+ generateTracks(index, currentTick, currentTempo);
35956
+ if (bar.tempoAutomations.length > 0) {
35957
+ currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35958
+ }
35959
+ }
35960
+ controller.moveNext();
35961
+ previousMasterBar = bar;
35962
+ }
35963
+ // here we interpolate the sync point which marks the end of the sync.
35964
+ // Sync points define new tempos at certain positions.
35965
+ // looking from the last sync point to the end we do not assume the end where the audio ends,
35966
+ // but where it ends according to the BPM and the remaining ticks.
35967
+ if (syncPoints.length > 0) {
35968
+ const lastSyncPoint = syncPoints[syncPoints.length - 1];
35969
+ const remainingTicks = controller.currentTick - lastSyncPoint.tick;
35970
+ if (remainingTicks > 0) {
35971
+ const syncPointData = new SyncPointData();
35972
+ // last occurence of the last bar
35973
+ syncPointData.barOccurence = barOccurence.get(score.masterBars.length - 1);
35974
+ // same tempo as last point
35975
+ syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35976
+ // interpolated end from last syncPoint
35977
+ syncPointData.millisecondOffset =
35978
+ lastSyncPoint.data.millisecondOffset +
35979
+ MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35980
+ syncPoints.push(new BackingTrackSyncPoint(controller.currentTick, syncPointData));
35981
+ }
35982
+ }
35983
+ finish(controller.currentTick);
35984
+ }
35923
35985
  static toChannelShort(data) {
35924
35986
  const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
35925
35987
  return Math.max(value, -1) + 1;
@@ -38256,21 +38318,23 @@
38256
38318
  this._backingTrackOutput.seekTo(this.sequencer.mainTimePositionToBackingTrack(timePosition, this._backingTrackOutput.backingTrackDuration));
38257
38319
  }
38258
38320
  }
38259
- loadBackingTrack(score, syncPoints) {
38321
+ loadBackingTrack(score) {
38260
38322
  const backingTrackInfo = score.backingTrack;
38261
38323
  if (backingTrackInfo) {
38262
38324
  this._backingTrackOutput.loadBackingTrack(backingTrackInfo);
38263
- this.sequencer.mainUpdateSyncPoints(syncPoints);
38264
38325
  this.timePosition = 0;
38265
38326
  }
38266
38327
  }
38328
+ updateSyncPoints(syncPoints) {
38329
+ this.sequencer.mainUpdateSyncPoints(syncPoints);
38330
+ this.tickPosition = this.tickPosition;
38331
+ }
38267
38332
  }
38268
38333
 
38269
38334
  class ExternalMediaSynthOutput {
38270
38335
  constructor() {
38271
38336
  // fake rate
38272
38337
  this.sampleRate = 44100;
38273
- this._padding = 0;
38274
38338
  this._seekPosition = 0;
38275
38339
  this.ready = new EventEmitter();
38276
38340
  this.samplesPlayed = new EventEmitterOfT();
@@ -38313,20 +38377,19 @@
38313
38377
  seekTo(time) {
38314
38378
  const handler = this.handler;
38315
38379
  if (handler) {
38316
- handler.seekTo(time - this._padding);
38380
+ handler.seekTo(time);
38317
38381
  }
38318
38382
  else {
38319
- this._seekPosition = time - this._padding;
38383
+ this._seekPosition = time;
38320
38384
  }
38321
38385
  }
38322
- loadBackingTrack(backingTrack) {
38323
- this._padding = backingTrack.padding;
38386
+ loadBackingTrack(_backingTrack) {
38324
38387
  }
38325
38388
  open(_bufferTimeInMilliseconds) {
38326
38389
  this.ready.trigger();
38327
38390
  }
38328
38391
  updatePosition(currentTime) {
38329
- this.timeUpdate.trigger(currentTime + this._padding);
38392
+ this.timeUpdate.trigger(currentTime);
38330
38393
  }
38331
38394
  play() {
38332
38395
  this.handler?.play();
@@ -38591,9 +38654,14 @@
38591
38654
  this._instance.loadMidiFile(midi);
38592
38655
  }
38593
38656
  }
38594
- loadBackingTrack(score, syncPoints) {
38657
+ loadBackingTrack(score) {
38595
38658
  if (this._instance) {
38596
- this._instance.loadBackingTrack(score, syncPoints);
38659
+ this._instance.loadBackingTrack(score);
38660
+ }
38661
+ }
38662
+ updateSyncPoints(syncPoints) {
38663
+ if (this._instance) {
38664
+ this._instance.updateSyncPoints(syncPoints);
38597
38665
  }
38598
38666
  }
38599
38667
  applyTranspositionPitches(transpositionPitches) {
@@ -39922,7 +39990,7 @@
39922
39990
  }
39923
39991
  appendRenderResult(result, isLast) {
39924
39992
  // resizing the canvas and wrapper elements at the end is enough
39925
- // it avoids flickering on resizes and re-renders.
39993
+ // it avoids flickering on resizes and re-renders.
39926
39994
  // the individual partials are anyhow sized correctly
39927
39995
  if (isLast) {
39928
39996
  this.canvasElement.width = result.totalWidth;
@@ -40684,9 +40752,23 @@
40684
40752
  this.onMidiLoad(midiFile);
40685
40753
  const player = this._player;
40686
40754
  player.loadMidiFile(midiFile);
40687
- player.loadBackingTrack(score, generator.syncPoints);
40755
+ player.loadBackingTrack(score);
40756
+ player.updateSyncPoints(generator.syncPoints);
40688
40757
  player.applyTranspositionPitches(generator.transpositionPitches);
40689
40758
  }
40759
+ /**
40760
+ * Triggers an update of the sync points for the current score after modification within the data model
40761
+ * @category Methods - Player
40762
+ * @since 1.6.0
40763
+ */
40764
+ updateSyncPoints() {
40765
+ if (!this.score) {
40766
+ return;
40767
+ }
40768
+ const score = this.score;
40769
+ const player = this._player;
40770
+ player.updateSyncPoints(MidiFileGenerator.generateSyncPoints(score));
40771
+ }
40690
40772
  /**
40691
40773
  * Changes the volume of the given tracks.
40692
40774
  * @param tracks The tracks for which the volume should be changed.
@@ -43354,6 +43436,8 @@
43354
43436
  }
43355
43437
  loadBackingTrack(_score) {
43356
43438
  }
43439
+ updateSyncPoints(_syncPoints) {
43440
+ }
43357
43441
  }
43358
43442
 
43359
43443
  /**
@@ -43728,7 +43812,6 @@
43728
43812
  constructor() {
43729
43813
  // fake rate
43730
43814
  this.sampleRate = 44100;
43731
- this._padding = 0;
43732
43815
  this._updateInterval = 0;
43733
43816
  this.ready = new EventEmitter();
43734
43817
  this.samplesPlayed = new EventEmitterOfT();
@@ -43752,13 +43835,12 @@
43752
43835
  this.audioElement.volume = value;
43753
43836
  }
43754
43837
  seekTo(time) {
43755
- this.audioElement.currentTime = time / 1000 - this._padding;
43838
+ this.audioElement.currentTime = time / 1000;
43756
43839
  }
43757
43840
  loadBackingTrack(backingTrack) {
43758
43841
  if (this.audioElement?.src) {
43759
43842
  URL.revokeObjectURL(this.audioElement.src);
43760
43843
  }
43761
- this._padding = backingTrack.padding / 1000;
43762
43844
  const blob = new Blob([backingTrack.rawAudioFile]);
43763
43845
  // https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
43764
43846
  // Step 8. resets the playbackRate, we need to remember and restore it.
@@ -43777,7 +43859,7 @@
43777
43859
  this.ready.trigger();
43778
43860
  }
43779
43861
  updatePosition() {
43780
- const timePos = (this.audioElement.currentTime + this._padding) * 1000;
43862
+ const timePos = this.audioElement.currentTime * 1000;
43781
43863
  this.timeUpdate.trigger(timePos);
43782
43864
  }
43783
43865
  play() {
@@ -43996,6 +44078,7 @@
43996
44078
  webFont.usages--;
43997
44079
  if (webFont.usages <= 0) {
43998
44080
  webFont.element.remove();
44081
+ BrowserUiFacade._registeredWebFonts.delete(webFont.hash);
43999
44082
  }
44000
44083
  }
44001
44084
  createCanvasElement() {
@@ -44152,6 +44235,7 @@
44152
44235
  checker.checkForFontAvailability();
44153
44236
  settings.display.resources.smuflFont = new Font(familyName, Environment.MusicFontSize, FontStyle.Plain, FontWeight.Regular);
44154
44237
  const webFont = {
44238
+ hash,
44155
44239
  element: styleElement,
44156
44240
  fontSuffix,
44157
44241
  usages: 1,
@@ -60863,9 +60947,9 @@
60863
60947
  print(`build date: ${VersionInfo.date}`);
60864
60948
  }
60865
60949
  }
60866
- VersionInfo.version = '1.6.0-alpha.1418';
60867
- VersionInfo.date = '2025-05-21T02:08:19.492Z';
60868
- VersionInfo.commit = '1f6342282295a9544ebc36ddd38d73d220d2caab';
60950
+ VersionInfo.version = '1.6.0-alpha.1420';
60951
+ VersionInfo.date = '2025-05-22T03:03:04.057Z';
60952
+ VersionInfo.commit = '230bdd455a1906e6f334b63bad3b8cf773f890e6';
60869
60953
 
60870
60954
  /**
60871
60955
  * A factory for custom layout engines.