@coderline/alphatab 1.6.0-alpha.1448 → 1.6.0-alpha.1450

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.1448 (develop, build 1448)
2
+ * alphaTab v1.6.0-alpha.1450 (develop, build 1450)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -22887,6 +22887,9 @@ class MidiFileSequencer {
22887
22887
  if (tempoChangeIndex !== state.tempoChangeIndex) {
22888
22888
  state.tempoChangeIndex = tempoChangeIndex;
22889
22889
  state.currentTempo = state.tempoChanges[state.tempoChangeIndex].bpm;
22890
+ if (state.syncPoints.length === 0) {
22891
+ state.syncPointTempo = state.currentTempo;
22892
+ }
22890
22893
  }
22891
22894
  }
22892
22895
  currentUpdateSyncPoints(timePosition) {
@@ -36331,6 +36334,7 @@ class PlayThroughContext {
36331
36334
  this.synthTime = 0;
36332
36335
  this.currentTempo = 0;
36333
36336
  this.automationToSyncPoint = new Map();
36337
+ this.createNewSyncPoints = false;
36334
36338
  }
36335
36339
  }
36336
36340
  /**
@@ -36383,7 +36387,7 @@ class MidiFileGenerator {
36383
36387
  }
36384
36388
  Logger.debug('Midi', 'Begin midi generation');
36385
36389
  this.syncPoints = [];
36386
- MidiFileGenerator.playThroughSong(this._score, this.syncPoints, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
36390
+ MidiFileGenerator.playThroughSong(this._score, this.syncPoints, false, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
36387
36391
  this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
36388
36392
  }, (index, currentTick, currentTempo) => {
36389
36393
  for (const track of this._score.tracks) {
@@ -36449,11 +36453,12 @@ class MidiFileGenerator {
36449
36453
  * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
36450
36454
  * need to be considered for synchronization.
36451
36455
  * @param score The song for which to regenerate the sync points.
36456
+ * @param createNew Whether a new set of sync points should be generated for the sync (start, stop and tempo changes).
36452
36457
  * @returns The generated sync points for usage in the backing track playback.
36453
36458
  */
