@coderline/alphatab 1.6.0-alpha.1416 → 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.1416 (develop, build 1416)
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
  /**
@@ -29076,7 +29093,7 @@
29076
29093
  }
29077
29094
  switch (typeof v) {
29078
29095
  case 'undefined':
29079
- return null;
29096
+ return undefined;
29080
29097
  case 'object': {
29081
29098
  const m = v;
29082
29099
  const families = m.get('families');
@@ -29156,10 +29173,13 @@
29156
29173
  return Font.withFamilyList(families, fontSize, fontStyle, fontWeight);
29157
29174
  }
29158
29175
  default:
29159
- return null;
29176
+ return undefined;
29160
29177
  }
29161
29178
  }
29162
29179
  static toJson(font) {
29180
+ if (!font) {
29181
+ return undefined;
29182
+ }
29163
29183
  const o = new Map();
29164
29184
  o.set('families', font.families);
29165
29185
  o.set('size', font.size);
@@ -30166,6 +30186,14 @@
30166
30186
  /*@target web*/
30167
30187
  o.set("fontdirectory", obj.fontDirectory);
30168
30188
  /*@target web*/
30189
+ if (obj.smuflFontSources !== null) {
30190
+ const m = new Map();
30191
+ o.set("smuflfontsources", m);
30192
+ for (const [k, v] of obj.smuflFontSources) {
30193
+ m.set(k.toString(), v);
30194
+ }
30195
+ }
30196
+ /*@target web*/
30169
30197
  o.set("file", obj.file);
30170
30198
  /*@target web*/
30171
30199
  o.set("tex", obj.tex);
@@ -30189,6 +30217,13 @@
30189
30217
  obj.fontDirectory = v;
30190
30218
  return true;
30191
30219
  /*@target web*/
30220
+ case "smuflfontsources":
30221
+ obj.smuflFontSources = new Map();
30222
+ JsonHelper.forEach(v, (v, k) => {
30223
+ obj.smuflFontSources.set(JsonHelper.parseEnum(k, exports.FontFileFormat), v);
30224
+ });
30225
+ return true;
30226
+ /*@target web*/
30192
30227
  case "file":
30193
30228
  obj.file = v;
30194
30229
  return true;
@@ -30232,6 +30267,7 @@
30232
30267
  return null;
30233
30268
  }
30234
30269
  const o = new Map();
30270
+ o.set("smuflfont", Font.toJson(obj.smuflFont));
30235
30271
  o.set("copyrightfont", Font.toJson(obj.copyrightFont));
30236
30272
  o.set("titlefont", Font.toJson(obj.titleFont));
30237
30273
  o.set("subtitlefont", Font.toJson(obj.subTitleFont));
@@ -30258,6 +30294,9 @@
30258
30294
  }
30259
30295
  static setProperty(obj, property, v) {
30260
30296
  switch (property) {
30297
+ case "smuflfont":
30298
+ obj.smuflFont = Font.fromJson(v);
30299
+ return true;
30261
30300
  case "copyrightfont":
30262
30301
  obj.copyrightFont = Font.fromJson(v);
30263
30302
  return true;
@@ -32463,15 +32502,9 @@
32463
32502
  return null;
32464
32503
  }
32465
32504
  const o = new Map();
32466
- o.set("padding", obj.padding);
32467
32505
  return o;
32468
32506
  }
32469
32507
  static setProperty(obj, property, v) {
32470
- switch (property) {
32471
- case "padding":
32472
- obj.padding = v;
32473
- return true;
32474
- }
32475
32508
  return false;
32476
32509
  }
32477
32510
  }
@@ -34235,15 +34268,6 @@
34235
34268
  this._color = new Color(0, 0, 0, 0xff);
34236
34269
  this._font = new Font('Arial', 10, FontStyle.Plain);
34237
34270
  this._lineWidth = 0;
34238
- const fontElement = document.createElement('span');
34239
- fontElement.classList.add('at');
34240
- document.body.appendChild(fontElement);
34241
- const style = window.getComputedStyle(fontElement);
34242
- let family = style.fontFamily;
34243
- if (family.startsWith('"') || family.startsWith("'")) {
34244
- family = family.substr(1, family.length - 2);
34245
- }
34246
- this._musicFont = new Font(family, Number.parseFloat(style.fontSize), FontStyle.Plain);
34247
34271
  this._measureCanvas = document.createElement('canvas');