36454
- static generateSyncPoints(score) {
36459
+ static generateSyncPoints(score, createNew = false) {
36455
36460
  const syncPoints = [];
36456
- MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36461
+ MidiFileGenerator.playThroughSong(score, syncPoints, createNew, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36457
36462
  }, (_barIndex, _currentTick, _currentTempo) => {
36458
36463
  }, _endTick => {
36459
36464
  });
@@ -36464,17 +36469,18 @@ class MidiFileGenerator {
36464
36469
  */
36465
36470
  static buildModifiedTempoLookup(score) {
36466
36471
  const syncPoints = [];
36467
- const context = MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36472
+ const context = MidiFileGenerator.playThroughSong(score, syncPoints, false, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36468
36473
  }, (_barIndex, _currentTick, _currentTempo) => {
36469
36474
  }, _endTick => {
36470
36475
  });
36471
36476
  return context.automationToSyncPoint;
36472
36477
  }
36473
- static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
36478
+ static playThroughSong(score, syncPoints, createNewSyncPoints, generateMasterBar, generateTracks, finish) {
36474
36479
  const controller = new MidiPlaybackController(score);
36475
36480
  const playContext = new PlayThroughContext();
36476
36481
  playContext.currentTempo = score.tempo;
36477
36482
  playContext.syncPoints = syncPoints;
36483
+ playContext.createNewSyncPoints = createNewSyncPoints;
36478
36484
  let previousMasterBar = null;
36479
36485
  // store the previous played bar for repeats
36480
36486
  const barOccurence = new Map();
@@ -36512,14 +36518,24 @@ class MidiFileGenerator {
36512
36518
  // we need to assume some BPM for the last interpolated point.
36513
36519
  // if we have more than just a start point, we keep the BPM before the last manual sync point
36514
36520
  // otherwise we have no customized sync BPM known and keep the synthesizer one.
36515
- backingTrackSyncPoint.syncBpm =
36516
- syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
36521
+ if (playContext.createNewSyncPoints) {
36522
+ backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
36523
+ backingTrackSyncPoint.synthBpm = lastSyncPoint.synthBpm;
36524
+ }
36525
+ else if (syncPoints.length === 1) {
36526
+ backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
36527
+ }
36528
+ else {
36529
+ backingTrackSyncPoint.syncBpm = syncPoints[syncPoints.length - 2].syncBpm;
36530
+ }
36517
36531
  backingTrackSyncPoint.synthTime =
36518
36532
  lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
36519
36533
  backingTrackSyncPoint.syncTime =
36520
36534
  lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
36521
36535
  // update the previous sync point according to the new time
36522
- lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
36536
+ if (!playContext.createNewSyncPoints) {
36537
+ lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
36538
+ }
36523
36539
  syncPoints.push(backingTrackSyncPoint);
36524
36540
  }
36525
36541
  }
@@ -36530,7 +36546,10 @@ class MidiFileGenerator {
36530
36546
  const duration = bar.calculateDuration();
36531
36547
  const barSyncPoints = bar.syncPoints;
36532
36548
  const barStartTick = context.synthTick;
36533
- if (barSyncPoints) {
36549
+ if (context.createNewSyncPoints) {
36550
+ MidiFileGenerator.processBarTimeWithNewSyncPoints(bar, occurence, context);
36551
+ }
36552
+ else if (barSyncPoints) {
36534
36553
  MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
36535
36554
  }
36536
36555
  else {
@@ -36544,6 +36563,44 @@ class MidiFileGenerator {
36544
36563
  context.synthTick = endTick;
36545
36564
  }
36546
36565
  }
36566
+ static processBarTimeWithNewSyncPoints(bar, occurence, context) {
36567
+ // start marker
36568
+ const barStartTick = context.synthTick;
36569
+ if (bar.index === 0 && occurence === 0) {
36570
+ context.currentTempo = bar.score.tempo;
36571
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36572
+ backingTrackSyncPoint.masterBarIndex = bar.index;
36573
+ backingTrackSyncPoint.masterBarOccurence = occurence;
36574
+ backingTrackSyncPoint.synthTick = barStartTick;
36575
+ backingTrackSyncPoint.synthBpm = context.currentTempo;
36576
+ backingTrackSyncPoint.synthTime = context.synthTime;
36577
+ backingTrackSyncPoint.syncBpm = context.currentTempo;
36578
+ backingTrackSyncPoint.syncTime = context.synthTime;
36579
+ context.syncPoints.push(backingTrackSyncPoint);
36580
+ }
36581
+ // walk tempo changes and create points
36582
+ const duration = bar.calculateDuration();
36583
+ for (const change of bar.tempoAutomations) {
36584
+ const absoluteTick = barStartTick + change.ratioPosition * duration;
36585
+ const tickOffset = absoluteTick - context.synthTick;
36586
+ if (tickOffset > 0) {
36587
+ context.synthTick = absoluteTick;
36588
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36589
+ }
36590
+ if (change.value !== context.currentTempo) {
36591
+ context.currentTempo = change.value;
36592
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36593
+ backingTrackSyncPoint.masterBarIndex = bar.index;
36594
+ backingTrackSyncPoint.masterBarOccurence = occurence;
36595
+ backingTrackSyncPoint.synthTick = absoluteTick;
36596
+ backingTrackSyncPoint.synthBpm = context.currentTempo;
36597
+ backingTrackSyncPoint.synthTime = context.synthTime;
36598
+ backingTrackSyncPoint.syncBpm = context.currentTempo;
36599
+ backingTrackSyncPoint.syncTime = context.synthTime;
36600
+ context.syncPoints.push(backingTrackSyncPoint);
36601
+ }
36602
+ }
36603
+ }
36547
36604
  static processBarTimeWithSyncPoints(bar, occurence, context) {
36548
36605
  const barStartTick = context.synthTick;
36549
36606
  const duration = bar.calculateDuration();
@@ -42881,7 +42938,12 @@ class AlphaTabApiBase {
42881
42938
  * @remarks
42882
42939
  * This will not export or use any backing track media but will always use the synthesizer to generate the output.
42883
42940
  * This method works with any PlayerMode active but changing the mode during export can lead to unexpected side effects.
42941
+ *
42942
+ * See [Audio Export](https://www.alphatab.net/docs/guides/audio-export) for further guidance how to use this feature.
42943
+ *
42884
42944
  * @param options The export options.
42945
+ * @category Methods - Player
42946
+ * @since 1.6.0
42885
42947
  * @returns An exporter instance to export the audio in a streaming fashion.
42886
42948
  */
42887
42949
  async exportAudio(options) {
@@ -61727,9 +61789,9 @@ class VersionInfo {
61727
61789
  print(`build date: ${VersionInfo.date}`);
61728
61790
  }
61729
61791
  }
61730
- VersionInfo.version = '1.6.0-alpha.1448';
61731
- VersionInfo.date = '2025-06-14T22:07:33.833Z';
61732
- VersionInfo.commit = 'bfeddfaced057b74c2fa71fa58aa407467dd7460';
61792
+ VersionInfo.version = '1.6.0-alpha.1450';
61793
+ VersionInfo.date = '2025-06-15T01:20:26.410Z';
61794
+ VersionInfo.commit = 'a7639d9604a6fcdedff2883864b1008daae5cc64';
61733
61795
 
61734
61796
  /**
61735
61797
  * A factory for custom layout engines.
@@ -2965,7 +2965,12 @@ export declare class AlphaTabApiBase<TSettings> {
2965
2965
  * @remarks
2966
2966
  * This will not export or use any backing track media but will always use the synthesizer to generate the output.
2967
2967
  * This method works with any PlayerMode active but changing the mode during export can lead to unexpected side effects.
2968
+ *
2969
+ * See [Audio Export](https://www.alphatab.net/docs/guides/audio-export) for further guidance how to use this feature.
2970
+ *
2968
2971
  * @param options The export options.
2972
+ * @category Methods - Player
2973
+ * @since 1.6.0
2969
2974
  * @returns An exporter instance to export the audio in a streaming fashion.
2970
2975
  */
2971
2976
  exportAudio(options: AudioExportOptions): Promise<IAudioExporter>;
@@ -9628,12 +9633,14 @@ declare class MidiFileGenerator {
9628
9633
  * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
9629
9634
  * need to be considered for synchronization.
9630
9635
  * @param score The song for which to regenerate the sync points.
9636
+ * @param createNew Whether a new set of sync points should be generated for the sync (start, stop and tempo changes).
9631
9637
  * @returns The generated sync points for usage in the backing track playback.
9632
9638
  */
9633
- static generateSyncPoints(score: Score): BackingTrackSyncPoint[];
9639
+ static generateSyncPoints(score: Score, createNew?: boolean): BackingTrackSyncPoint[];
9634
9640
  /* Excluded from this release type: buildModifiedTempoLookup */
9635
9641
  private static playThroughSong;
9636
9642
  private static processBarTime;
9643
+ private static processBarTimeWithNewSyncPoints;
9637
9644
  private static processBarTimeWithSyncPoints;
9638
9645
  private static processBarTimeNoSyncPoints;
9639
9646
  private static toChannelShort;
package/dist/alphaTab.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.6.0-alpha.1448 (develop, build 1448)
2
+ * alphaTab v1.6.0-alpha.1450 (develop, build 1450)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -22893,6 +22893,9 @@
22893
22893
  if (tempoChangeIndex !== state.tempoChangeIndex) {
22894
22894
  state.tempoChangeIndex = tempoChangeIndex;
22895
22895
  state.currentTempo = state.tempoChanges[state.tempoChangeIndex].bpm;
22896
+ if (state.syncPoints.length === 0) {
22897
+ state.syncPointTempo = state.currentTempo;
22898
+ }
22896
22899
  }
22897
22900
  }
22898
22901
  currentUpdateSyncPoints(timePosition) {
@@ -36337,6 +36340,7 @@
36337
36340
  this.synthTime = 0;
36338
36341
  this.currentTempo = 0;
36339
36342
  this.automationToSyncPoint = new Map();
36343
+ this.createNewSyncPoints = false;
36340
36344
  }
36341
36345
  }
36342
36346
  /**
@@ -36389,7 +36393,7 @@
36389
36393
  }
36390
36394
  Logger.debug('Midi', 'Begin midi generation');
36391
36395
  this.syncPoints = [];
36392
- MidiFileGenerator.playThroughSong(this._score, this.syncPoints, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
36396
+ MidiFileGenerator.playThroughSong(this._score, this.syncPoints, false, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
36393
36397
  this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
36394
36398
  }, (index, currentTick, currentTempo) => {
36395
36399
  for (const track of this._score.tracks) {
@@ -36455,11 +36459,12 @@
36455
36459
  * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
36456
36460
  * need to be considered for synchronization.
36457
36461
  * @param score The song for which to regenerate the sync points.
36462
+ * @param createNew Whether a new set of sync points should be generated for the sync (start, stop and tempo changes).
36458
36463
  * @returns The generated sync points for usage in the backing track playback.
36459
36464
  */
36460
- static generateSyncPoints(score) {
36465
+ static generateSyncPoints(score, createNew = false) {
36461
36466
  const syncPoints = [];
36462
- MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36467
+ MidiFileGenerator.playThroughSong(score, syncPoints, createNew, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36463
36468
  }, (_barIndex, _currentTick, _currentTempo) => {
36464
36469
  }, _endTick => {
36465
36470
  });
@@ -36470,17 +36475,18 @@
36470
36475
  */
36471
36476
  static buildModifiedTempoLookup(score) {
36472
36477
  const syncPoints = [];
36473
- const context = MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36478
+ const context = MidiFileGenerator.playThroughSong(score, syncPoints, false, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
36474
36479
  }, (_barIndex, _currentTick, _currentTempo) => {
36475
36480
  }, _endTick => {
36476
36481
  });
36477
36482
  return context.automationToSyncPoint;
36478
36483
  }
36479
- static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
36484
+ static playThroughSong(score, syncPoints, createNewSyncPoints, generateMasterBar, generateTracks, finish) {
36480
36485
  const controller = new MidiPlaybackController(score);
36481
36486
  const playContext = new PlayThroughContext();
36482
36487
  playContext.currentTempo = score.tempo;
36483
36488
  playContext.syncPoints = syncPoints;
36489
+ playContext.createNewSyncPoints = createNewSyncPoints;
36484
36490
  let previousMasterBar = null;
36485
36491
  // store the previous played bar for repeats
36486
36492
  const barOccurence = new Map();
@@ -36518,14 +36524,24 @@
36518
36524
  // we need to assume some BPM for the last interpolated point.
36519
36525
  // if we have more than just a start point, we keep the BPM before the last manual sync point
36520
36526
  // otherwise we have no customized sync BPM known and keep the synthesizer one.
36521
- backingTrackSyncPoint.syncBpm =
36522
- syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
36527
+ if (playContext.createNewSyncPoints) {
36528
+ backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
36529
+ backingTrackSyncPoint.synthBpm = lastSyncPoint.synthBpm;
36530
+ }
36531
+ else if (syncPoints.length === 1) {
36532
+ backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
36533
+ }
36534
+ else {
36535
+ backingTrackSyncPoint.syncBpm = syncPoints[syncPoints.length - 2].syncBpm;
36536
+ }
36523
36537
  backingTrackSyncPoint.synthTime =
36524
36538
  lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
36525
36539
  backingTrackSyncPoint.syncTime =
36526
36540
  lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
36527
36541
  // update the previous sync point according to the new time
36528
- lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
36542
+ if (!playContext.createNewSyncPoints) {
36543
+ lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
36544
+ }
36529
36545
  syncPoints.push(backingTrackSyncPoint);
36530
36546
  }
36531
36547
  }
@@ -36536,7 +36552,10 @@
36536
36552
  const duration = bar.calculateDuration();
36537
36553
  const barSyncPoints = bar.syncPoints;
36538
36554
  const barStartTick = context.synthTick;
36539
- if (barSyncPoints) {
36555
+ if (context.createNewSyncPoints) {
36556
+ MidiFileGenerator.processBarTimeWithNewSyncPoints(bar, occurence, context);
36557
+ }
36558
+ else if (barSyncPoints) {
36540
36559
  MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
36541
36560
  }
36542
36561
  else {
@@ -36550,6 +36569,44 @@
36550
36569
  context.synthTick = endTick;
36551
36570
  }
36552
36571
  }
36572
+ static processBarTimeWithNewSyncPoints(bar, occurence, context) {
36573
+ // start marker
36574
+ const barStartTick = context.synthTick;
36575
+ if (bar.index === 0 && occurence === 0) {
36576
+ context.currentTempo = bar.score.tempo;
36577
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36578
+ backingTrackSyncPoint.masterBarIndex = bar.index;
36579
+ backingTrackSyncPoint.masterBarOccurence = occurence;
36580
+ backingTrackSyncPoint.synthTick = barStartTick;
36581
+ backingTrackSyncPoint.synthBpm = context.currentTempo;
36582
+ backingTrackSyncPoint.synthTime = context.synthTime;
36583
+ backingTrackSyncPoint.syncBpm = context.currentTempo;
36584
+ backingTrackSyncPoint.syncTime = context.synthTime;
36585
+ context.syncPoints.push(backingTrackSyncPoint);
36586
+ }
36587
+ // walk tempo changes and create points
36588
+ const duration = bar.calculateDuration();
36589
+ for (const change of bar.tempoAutomations) {
36590
+ const absoluteTick = barStartTick + change.ratioPosition * duration;
36591
+ const tickOffset = absoluteTick - context.synthTick;
36592
+ if (tickOffset > 0) {
36593
+ context.synthTick = absoluteTick;
36594
+ context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
36595
+ }
36596
+ if (change.value !== context.currentTempo) {
36597
+ context.currentTempo = change.value;
36598
+ const backingTrackSyncPoint = new BackingTrackSyncPoint();
36599
+ backingTrackSyncPoint.masterBarIndex = bar.index;
36600
+ backingTrackSyncPoint.masterBarOccurence = occurence;
36601
+ backingTrackSyncPoint.synthTick = absoluteTick;
36602
+ backingTrackSyncPoint.synthBpm = context.currentTempo;
36603
+ backingTrackSyncPoint.synthTime = context.synthTime;
36604
+ backingTrackSyncPoint.syncBpm = context.currentTempo;
36605
+ backingTrackSyncPoint.syncTime = context.synthTime;
36606
+ context.syncPoints.push(backingTrackSyncPoint);
36607
+ }
36608
+ }
36609
+ }
36553
36610
  static processBarTimeWithSyncPoints(bar, occurence, context) {
36554
36611
  const barStartTick = context.synthTick;
36555
36612
  const duration = bar.calculateDuration();
@@ -42887,7 +42944,12 @@
42887
42944
  * @remarks
42888
42945
  * This will not export or use any backing track media but will always use the synthesizer to generate the output.
42889
42946
  * This method works with any PlayerMode active but changing the mode during export can lead to unexpected side effects.
42947
+ *
42948
+ * See [Audio Export](https://www.alphatab.net/docs/guides/audio-export) for further guidance how to use this feature.
42949
+ *
42890
42950
  * @param options The export options.
42951
+ * @category Methods - Player
42952
+ * @since 1.6.0
42891
42953
  * @returns An exporter instance to export the audio in a streaming fashion.
42892
42954
  */
42893
42955
  async exportAudio(options) {
@@ -61733,9 +61795,9 @@
61733
61795
  print(`build date: ${VersionInfo.date}`);
61734
61796
  }
61735
61797
  }
61736
- VersionInfo.version = '1.6.0-alpha.1448';
61737
- VersionInfo.date = '2025-06-14T22:07:33.833Z';
61738
- VersionInfo.commit = 'bfeddfaced057b74c2fa71fa58aa407467dd7460';
61798
+ VersionInfo.version = '1.6.0-alpha.1450';
61799
+ VersionInfo.date = '2025-06-15T01:20:26.410Z';
61800
+ VersionInfo.commit = 'a7639d9604a6fcdedff2883864b1008daae5cc64';
61739
61801
 
61740
61802
  /**
61741
61803
  * A factory for custom layout engines.