34248
34272
  this._measureCanvas.width = 10;
34249
34273
  this._measureCanvas.height = 10;
@@ -34258,6 +34282,7 @@
34258
34282
  return null;
34259
34283
  }
34260
34284
  beginRender(width, height) {
34285
+ this._musicFont = this.settings.display.resources.smuflFont;
34261
34286
  const scale = this.settings.display.scale;
34262
34287
  this._canvas = document.createElement('canvas');
34263
34288
  this._canvas.width = (width * Environment.HighDpiFactor) | 0;
@@ -34942,7 +34967,13 @@
34942
34967
  * Gets the list of tempo changes within the tick lookup.
34943
34968
  */
34944
34969
  this.tempoChanges = [];
34970
+ /**
34971
+ * The first beat in the bar.
34972
+ */
34945
34973
  this.firstBeat = null;
34974
+ /**
34975
+ * The last beat in the bar.
34976
+ */
34946
34977
  this.lastBeat = null;
34947
34978
  /**
34948
34979
  * Gets or sets the {@link MasterBarTickLookup} of the next masterbar in the {@link Score}
@@ -35356,14 +35387,13 @@
35356
35387
  constructor() {
35357
35388
  this._currentMasterBar = null;
35358
35389
  /**
35359
- * 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}.
35360
35391
  * This lookup only contains the first time a MasterBar is played. For a whole sequence of the song refer to {@link MasterBars}.
35361
35392
  * @internal
35362
35393
  */
35363
35394
  this.masterBarLookup = new Map();
35364
35395
  /**
35365
- * Gets a list of all {@link MasterBarTickLookup} sorted by time.
35366
- * @internal
35396
+ * A list of all {@link MasterBarTickLookup} sorted by time.
35367
35397
  */
35368
35398
  this.masterBars = [];
35369
35399
  /**
@@ -35737,6 +35767,17 @@
35737
35767
  }
35738
35768
  }
35739
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
+
35740
35781
  class MidiNoteDuration {
35741
35782
  constructor() {
35742
35783
  this.noteOnly = 0;
@@ -35806,62 +35847,22 @@
35806
35847
  this.generateTrack(track);
35807
35848
  }
35808
35849
  Logger.debug('Midi', 'Begin midi generation');
35809
- const controller = new MidiPlaybackController(this._score);
35810
- let previousMasterBar = null;
35811
- let currentTempo = this._score.tempo;
35812
- // store the previous played bar for repeats
35813
- const barOccurence = new Map();
35814
- while (!controller.finished) {
35815
- const index = controller.index;
35816
- const bar = this._score.masterBars[index];
35817
- const currentTick = controller.currentTick;
35818
- controller.processCurrent();
35819
- if (controller.shouldPlay) {
35820
- let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35821
- occurence++;
35822
- barOccurence.set(index, occurence);
35823
- this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35824
- if (bar.tempoAutomations.length > 0) {
35825
- currentTempo = bar.tempoAutomations[0].value;
35826
- }
35827
- for (const track of this._score.tracks) {
35828
- for (const staff of track.staves) {
35829
- if (index < staff.bars.length) {
35830
- this.generateBar(staff.bars[index], currentTick, currentTempo);
35831
- }
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);
35832
35858
  }
35833
35859
  }
35834
- if (bar.tempoAutomations.length > 0) {
35835
- currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35836
- }
35837
35860
  }
35838
- controller.moveNext();
35839
- previousMasterBar = bar;
35840
- }
35841
- // here we interpolate the sync point which marks the end of the sync.
35842
- // Sync points define new tempos at certain positions.
35843
- // looking from the last sync point to the end we do not assume the end where the audio ends,
35844
- // but where it ends according to the BPM and the remaining ticks.
35845
- if (this.syncPoints.length > 0) {
35846
- const lastSyncPoint = this.syncPoints[this.syncPoints.length - 1];
35847
- const endTick = controller.currentTick;
35848
- const remainingTicks = endTick - lastSyncPoint.tick;
35849
- if (remainingTicks > 0) {
35850
- const syncPointData = new SyncPointData();
35851
- // last occurence of the last bar
35852
- syncPointData.barOccurence = barOccurence.get(this._score.masterBars.length - 1);
35853
- // same tempo as last point
35854
- syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35855
- // interpolated end from last syncPoint
35856
- syncPointData.millisecondOffset =
35857
- lastSyncPoint.data.millisecondOffset +
35858
- MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35859
- this.syncPoints.push(new BackingTrackSyncPoint(endTick, syncPointData));
35861
+ }, endTick => {
35862
+ for (const track of this._score.tracks) {
35863
+ this._handler.finishTrack(track.index, endTick);
35860
35864
  }
35861
- }
35862
- for (const track of this._score.tracks) {
35863
- this._handler.finishTrack(track.index, controller.currentTick);
35864
- }
35865
+ });
35865
35866
  Logger.debug('Midi', 'Midi generation done');
35866
35867
  }
35867
35868
  generateTrack(track) {
@@ -35906,6 +35907,81 @@
35906
35907
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
35907
35908
  this.addProgramChange(track, 0, channel, playbackInfo.program);
35908
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
+ }
35909
35985
  static toChannelShort(data) {
35910
35986
  const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
35911
35987
  return Math.max(value, -1) + 1;
@@ -38242,21 +38318,23 @@
38242
38318
  this._backingTrackOutput.seekTo(this.sequencer.mainTimePositionToBackingTrack(timePosition, this._backingTrackOutput.backingTrackDuration));
38243
38319
  }
38244
38320
  }
38245
- loadBackingTrack(score, syncPoints) {
38321
+ loadBackingTrack(score) {
38246
38322
  const backingTrackInfo = score.backingTrack;
38247
38323
  if (backingTrackInfo) {
38248
38324
  this._backingTrackOutput.loadBackingTrack(backingTrackInfo);
38249
- this.sequencer.mainUpdateSyncPoints(syncPoints);
38250
38325
  this.timePosition = 0;
38251
38326
  }
38252
38327
  }
38328
+ updateSyncPoints(syncPoints) {
38329
+ this.sequencer.mainUpdateSyncPoints(syncPoints);
38330
+ this.tickPosition = this.tickPosition;
38331
+ }
38253
38332
  }
38254
38333
 
38255
38334
  class ExternalMediaSynthOutput {
38256
38335
  constructor() {
38257
38336
  // fake rate
38258
38337
  this.sampleRate = 44100;
38259
- this._padding = 0;
38260
38338
  this._seekPosition = 0;
38261
38339
  this.ready = new EventEmitter();
38262
38340
  this.samplesPlayed = new EventEmitterOfT();
@@ -38299,20 +38377,19 @@
38299
38377
  seekTo(time) {
38300
38378
  const handler = this.handler;
38301
38379
  if (handler) {
38302
- handler.seekTo(time - this._padding);
38380
+ handler.seekTo(time);
38303
38381
  }
38304
38382
  else {
38305
- this._seekPosition = time - this._padding;
38383
+ this._seekPosition = time;
38306
38384
  }
38307
38385
  }
38308
- loadBackingTrack(backingTrack) {
38309
- this._padding = backingTrack.padding;
38386
+ loadBackingTrack(_backingTrack) {
38310
38387
  }
38311
38388
  open(_bufferTimeInMilliseconds) {
38312
38389
  this.ready.trigger();
38313
38390
  }
38314
38391
  updatePosition(currentTime) {
38315
- this.timeUpdate.trigger(currentTime + this._padding);
38392
+ this.timeUpdate.trigger(currentTime);
38316
38393
  }
38317
38394
  play() {
38318
38395
  this.handler?.play();
@@ -38577,9 +38654,14 @@
38577
38654
  this._instance.loadMidiFile(midi);
38578
38655
  }
38579
38656
  }
38580
- loadBackingTrack(score, syncPoints) {
38657
+ loadBackingTrack(score) {
38581
38658
  if (this._instance) {
38582
- this._instance.loadBackingTrack(score, syncPoints);
38659
+ this._instance.loadBackingTrack(score);
38660
+ }
38661
+ }
38662
+ updateSyncPoints(syncPoints) {
38663
+ if (this._instance) {
38664
+ this._instance.updateSyncPoints(syncPoints);
38583
38665
  }
38584
38666
  }
38585
38667
  applyTranspositionPitches(transpositionPitches) {
@@ -39908,7 +39990,7 @@
39908
39990
  }
39909
39991
  appendRenderResult(result, isLast) {
39910
39992
  // resizing the canvas and wrapper elements at the end is enough
39911
- // it avoids flickering on resizes and re-renders.
39993
+ // it avoids flickering on resizes and re-renders.
39912
39994
  // the individual partials are anyhow sized correctly
39913
39995
  if (isLast) {
39914
39996
  this.canvasElement.width = result.totalWidth;
@@ -40670,9 +40752,23 @@
40670
40752
  this.onMidiLoad(midiFile);
40671
40753
  const player = this._player;
40672
40754
  player.loadMidiFile(midiFile);
40673
- player.loadBackingTrack(score, generator.syncPoints);
40755
+ player.loadBackingTrack(score);
40756
+ player.updateSyncPoints(generator.syncPoints);
40674
40757
  player.applyTranspositionPitches(generator.transpositionPitches);
40675
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
+ }
40676
40772
  /**
40677
40773
  * Changes the volume of the given tracks.
40678
40774
  * @param tracks The tracks for which the volume should be changed.
@@ -43340,6 +43436,8 @@
43340
43436
  }
43341
43437
  loadBackingTrack(_score) {
43342
43438
  }
43439
+ updateSyncPoints(_syncPoints) {
43440
+ }
43343
43441
  }
43344
43442
 
43345
43443
  /**
@@ -43714,7 +43812,6 @@
43714
43812
  constructor() {
43715
43813
  // fake rate
43716
43814
  this.sampleRate = 44100;
43717
- this._padding = 0;
43718
43815
  this._updateInterval = 0;
43719
43816
  this.ready = new EventEmitter();
43720
43817
  this.samplesPlayed = new EventEmitterOfT();
@@ -43738,13 +43835,12 @@
43738
43835
  this.audioElement.volume = value;
43739
43836
  }
43740
43837
  seekTo(time) {
43741
- this.audioElement.currentTime = time / 1000 - this._padding;
43838
+ this.audioElement.currentTime = time / 1000;
43742
43839
  }
43743
43840
  loadBackingTrack(backingTrack) {
43744
43841
  if (this.audioElement?.src) {
43745
43842
  URL.revokeObjectURL(this.audioElement.src);
43746
43843
  }
43747
- this._padding = backingTrack.padding / 1000;
43748
43844
  const blob = new Blob([backingTrack.rawAudioFile]);
43749
43845
  // https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
43750
43846
  // Step 8. resets the playbackRate, we need to remember and restore it.
@@ -43763,7 +43859,7 @@
43763
43859
  this.ready.trigger();
43764
43860
  }
43765
43861
  updatePosition() {
43766
- const timePos = (this.audioElement.currentTime + this._padding) * 1000;
43862
+ const timePos = this.audioElement.currentTime * 1000;
43767
43863
  this.timeUpdate.trigger(timePos);
43768
43864
  }
43769
43865
  play() {
@@ -43849,10 +43945,6 @@
43849
43945
  return this.areAllFontsLoaded();
43850
43946
  }
43851
43947
  areAllFontsLoaded() {
43852
- Environment.bravuraFontChecker.checkForFontAvailability();
43853
- if (!Environment.bravuraFontChecker.isFontLoaded) {
43854
- return false;
43855
- }
43856
43948
  let isAnyNotLoaded = false;
43857
43949
  for (const checker of this._fontCheckers.values()) {
43858
43950
  if (!checker.isFontLoaded) {
@@ -43889,7 +43981,6 @@
43889
43981
  rootElement.classList.add('alphaTab');
43890
43982
  this.rootContainer = new HtmlElementContainer(rootElement);
43891
43983
  this.areWorkersSupported = 'Worker' in window;
43892
- Environment.bravuraFontChecker.fontLoaded.on(this.onFontLoaded.bind(this));
43893
43984
  this._intersectionObserver = new IntersectionObserver(this.onElementVisibilityChanged.bind(this), {
43894
43985
  threshold: [0, 0.01, 1]
43895
43986
  });
@@ -43957,7 +44048,7 @@
43957
44048
  this._contents = element.element.innerHTML;
43958
44049
  element.element.innerHTML = '';
43959
44050
  }
43960
- this.createStyleElement(settings);
44051
+ this.createStyleElements(settings);
43961
44052
  this._file = settings.core.file;
43962
44053
  }
43963
44054
  setupFontCheckers(settings) {
@@ -43983,10 +44074,16 @@
43983
44074
  }
43984
44075
  destroy() {
43985
44076
  this.rootContainer.element.innerHTML = '';
44077
+ const webFont = this._webFont;
44078
+ webFont.usages--;
44079
+ if (webFont.usages <= 0) {
44080
+ webFont.element.remove();
44081
+ BrowserUiFacade._registeredWebFonts.delete(webFont.hash);
44082
+ }
43986
44083
  }
43987
44084
  createCanvasElement() {
43988
44085
  const canvasElement = document.createElement('div');
43989
- canvasElement.className = 'at-surface';
44086
+ canvasElement.classList.add('at-surface', `at${this._webFont.fontSuffix}`);
43990
44087
  canvasElement.style.fontSize = '0';
43991
44088
  canvasElement.style.overflow = 'hidden';
43992
44089
  canvasElement.style.lineHeight = '0';
@@ -44084,9 +44181,133 @@
44084
44181
  initialRender();
44085
44182
  }
44086
44183
  }
44087
- createStyleElement(settings) {
44088
- const elementDocument = this._api.container.element.ownerDocument;
44089
- Environment.createStyleElement(elementDocument, settings.core.fontDirectory);
44184
+ createStyleElements(settings) {
44185
+ const root = this._api.container.element.ownerDocument;
44186
+ BrowserUiFacade.createSharedStyleElement(root);
44187
+ // SmuFl Font Specific style
44188
+ const smuflFontSources = settings.core.smuflFontSources ?? CoreSettings.buildDefaultSmuflFontSources(settings.core.fontDirectory);
44189
+ // create a simple unique hash for the font source definition
44190
+ // as data urls might be used we don't want to just use the plain strings.
44191
+ const hash = BrowserUiFacade.cyrb53(smuflFontSources.values());
44192
+ // reuse existing style if available
44193
+ const registeredWebFonts = BrowserUiFacade._registeredWebFonts;
44194
+ if (registeredWebFonts.has(hash)) {
44195
+ const webFont = registeredWebFonts.get(hash);
44196
+ webFont.usages++;
44197
+ webFont.checker.fontLoaded.on(this.onFontLoaded.bind(this));
44198
+ this._webFont = webFont;
44199
+ return;
44200
+ }
44201
+ const fontSuffix = registeredWebFonts.size === 0 ? '' : String(registeredWebFonts.size);
44202
+ const familyName = `alphaTab${fontSuffix}`;
44203
+ const src = Array.from(smuflFontSources.entries())
44204
+ .map(e => `url(${JSON.stringify(e[1])}) format('${BrowserUiFacade.cssFormat(e[0])}')`)
44205
+ .join(',');
44206
+ const css = `
44207
+ @font-face {
44208
+ font-display: block;
44209
+ font-family: '${familyName}';
44210
+ src: ${src};
44211
+ font-weight: normal;
44212
+ font-style: normal;
44213
+ }
44214
+ .at-surface.at${fontSuffix} .at {
44215
+ font-family: '${familyName}';
44216
+ speak: none;
44217
+ font-style: normal;
44218
+ font-weight: normal;
44219
+ font-variant: normal;
44220
+ text-transform: none;
44221
+ line-height: 1;
44222
+ line-height: 1;
44223
+ -webkit-font-smoothing: antialiased;
44224
+ -moz-osx-font-smoothing: grayscale;
44225
+ font-size: ${Environment.MusicFontSize}px;
44226
+ overflow: visible !important;
44227
+ }`;
44228
+ const styleElement = root.createElement('style');
44229
+ styleElement.id = `alphaTabStyle${fontSuffix}`;
44230
+ styleElement.innerHTML = css;
44231
+ root.getElementsByTagName('head').item(0).appendChild(styleElement);
44232
+ const checker = new FontLoadingChecker([familyName]);
44233
+ checker.fontLoaded.on(this.onFontLoaded.bind(this));
44234
+ this._fontCheckers.set(familyName, checker);
44235
+ checker.checkForFontAvailability();
44236
+ settings.display.resources.smuflFont = new Font(familyName, Environment.MusicFontSize, FontStyle.Plain, FontWeight.Regular);
44237
+ const webFont = {
44238
+ hash,
44239
+ element: styleElement,
44240
+ fontSuffix,
44241
+ usages: 1,
44242
+ checker
44243
+ };
44244
+ registeredWebFonts.set(hash, webFont);
44245
+ this._webFont = webFont;
44246
+ }
44247
+ static cssFormat(format) {
44248
+ switch (format) {
44249
+ case exports.FontFileFormat.EmbeddedOpenType:
44250
+ return 'embedded-opentype';
44251
+ case exports.FontFileFormat.Woff:
44252
+ return 'woff';
44253
+ case exports.FontFileFormat.Woff2:
44254
+ return 'woff2';
44255
+ case exports.FontFileFormat.OpenType:
44256
+ return 'opentype';
44257
+ case exports.FontFileFormat.TrueType:
44258
+ return 'truetype';
44259
+ case exports.FontFileFormat.Svg:
44260
+ return 'svg';
44261
+ }
44262
+ }
44263
+ /**
44264
+ * cyrb53 (c) 2018 bryc (github.com/bryc)
44265
+ * License: Public domain (or MIT if needed). Attribution appreciated.
44266
+ * A fast and simple 53-bit string hash function with decent collision resistance.
44267
+ * Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity
44268
+ * @param str
44269
+ * @param seed
44270
+ * @returns
44271
+ */
44272
+ static cyrb53(strings, seed = 0) {
44273
+ let h1 = 0xdeadbeef ^ seed;
44274
+ let h2 = 0x41c6ce57 ^ seed;
44275
+ for (const str of strings) {
44276
+ for (let i = 0; i < str.length; i++) {
44277
+ const ch = str.charCodeAt(i);
44278
+ h1 = Math.imul(h1 ^ ch, 2654435761);
44279
+ h2 = Math.imul(h2 ^ ch, 1597334677);
44280
+ }
44281
+ }
44282
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
44283
+ h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
44284
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
44285
+ h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
44286
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
44287
+ }
44288
+ /**
44289
+ * Creates the default CSS styles used across all alphaTab instances.
44290
+ * @target web
44291
+ * @internal
44292
+ */
44293
+ static createSharedStyleElement(root) {
44294
+ let styleElement = root.getElementById('alphaTabStyle');
44295
+ if (!styleElement) {
44296
+ styleElement = document.createElement('style');
44297
+ styleElement.id = 'alphaTabStyleShared';
44298
+ const css = `
44299
+ .at-surface * {
44300
+ cursor: default;
44301
+ vertical-align: top;
44302
+ overflow: visible;
44303
+ }
44304
+ .at-surface-svg text {
44305
+ dominant-baseline: central;
44306
+ white-space:pre;
44307
+ }`;
44308
+ styleElement.innerHTML = css;
44309
+ document.getElementsByTagName('head').item(0).appendChild(styleElement);
44310
+ }
44090
44311
  }
44091
44312
  parseTracks(tracksData) {
44092
44313
  if (!tracksData) {
@@ -44468,6 +44689,7 @@
44468
44689
  return new BackingTrackPlayer(new AudioElementBackingTrackSynthOutput(), this._api.settings.player.bufferTimeInMilliseconds);
44469
44690
  }
44470
44691
  }
44692
+ BrowserUiFacade._registeredWebFonts = new Map();
44471
44693
 
44472
44694
  /**
44473
44695
  * Represents the progress of any data being loaded.
@@ -60725,9 +60947,9 @@
60725
60947
  print(`build date: ${VersionInfo.date}`);
60726
60948
  }
60727
60949
  }
60728
- VersionInfo.version = '1.6.0-alpha.1416';
60729
- VersionInfo.date = '2025-05-19T16:41:23.634Z';
60730
- VersionInfo.commit = '4aff072e1be5292482c3d77f434dd1555b6cbb8d';
60950
+ VersionInfo.version = '1.6.0-alpha.1420';
60951
+ VersionInfo.date = '2025-05-22T03:03:04.057Z';
60952
+ VersionInfo.commit = '230bdd455a1906e6f334b63bad3b8cf773f890e6';
60731
60953
 
60732
60954
  /**
60733
60955
  * A factory for custom layout engines.
@@ -60759,59 +60981,6 @@
60759
60981
  * @partial
60760
60982
  */
60761
60983
  class Environment {
60762
- /**
60763
- * @target web
60764
- * @internal
60765
- */
60766
- static createStyleElement(elementDocument, fontDirectory) {
60767
- let styleElement = elementDocument.getElementById('alphaTabStyle');
60768
- if (!styleElement) {
60769
- if (!fontDirectory) {
60770
- Logger.error('AlphaTab', 'Font directory could not be detected, cannot create style element');
60771
- return;
60772
- }
60773
- styleElement = elementDocument.createElement('style');
60774
- styleElement.id = 'alphaTabStyle';
60775
- const css = `
60776
- @font-face {
60777
- font-display: block;
60778
- font-family: 'alphaTab';
60779
- src: url('${fontDirectory}Bravura.eot');
60780
- src: url('${fontDirectory}Bravura.eot?#iefix') format('embedded-opentype')
60781
- , url('${fontDirectory}Bravura.woff') format('woff')
60782
- , url('${fontDirectory}Bravura.otf') format('opentype')
60783
- , url('${fontDirectory}Bravura.svg#Bravura') format('svg');
60784
- font-weight: normal;
60785
- font-style: normal;
60786
- }
60787
- .at-surface * {
60788
- cursor: default;
60789
- vertical-align: top;
60790
- overflow: visible;
60791
- }
60792
- .at-surface-svg text {
60793
- dominant-baseline: central;
60794
- white-space:pre;
60795
- }
60796
- .at {
60797
- font-family: 'alphaTab';
60798
- speak: none;
60799
- font-style: normal;
60800
- font-weight: normal;
60801
- font-variant: normal;
60802
- text-transform: none;
60803
- line-height: 1;
60804
- line-height: 1;
60805
- -webkit-font-smoothing: antialiased;
60806
- -moz-osx-font-smoothing: grayscale;
60807
- font-size: ${Environment.MusicFontSize}px;
60808
- overflow: visible !important;
60809
- }`;
60810
- styleElement.innerHTML = css;
60811
- elementDocument.getElementsByTagName('head').item(0).appendChild(styleElement);
60812
- Environment.bravuraFontChecker.checkForFontAvailability();
60813
- }
60814
- }
60815
60984
  /**
60816
60985
  * @target web
60817
60986
  * @internal
@@ -60887,7 +61056,9 @@
60887
61056
  catch (e) {
60888
61057
  }
60889
61058
  // normal browser include as <script>
60890
- if ('document' in Environment.globalThis && document.currentScript) {
61059
+ if ('document' in Environment.globalThis &&
61060
+ document.currentScript &&
61061
+ document.currentScript instanceof HTMLScriptElement) {
60891
61062
  return document.currentScript.src;
60892
61063
  }
60893
61064
  return null;
@@ -61365,11 +61536,6 @@
61365
61536
  * @target web
61366
61537
  */
61367
61538
  Environment.fontDirectory = Environment.detectFontDirectory();
61368
- /**
61369
- * @target web
61370
- * @internal
61371
- */
61372
- Environment.bravuraFontChecker = new FontLoadingChecker(['alphaTab']);
61373
61539
  Environment.renderEngines = Environment.createDefaultRenderEngines();
61374
61540
  /**
61375
61541
  * @internal
@@ -61380,12 +61546,58 @@
61380
61546
  */
61381
61547
  Environment.staveProfiles = Environment.createDefaultStaveProfiles();
61382
61548
 
61549
+ /**
61550
+ * Lists the known file formats for font files.
61551
+ * @target web
61552
+ */
61553
+ exports.FontFileFormat = void 0;
61554
+ (function (FontFileFormat) {
61555
+ /**
61556
+ * .eot
61557
+ */
61558
+ FontFileFormat[FontFileFormat["EmbeddedOpenType"] = 0] = "EmbeddedOpenType";
61559
+ /**
61560
+ * .woff
61561
+ */
61562
+ FontFileFormat[FontFileFormat["Woff"] = 1] = "Woff";
61563
+ /**
61564
+ * .woff2
61565
+ */
61566
+ FontFileFormat[FontFileFormat["Woff2"] = 2] = "Woff2";
61567
+ /**
61568
+ * .otf
61569
+ */
61570
+ FontFileFormat[FontFileFormat["OpenType"] = 3] = "OpenType";
61571
+ /**
61572
+ * .ttf
61573
+ */
61574
+ FontFileFormat[FontFileFormat["TrueType"] = 4] = "TrueType";
61575
+ /**
61576
+ * .svg
61577
+ */
61578
+ FontFileFormat[FontFileFormat["Svg"] = 5] = "Svg";
61579
+ })(exports.FontFileFormat || (exports.FontFileFormat = {}));
61383
61580
  /**
61384
61581
  * All main settings of alphaTab controlling rather general aspects of its behavior.
61385
61582
  * @json
61386
61583
  * @json_declaration
61387
61584
  */
61388
61585
  class CoreSettings {
61586
+ /**
61587
+ * Builds the default SMuFL font sources for the usage with alphaTab in cases
61588
+ * where no custom {@link smuflFontSources} are provided.
61589
+ * @param fontDirectory The {@link fontDirectory} configured.
61590
+ * @target web
61591
+ */
61592
+ static buildDefaultSmuflFontSources(fontDirectory) {
61593
+ const map = new Map();
61594
+ // WOFF, WOFF2 and OTF should cover all our platform needs
61595
+ const prefix = fontDirectory ?? '';
61596
+ map.set(exports.FontFileFormat.Woff2, `${prefix}Bravura.woff2`);
61597
+ map.set(exports.FontFileFormat.Woff, `${prefix}Bravura.woff`);
61598
+ map.set(exports.FontFileFormat.OpenType, `${prefix}Bravura.otf`);
61599
+ return map;
61600
+ }
61389
61601
  /**
61390
61602
  * @target web
61391
61603
  */
@@ -61411,12 +61623,27 @@
61411
61623
  * where the Web Font files of [Bravura](https://github.com/steinbergmedia/bravura) are. Normally alphaTab expects
61412
61624
  * them to be in a `font` subfolder beside the script file. If this is not the case, this setting must be used to configure the path.
61413
61625
  * Alternatively also a global variable `ALPHATAB_FONT` can be set on the page before initializing alphaTab.
61626
+ *
61627
+ * Use {@link smuflFontSources} for more flexible font configuration.
61414
61628
  * @defaultValue `"${AlphaTabScriptFolder}/font/"`
61415
61629
  * @category Core - JavaScript Specific
61416
61630
  * @target web
61417
61631
  * @since 0.9.6
61418
61632
  */
61419
61633
  this.fontDirectory = null;
61634
+ /**
61635
+ * Defines the URLs from which to load the SMuFL compliant font files.
61636
+ * @remarks
61637
+ * These sources will be used to load and register the webfonts on the page so
61638
+ * they are available for rendering the music sheet. The sources can be set to any
61639
+ * CSS compatible URL which can be passed into `url()`.
61640
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#url
61641
+ * @defaultValue Bravura files located at {@link fontDirectory} .
61642
+ * @category Core - JavaScript Specific
61643
+ * @target web
61644
+ * @since 1.6.0
61645
+ */
61646
+ this.smuflFontSources = null;
61420
61647
  /**
61421
61648
  * The full URL to the input file to be loaded.
61422
61649
  * @remarks