@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.
@@ -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
  *
@@ -14312,13 +14312,6 @@ class XmlDocument extends XmlNode {
14312
14312
  * @json_strict
14313
14313
  */
14314
14314
  class BackingTrack {
14315
- constructor() {
14316
- /**
14317
- * The number of milliseconds the audio should be shifted to align with the song.
14318
- * (e.g. negative values allow skipping potential silent parts at the start of the file and directly start with the first note).
14319
- */
14320
- this.padding = 0;
14321
- }
14322
14315
  }
14323
14316
 
14324
14317
  /**
@@ -14351,6 +14344,7 @@ class GpifParser {
14351
14344
  constructor() {
14352
14345
  this._hasAnacrusis = false;
14353
14346
  this._skipApplyLyrics = false;
14347
+ this._backingTrackPadding = 0;
14354
14348
  this._doubleBars = new Set();
14355
14349
  this._keySignatures = new Map();
14356
14350
  }
@@ -14583,7 +14577,7 @@ class GpifParser {
14583
14577
  assetId = c.innerText;
14584
14578
  break;
14585
14579
  case 'FramePadding':
14586
- backingTrack.padding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
14580
+ this._backingTrackPadding = GpifParser.parseIntSafe(c.innerText, 0) / GpifParser.SampleRate * 1000;
14587
14581
  break;
14588
14582
  }
14589
14583
  }
@@ -16797,6 +16791,7 @@ class GpifParser {
16797
16791
  masterBar.tempoAutomations.push(automation);
16798
16792
  break;
16799
16793
  case AutomationType.SyncPoint:
16794
+ automation.syncPointValue.millisecondOffset -= this._backingTrackPadding;
16800
16795
  masterBar.addSyncPoint(automation);
16801
16796
  break;
16802
16797
  }
@@ -22285,17 +22280,6 @@ class SynthEvent {
22285
22280
  }
22286
22281
  }
22287
22282
 
22288
- /**
22289
- * Rerpresents a point to sync the alphaTab time axis with an external backing track.
22290
- */
22291
- class BackingTrackSyncPoint {
22292
- constructor(tick, data) {
22293
- this.tick = 0;
22294
- this.tick = tick;
22295
- this.data = data;
22296
- }
22297
- }
22298
-
22299
22283
  class MidiFileSequencerTempoChange {
22300
22284
  constructor(bpm, ticks, time) {
22301
22285
  this.bpm = bpm;
@@ -22303,10 +22287,12 @@ class MidiFileSequencerTempoChange {
22303
22287
  this.time = time;
22304
22288
  }
22305
22289
  }
22306
- class BackingTrackSyncPointWithTime extends BackingTrackSyncPoint {
22307
- constructor(tick, data, time) {
22308
- super(tick, data);
22309
- this.time = time;
22290
+ class BackingTrackSyncPointWithTime {
22291
+ constructor(tick, time, modifiedTempo, millisecondOffset) {
22292
+ this.alphaTabTick = tick;
22293
+ this.alphaTabTime = time;
22294
+ this.modifiedTempo = modifiedTempo;
22295
+ this.millisecondOffset = millisecondOffset;
22310
22296
  }
22311
22297
  }
22312
22298
  class MidiSequencerState {
@@ -22412,7 +22398,7 @@ class MidiFileSequencer {
22412
22398
  this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
22413
22399
  this._mainState.modifiedTempo =
22414
22400
  this._mainState.syncPoints.length > 0
22415
- ? this._mainState.syncPoints[0].data.modifiedTempo
22401
+ ? this._mainState.syncPoints[0].modifiedTempo
22416
22402
  : this._mainState.currentTempo;
22417
22403
  if (this.isPlayingMain) {
22418
22404
  const metronomeVolume = this._synthesizer.metronomeVolume;
@@ -22584,25 +22570,52 @@ class MidiFileSequencer {
22584
22570
  mainUpdateSyncPoints(syncPoints) {
22585
22571
  const state = this._mainState;
22586
22572
  syncPoints.sort((a, b) => a.tick - b.tick); // just in case
22587
- state.syncPoints = new Array(syncPoints.length);
22573
+ state.syncPoints = [];
22588
22574
  if (syncPoints.length >= 0) {
22589
22575
  let bpm = 120;
22590
22576
  let absTick = 0;
22591
22577
  let absTime = 0.0;
22592
- let previousTick = 0;
22593
22578
  let tempoChangeIndex = 0;
22594
22579
  for (let i = 0; i < syncPoints.length; i++) {
22595
22580
  const p = syncPoints[i];
22596
- const deltaTick = p.tick - previousTick;
22597
- absTick += deltaTick;
22598
- absTime += deltaTick * (60000.0 / (bpm * state.division));
22599
- state.syncPoints[i] = new BackingTrackSyncPointWithTime(p.tick, p.data, absTime);
22600
- previousTick = p.tick;
22581
+ let deltaTick = 0;
22582
+ // remember state from previous sync point (or start). to handle linear interpolation
22583
+ let previousModifiedTempo;
22584
+ let previousMillisecondOffset;
22585
+ let previousTick;
22586
+ if (i === 0) {
22587
+ previousModifiedTempo = bpm;
22588
+ previousMillisecondOffset = 0;
22589
+ previousTick = 0;
22590
+ }
22591
+ else {
22592
+ const previousSyncPoint = syncPoints[i - 1];
22593
+ previousModifiedTempo = previousSyncPoint.data.modifiedTempo;
22594
+ previousMillisecondOffset = previousSyncPoint.data.millisecondOffset;
22595
+ previousTick = previousSyncPoint.tick;
22596
+ }
22597
+ // process time until sync point
22598
+ // here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
22599
+ // we have to calculate a interpolated sync point on the alphaTab time axis.
22600
+ // otherwise the linear interpolation later in the lookup will fail.
22601
+ // 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
22601
22602
  while (tempoChangeIndex < state.tempoChanges.length &&
22602
- state.tempoChanges[tempoChangeIndex].ticks <= absTick) {
22603
+ state.tempoChanges[tempoChangeIndex].ticks <= p.tick) {
22604
+ deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
22605
+ if (deltaTick > 0) {
22606
+ absTick += deltaTick;
22607
+ absTime += deltaTick * (60000.0 / (bpm * state.division));
22608
+ const millisPerTick = (p.data.millisecondOffset - previousMillisecondOffset) / (p.tick - previousTick);
22609
+ const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
22610
+ state.syncPoints.push(new BackingTrackSyncPointWithTime(absTick, absTime, previousModifiedTempo, interpolatedMillisecondOffset));
22611
+ }
22603
22612
  bpm = state.tempoChanges[tempoChangeIndex].bpm;
22604
22613
  tempoChangeIndex++;
22605
22614
  }
22615
+ deltaTick = p.tick - absTick;
22616
+ absTick += deltaTick;
22617
+ absTime += deltaTick * (60000.0 / (bpm * state.division));
22618
+ state.syncPoints.push(new BackingTrackSyncPointWithTime(p.tick, absTime, p.data.modifiedTempo, p.data.millisecondOffset));
22606
22619
  }
22607
22620
  }
22608
22621
  state.syncPointIndex = 0;
@@ -22616,7 +22629,7 @@ class MidiFileSequencer {
22616
22629
  this.updateCurrentTempo(state, timePosition);
22617
22630
  const lastTempoChange = state.tempoChanges[state.tempoChangeIndex];
22618
22631
  const timeDiff = timePosition - lastTempoChange.time;
22619
- const ticks = ((timeDiff / (60000.0 / (lastTempoChange.bpm * state.division))) | 0);
22632
+ const ticks = (timeDiff / (60000.0 / (lastTempoChange.bpm * state.division))) | 0;
22620
22633
  // we add 1 for possible rounding errors.(floating point issuses)
22621
22634
  return lastTempoChange.ticks + ticks + 1;
22622
22635
  }
@@ -22636,16 +22649,16 @@ class MidiFileSequencer {
22636
22649
  const syncPoints = state.syncPoints;
22637
22650
  if (syncPoints.length > 0) {
22638
22651
  let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
22639
- if (timePosition < syncPoints[syncPointIndex].data.millisecondOffset) {
22652
+ if (timePosition < syncPoints[syncPointIndex].millisecondOffset) {
22640
22653
  syncPointIndex = 0;
22641
22654
  }
22642
22655
  while (syncPointIndex + 1 < syncPoints.length &&
22643
- syncPoints[syncPointIndex + 1].data.millisecondOffset <= timePosition) {
22656
+ syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
22644
22657
  syncPointIndex++;
22645
22658
  }
22646
22659
  if (syncPointIndex !== state.syncPointIndex) {
22647
22660
  state.syncPointIndex = syncPointIndex;
22648
- state.modifiedTempo = syncPoints[syncPointIndex].data.modifiedTempo;
22661
+ state.modifiedTempo = syncPoints[syncPointIndex].modifiedTempo;
22649
22662
  }
22650
22663
  }
22651
22664
  else {
@@ -22661,18 +22674,18 @@ class MidiFileSequencer {
22661
22674
  this.updateCurrentTempo(this._mainState, timePosition);
22662
22675
  const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22663
22676
  const currentSyncPoint = syncPoints[syncPointIndex];
22664
- const timeDiff = timePosition - currentSyncPoint.data.millisecondOffset;
22677
+ const timeDiff = timePosition - currentSyncPoint.millisecondOffset;
22665
22678
  let alphaTabTimeDiff;
22666
22679
  if (syncPointIndex + 1 < syncPoints.length) {
22667
22680
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22668
- const relativeTimeDiff = timeDiff / (nextSyncPoint.data.millisecondOffset - currentSyncPoint.data.millisecondOffset);
22669
- alphaTabTimeDiff = (nextSyncPoint.time - currentSyncPoint.time) * relativeTimeDiff;
22681
+ const relativeTimeDiff = timeDiff / (nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset);
22682
+ alphaTabTimeDiff = (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22670
22683
  }
22671
22684
  else {
22672
- const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.data.millisecondOffset);
22673
- alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.time) * relativeTimeDiff;
22685
+ const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.millisecondOffset);
22686
+ alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.alphaTabTime) * relativeTimeDiff;
22674
22687
  }
22675
- return (currentSyncPoint.time + alphaTabTimeDiff) / this.playbackSpeed;
22688
+ return (currentSyncPoint.alphaTabTime + alphaTabTimeDiff) / this.playbackSpeed;
22676
22689
  }
22677
22690
  mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
22678
22691
  const mainState = this._mainState;
@@ -22682,25 +22695,27 @@ class MidiFileSequencer {
22682
22695
  }
22683
22696
  timePosition *= this.playbackSpeed;
22684
22697
  let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
22685
- if (timePosition < syncPoints[syncPointIndex].time) {
22698
+ if (timePosition < syncPoints[syncPointIndex].alphaTabTime) {
22686
22699
  syncPointIndex = 0;
22687
22700
  }
22688
- while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].time <= timePosition) {
22701
+ while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].alphaTabTime <= timePosition) {
22689
22702
  syncPointIndex++;
22690
22703
  }
22704
+ // NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
22705
+ // we ensure that we have a linear increase between two points
22691
22706
  const currentSyncPoint = syncPoints[syncPointIndex];
22692
- const alphaTabTimeDiff = timePosition - currentSyncPoint.time;
22707
+ const alphaTabTimeDiff = timePosition - currentSyncPoint.alphaTabTime;
22693
22708
  let backingTrackPos;
22694
22709
  if (syncPointIndex + 1 < syncPoints.length) {
22695
22710
  const nextSyncPoint = syncPoints[syncPointIndex + 1];
22696
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.time - currentSyncPoint.time);
22697
- const backingTrackDiff = nextSyncPoint.data.millisecondOffset - currentSyncPoint.data.millisecondOffset;
22698
- backingTrackPos = currentSyncPoint.data.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22711
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.alphaTabTime - currentSyncPoint.alphaTabTime);
22712
+ const backingTrackDiff = nextSyncPoint.millisecondOffset - currentSyncPoint.millisecondOffset;
22713
+ backingTrackPos = currentSyncPoint.millisecondOffset + backingTrackDiff * relativeAlphaTabTimeDiff;
22699
22714
  }
22700
22715
  else {
22701
- const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.time);
22702
- const frameDiff = backingTrackLength - currentSyncPoint.data.millisecondOffset;
22703
- backingTrackPos = currentSyncPoint.data.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22716
+ const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.alphaTabTime);
22717
+ const frameDiff = backingTrackLength - currentSyncPoint.millisecondOffset;
22718
+ backingTrackPos = currentSyncPoint.millisecondOffset + frameDiff * relativeAlphaTabTimeDiff;
22704
22719
  }
22705
22720
  return backingTrackPos;
22706
22721
  }
@@ -28582,7 +28597,9 @@ class AlphaSynthBase {
28582
28597
  hasSamplesForPercussion(key) {
28583
28598
  return this.synthesizer.hasSamplesForPercussion(key);
28584
28599
  }
28585
- loadBackingTrack(_score, _syncPoints) {
28600
+ loadBackingTrack(_score) {
28601
+ }
28602
+ updateSyncPoints(_syncPoints) {
28586
28603
  }
28587
28604
  }
28588
28605
  /**
@@ -29070,7 +29087,7 @@ class Font {
29070
29087
  }
29071
29088
  switch (typeof v) {
29072
29089
  case 'undefined':
29073
- return null;
29090
+ return undefined;
29074
29091
  case 'object': {
29075
29092
  const m = v;
29076
29093
  const families = m.get('families');
@@ -29150,10 +29167,13 @@ class Font {
29150
29167
  return Font.withFamilyList(families, fontSize, fontStyle, fontWeight);
29151
29168
  }
29152
29169
  default:
29153
- return null;
29170
+ return undefined;
29154
29171
  }
29155
29172
  }
29156
29173
  static toJson(font) {
29174
+ if (!font) {
29175
+ return undefined;
29176
+ }
29157
29177
  const o = new Map();
29158
29178
  o.set('families', font.families);
29159
29179
  o.set('size', font.size);
@@ -30160,6 +30180,14 @@ class CoreSettingsSerializer {
30160
30180
  /*@target web*/
30161
30181
  o.set("fontdirectory", obj.fontDirectory);
30162
30182
  /*@target web*/
30183
+ if (obj.smuflFontSources !== null) {
30184
+ const m = new Map();
30185
+ o.set("smuflfontsources", m);
30186
+ for (const [k, v] of obj.smuflFontSources) {
30187
+ m.set(k.toString(), v);
30188
+ }
30189
+ }
30190
+ /*@target web*/
30163
30191
  o.set("file", obj.file);
30164
30192
  /*@target web*/
30165
30193
  o.set("tex", obj.tex);
@@ -30183,6 +30211,13 @@ class CoreSettingsSerializer {
30183
30211
  obj.fontDirectory = v;
30184
30212
  return true;
30185
30213
  /*@target web*/
30214
+ case "smuflfontsources":
30215
+ obj.smuflFontSources = new Map();
30216
+ JsonHelper.forEach(v, (v, k) => {
30217
+ obj.smuflFontSources.set(JsonHelper.parseEnum(k, FontFileFormat), v);
30218
+ });
30219
+ return true;
30220
+ /*@target web*/
30186
30221
  case "file":
30187
30222
  obj.file = v;
30188
30223
  return true;
@@ -30226,6 +30261,7 @@ class RenderingResourcesSerializer {
30226
30261
  return null;
30227
30262
  }
30228
30263
  const o = new Map();
30264
+ o.set("smuflfont", Font.toJson(obj.smuflFont));
30229
30265
  o.set("copyrightfont", Font.toJson(obj.copyrightFont));
30230
30266
  o.set("titlefont", Font.toJson(obj.titleFont));
30231
30267
  o.set("subtitlefont", Font.toJson(obj.subTitleFont));
@@ -30252,6 +30288,9 @@ class RenderingResourcesSerializer {
30252
30288
  }
30253
30289
  static setProperty(obj, property, v) {
30254
30290
  switch (property) {
30291
+ case "smuflfont":
30292
+ obj.smuflFont = Font.fromJson(v);
30293
+ return true;
30255
30294
  case "copyrightfont":
30256
30295
  obj.copyrightFont = Font.fromJson(v);
30257
30296
  return true;
@@ -32457,15 +32496,9 @@ class BackingTrackSerializer {
32457
32496
  return null;
32458
32497
  }
32459
32498
  const o = new Map();
32460
- o.set("padding", obj.padding);
32461
32499
  return o;
32462
32500
  }
32463
32501
  static setProperty(obj, property, v) {
32464
- switch (property) {
32465
- case "padding":
32466
- obj.padding = v;
32467
- return true;
32468
- }
32469
32502
  return false;
32470
32503
  }
32471
32504
  }
@@ -34229,15 +34262,6 @@ class Html5Canvas {
34229
34262
  this._color = new Color(0, 0, 0, 0xff);
34230
34263
  this._font = new Font('Arial', 10, FontStyle.Plain);
34231
34264
  this._lineWidth = 0;
34232
- const fontElement = document.createElement('span');
34233
- fontElement.classList.add('at');
34234
- document.body.appendChild(fontElement);
34235
- const style = window.getComputedStyle(fontElement);
34236
- let family = style.fontFamily;
34237
- if (family.startsWith('"') || family.startsWith("'")) {
34238
- family = family.substr(1, family.length - 2);
34239
- }
34240
- this._musicFont = new Font(family, Number.parseFloat(style.fontSize), FontStyle.Plain);
34241
34265
  this._measureCanvas = document.createElement('canvas');
34242
34266
  this._measureCanvas.width = 10;
34243
34267
  this._measureCanvas.height = 10;
@@ -34252,6 +34276,7 @@ class Html5Canvas {
34252
34276
  return null;
34253
34277
  }
34254
34278
  beginRender(width, height) {
34279
+ this._musicFont = this.settings.display.resources.smuflFont;
34255
34280
  const scale = this.settings.display.scale;
34256
34281
  this._canvas = document.createElement('canvas');
34257
34282
  this._canvas.width = (width * Environment.HighDpiFactor) | 0;
@@ -34936,7 +34961,13 @@ class MasterBarTickLookup {
34936
34961
  * Gets the list of tempo changes within the tick lookup.
34937
34962
  */
34938
34963
  this.tempoChanges = [];
34964
+ /**
34965
+ * The first beat in the bar.
34966
+ */
34939
34967
  this.firstBeat = null;
34968
+ /**
34969
+ * The last beat in the bar.
34970
+ */
34940
34971
  this.lastBeat = null;
34941
34972
  /**
34942
34973
  * Gets or sets the {@link MasterBarTickLookup} of the next masterbar in the {@link Score}
@@ -35350,14 +35381,13 @@ class MidiTickLookup {
35350
35381
  constructor() {
35351
35382
  this._currentMasterBar = null;
35352
35383
  /**
35353
- * Gets a dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35384
+ * A dictionary of all master bars played. The index is the index equals to {@link MasterBar.index}.
35354
35385
  * This lookup only contains the first time a MasterBar is played. For a whole sequence of the song refer to {@link MasterBars}.
35355
35386
  * @internal
35356
35387
  */
35357
35388
  this.masterBarLookup = new Map();
35358
35389
  /**
35359
- * Gets a list of all {@link MasterBarTickLookup} sorted by time.
35360
- * @internal
35390
+ * A list of all {@link MasterBarTickLookup} sorted by time.
35361
35391
  */
35362
35392
  this.masterBars = [];
35363
35393
  /**
@@ -35731,6 +35761,17 @@ class MidiTickLookup {
35731
35761
  }
35732
35762
  }
35733
35763
 
35764
+ /**
35765
+ * Rerpresents a point to sync the alphaTab time axis with an external backing track.
35766
+ */
35767
+ class BackingTrackSyncPoint {
35768
+ constructor(tick, data) {
35769
+ this.tick = 0;
35770
+ this.tick = tick;
35771
+ this.data = data;
35772
+ }
35773
+ }
35774
+
35734
35775
  class MidiNoteDuration {
35735
35776
  constructor() {
35736
35777
  this.noteOnly = 0;
@@ -35800,62 +35841,22 @@ class MidiFileGenerator {
35800
35841
  this.generateTrack(track);
35801
35842
  }
35802
35843
  Logger.debug('Midi', 'Begin midi generation');
35803
- const controller = new MidiPlaybackController(this._score);
35804
- let previousMasterBar = null;
35805
- let currentTempo = this._score.tempo;
35806
- // store the previous played bar for repeats
35807
- const barOccurence = new Map();
35808
- while (!controller.finished) {
35809
- const index = controller.index;
35810
- const bar = this._score.masterBars[index];
35811
- const currentTick = controller.currentTick;
35812
- controller.processCurrent();
35813
- if (controller.shouldPlay) {
35814
- let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35815
- occurence++;
35816
- barOccurence.set(index, occurence);
35817
- this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35818
- if (bar.tempoAutomations.length > 0) {
35819
- currentTempo = bar.tempoAutomations[0].value;
35820
- }
35821
- for (const track of this._score.tracks) {
35822
- for (const staff of track.staves) {
35823
- if (index < staff.bars.length) {
35824
- this.generateBar(staff.bars[index], currentTick, currentTempo);
35825
- }
35844
+ this.syncPoints = [];
35845
+ MidiFileGenerator.playThroughSong(this._score, this.syncPoints, (bar, previousMasterBar, currentTick, currentTempo, occurence) => {
35846
+ this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35847
+ }, (index, currentTick, currentTempo) => {
35848
+ for (const track of this._score.tracks) {
35849
+ for (const staff of track.staves) {
35850
+ if (index < staff.bars.length) {
35851
+ this.generateBar(staff.bars[index], currentTick, currentTempo);
35826
35852
  }
35827
35853
  }
35828
- if (bar.tempoAutomations.length > 0) {
35829
- currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35830
- }
35831
35854
  }
35832
- controller.moveNext();
35833
- previousMasterBar = bar;
35834
- }
35835
- // here we interpolate the sync point which marks the end of the sync.
35836
- // Sync points define new tempos at certain positions.
35837
- // looking from the last sync point to the end we do not assume the end where the audio ends,
35838
- // but where it ends according to the BPM and the remaining ticks.
35839
- if (this.syncPoints.length > 0) {
35840
- const lastSyncPoint = this.syncPoints[this.syncPoints.length - 1];
35841
- const endTick = controller.currentTick;
35842
- const remainingTicks = endTick - lastSyncPoint.tick;
35843
- if (remainingTicks > 0) {
35844
- const syncPointData = new SyncPointData();
35845
- // last occurence of the last bar
35846
- syncPointData.barOccurence = barOccurence.get(this._score.masterBars.length - 1);
35847
- // same tempo as last point
35848
- syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35849
- // interpolated end from last syncPoint
35850
- syncPointData.millisecondOffset =
35851
- lastSyncPoint.data.millisecondOffset +
35852
- MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35853
- this.syncPoints.push(new BackingTrackSyncPoint(endTick, syncPointData));
35855
+ }, endTick => {
35856
+ for (const track of this._score.tracks) {
35857
+ this._handler.finishTrack(track.index, endTick);
35854
35858
  }
35855
- }
35856
- for (const track of this._score.tracks) {
35857
- this._handler.finishTrack(track.index, controller.currentTick);
35858
- }
35859
+ });
35859
35860
  Logger.debug('Midi', 'Midi generation done');
35860
35861
  }
35861
35862
  generateTrack(track) {
@@ -35900,6 +35901,81 @@ class MidiFileGenerator {
35900
35901
  this._handler.addControlChange(track.index, 0, channel, ControllerType.DataEntryCoarse, MidiFileGenerator.PitchBendRangeInSemitones);
35901
35902
  this.addProgramChange(track, 0, channel, playbackInfo.program);
35902
35903
  }
35904
+ /**
35905
+ * Generates the sync points for the given score without re-generating the midi itself.
35906
+ * @remarks
35907
+ * Use this method if a re-generation of the sync points after modification is required.
35908
+ * It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
35909
+ * need to be considered for synchronization.
35910
+ * @param score The song for which to regenerate the sync points.
35911
+ * @returns The generated sync points for usage in the backing track playback.
35912
+ */
35913
+ static generateSyncPoints(score) {
35914
+ const syncPoints = [];
35915
+ MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
35916
+ }, (_barIndex, _currentTick, _currentTempo) => {
35917
+ }, _endTick => {
35918
+ });
35919
+ return syncPoints;
35920
+ }
35921
+ static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
35922
+ const controller = new MidiPlaybackController(score);
35923
+ let currentTempo = score.tempo;
35924
+ let previousMasterBar = null;
35925
+ // store the previous played bar for repeats
35926
+ const barOccurence = new Map();
35927
+ while (!controller.finished) {
35928
+ const index = controller.index;
35929
+ const bar = score.masterBars[index];
35930
+ const currentTick = controller.currentTick;
35931
+ controller.processCurrent();
35932
+ if (controller.shouldPlay) {
35933
+ let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
35934
+ occurence++;
35935
+ barOccurence.set(index, occurence);
35936
+ generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
35937
+ const barSyncPoints = bar.syncPoints;
35938
+ if (barSyncPoints) {
35939
+ for (const syncPoint of barSyncPoints) {
35940
+ if (syncPoint.syncPointValue.barOccurence === occurence) {
35941
+ const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
35942
+ syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
35943
+ }
35944
+ }
35945
+ }
35946
+ if (bar.tempoAutomations.length > 0) {
35947
+ currentTempo = bar.tempoAutomations[0].value;
35948
+ }
35949
+ generateTracks(index, currentTick, currentTempo);
35950
+ if (bar.tempoAutomations.length > 0) {
35951
+ currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
35952
+ }
35953
+ }
35954
+ controller.moveNext();
35955
+ previousMasterBar = bar;
35956
+ }
35957
+ // here we interpolate the sync point which marks the end of the sync.
35958
+ // Sync points define new tempos at certain positions.
35959
+ // looking from the last sync point to the end we do not assume the end where the audio ends,
35960
+ // but where it ends according to the BPM and the remaining ticks.
35961
+ if (syncPoints.length > 0) {
35962
+ const lastSyncPoint = syncPoints[syncPoints.length - 1];
35963
+ const remainingTicks = controller.currentTick - lastSyncPoint.tick;
35964
+ if (remainingTicks > 0) {
35965
+ const syncPointData = new SyncPointData();
35966
+ // last occurence of the last bar
35967
+ syncPointData.barOccurence = barOccurence.get(score.masterBars.length - 1);
35968
+ // same tempo as last point
35969
+ syncPointData.modifiedTempo = lastSyncPoint.data.modifiedTempo;
35970
+ // interpolated end from last syncPoint
35971
+ syncPointData.millisecondOffset =
35972
+ lastSyncPoint.data.millisecondOffset +
35973
+ MidiUtils.ticksToMillis(remainingTicks, syncPointData.modifiedTempo);
35974
+ syncPoints.push(new BackingTrackSyncPoint(controller.currentTick, syncPointData));
35975
+ }
35976
+ }
35977
+ finish(controller.currentTick);
35978
+ }
35903
35979
  static toChannelShort(data) {
35904
35980
  const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
35905
35981
  return Math.max(value, -1) + 1;
@@ -38236,21 +38312,23 @@ class BackingTrackPlayer extends AlphaSynthBase {
38236
38312
  this._backingTrackOutput.seekTo(this.sequencer.mainTimePositionToBackingTrack(timePosition, this._backingTrackOutput.backingTrackDuration));
38237
38313
  }
38238
38314
  }
38239
- loadBackingTrack(score, syncPoints) {
38315
+ loadBackingTrack(score) {
38240
38316
  const backingTrackInfo = score.backingTrack;
38241
38317
  if (backingTrackInfo) {
38242
38318
  this._backingTrackOutput.loadBackingTrack(backingTrackInfo);
38243
- this.sequencer.mainUpdateSyncPoints(syncPoints);
38244
38319
  this.timePosition = 0;
38245
38320
  }
38246
38321
  }
38322
+ updateSyncPoints(syncPoints) {
38323
+ this.sequencer.mainUpdateSyncPoints(syncPoints);
38324
+ this.tickPosition = this.tickPosition;
38325
+ }
38247
38326
  }
38248
38327
 
38249
38328
  class ExternalMediaSynthOutput {
38250
38329
  constructor() {
38251
38330
  // fake rate
38252
38331
  this.sampleRate = 44100;
38253
- this._padding = 0;
38254
38332
  this._seekPosition = 0;
38255
38333
  this.ready = new EventEmitter();
38256
38334
  this.samplesPlayed = new EventEmitterOfT();
@@ -38293,20 +38371,19 @@ class ExternalMediaSynthOutput {
38293
38371
  seekTo(time) {
38294
38372
  const handler = this.handler;
38295
38373
  if (handler) {
38296
- handler.seekTo(time - this._padding);
38374
+ handler.seekTo(time);
38297
38375
  }
38298
38376
  else {
38299
- this._seekPosition = time - this._padding;
38377
+ this._seekPosition = time;
38300
38378
  }
38301
38379
  }
38302
- loadBackingTrack(backingTrack) {
38303
- this._padding = backingTrack.padding;
38380
+ loadBackingTrack(_backingTrack) {
38304
38381
  }
38305
38382
  open(_bufferTimeInMilliseconds) {
38306
38383
  this.ready.trigger();
38307
38384
  }
38308
38385
  updatePosition(currentTime) {
38309
- this.timeUpdate.trigger(currentTime + this._padding);
38386
+ this.timeUpdate.trigger(currentTime);
38310
38387
  }
38311
38388
  play() {
38312
38389
  this.handler?.play();
@@ -38571,9 +38648,14 @@ class AlphaSynthWrapper {
38571
38648
  this._instance.loadMidiFile(midi);
38572
38649
  }
38573
38650
  }
38574
- loadBackingTrack(score, syncPoints) {
38651
+ loadBackingTrack(score) {
38575
38652
  if (this._instance) {
38576
- this._instance.loadBackingTrack(score, syncPoints);
38653
+ this._instance.loadBackingTrack(score);
38654
+ }
38655
+ }
38656
+ updateSyncPoints(syncPoints) {
38657
+ if (this._instance) {
38658
+ this._instance.updateSyncPoints(syncPoints);
38577
38659
  }
38578
38660
  }
38579
38661
  applyTranspositionPitches(transpositionPitches) {
@@ -39902,7 +39984,7 @@ class AlphaTabApiBase {
39902
39984
  }
39903
39985
  appendRenderResult(result, isLast) {
39904
39986
  // resizing the canvas and wrapper elements at the end is enough
39905
- // it avoids flickering on resizes and re-renders.
39987
+ // it avoids flickering on resizes and re-renders.
39906
39988
  // the individual partials are anyhow sized correctly
39907
39989
  if (isLast) {
39908
39990
  this.canvasElement.width = result.totalWidth;
@@ -40664,9 +40746,23 @@ class AlphaTabApiBase {
40664
40746
  this.onMidiLoad(midiFile);
40665
40747
  const player = this._player;
40666
40748
  player.loadMidiFile(midiFile);
40667
- player.loadBackingTrack(score, generator.syncPoints);
40749
+ player.loadBackingTrack(score);
40750
+ player.updateSyncPoints(generator.syncPoints);
40668
40751
  player.applyTranspositionPitches(generator.transpositionPitches);
40669
40752
  }
40753
+ /**
40754
+ * Triggers an update of the sync points for the current score after modification within the data model
40755
+ * @category Methods - Player
40756
+ * @since 1.6.0
40757
+ */
40758
+ updateSyncPoints() {
40759
+ if (!this.score) {
40760
+ return;
40761
+ }
40762
+ const score = this.score;
40763
+ const player = this._player;
40764
+ player.updateSyncPoints(MidiFileGenerator.generateSyncPoints(score));
40765
+ }
40670
40766
  /**
40671
40767
  * Changes the volume of the given tracks.
40672
40768
  * @param tracks The tracks for which the volume should be changed.
@@ -43334,6 +43430,8 @@ class AlphaSynthWebWorkerApi {
43334
43430
  }
43335
43431
  loadBackingTrack(_score) {
43336
43432
  }
43433
+ updateSyncPoints(_syncPoints) {
43434
+ }
43337
43435
  }
43338
43436
 
43339
43437
  /**
@@ -43708,7 +43806,6 @@ class AudioElementBackingTrackSynthOutput {
43708
43806
  constructor() {
43709
43807
  // fake rate
43710
43808
  this.sampleRate = 44100;
43711
- this._padding = 0;
43712
43809
  this._updateInterval = 0;
43713
43810
  this.ready = new EventEmitter();
43714
43811
  this.samplesPlayed = new EventEmitterOfT();
@@ -43732,13 +43829,12 @@ class AudioElementBackingTrackSynthOutput {
43732
43829
  this.audioElement.volume = value;
43733
43830
  }
43734
43831
  seekTo(time) {
43735
- this.audioElement.currentTime = time / 1000 - this._padding;
43832
+ this.audioElement.currentTime = time / 1000;
43736
43833
  }
43737
43834
  loadBackingTrack(backingTrack) {
43738
43835
  if (this.audioElement?.src) {
43739
43836
  URL.revokeObjectURL(this.audioElement.src);
43740
43837
  }
43741
- this._padding = backingTrack.padding / 1000;
43742
43838
  const blob = new Blob([backingTrack.rawAudioFile]);
43743
43839
  // https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
43744
43840
  // Step 8. resets the playbackRate, we need to remember and restore it.
@@ -43757,7 +43853,7 @@ class AudioElementBackingTrackSynthOutput {
43757
43853
  this.ready.trigger();
43758
43854
  }
43759
43855
  updatePosition() {
43760
- const timePos = (this.audioElement.currentTime + this._padding) * 1000;
43856
+ const timePos = this.audioElement.currentTime * 1000;
43761
43857
  this.timeUpdate.trigger(timePos);
43762
43858
  }
43763
43859
  play() {
@@ -43843,10 +43939,6 @@ class BrowserUiFacade {
43843
43939
  return this.areAllFontsLoaded();
43844
43940
  }
43845
43941
  areAllFontsLoaded() {
43846
- Environment.bravuraFontChecker.checkForFontAvailability();
43847
- if (!Environment.bravuraFontChecker.isFontLoaded) {
43848
- return false;
43849
- }
43850
43942
  let isAnyNotLoaded = false;
43851
43943
  for (const checker of this._fontCheckers.values()) {
43852
43944
  if (!checker.isFontLoaded) {
@@ -43883,7 +43975,6 @@ class BrowserUiFacade {
43883
43975
  rootElement.classList.add('alphaTab');
43884
43976
  this.rootContainer = new HtmlElementContainer(rootElement);
43885
43977
  this.areWorkersSupported = 'Worker' in window;
43886
- Environment.bravuraFontChecker.fontLoaded.on(this.onFontLoaded.bind(this));
43887
43978
  this._intersectionObserver = new IntersectionObserver(this.onElementVisibilityChanged.bind(this), {
43888
43979
  threshold: [0, 0.01, 1]
43889
43980
  });
@@ -43951,7 +44042,7 @@ class BrowserUiFacade {
43951
44042
  this._contents = element.element.innerHTML;
43952
44043
  element.element.innerHTML = '';
43953
44044
  }
43954
- this.createStyleElement(settings);
44045
+ this.createStyleElements(settings);
43955
44046
  this._file = settings.core.file;
43956
44047
  }
43957
44048
  setupFontCheckers(settings) {
@@ -43977,10 +44068,16 @@ class BrowserUiFacade {
43977
44068
  }
43978
44069
  destroy() {
43979
44070
  this.rootContainer.element.innerHTML = '';
44071
+ const webFont = this._webFont;
44072
+ webFont.usages--;
44073
+ if (webFont.usages <= 0) {
44074
+ webFont.element.remove();
44075
+ BrowserUiFacade._registeredWebFonts.delete(webFont.hash);
44076
+ }
43980
44077
  }
43981
44078
  createCanvasElement() {
43982
44079
  const canvasElement = document.createElement('div');
43983
- canvasElement.className = 'at-surface';
44080
+ canvasElement.classList.add('at-surface', `at${this._webFont.fontSuffix}`);
43984
44081
  canvasElement.style.fontSize = '0';
43985
44082
  canvasElement.style.overflow = 'hidden';
43986
44083
  canvasElement.style.lineHeight = '0';
@@ -44078,9 +44175,133 @@ class BrowserUiFacade {
44078
44175
  initialRender();
44079
44176
  }
44080
44177
  }
44081
- createStyleElement(settings) {
44082
- const elementDocument = this._api.container.element.ownerDocument;
44083
- Environment.createStyleElement(elementDocument, settings.core.fontDirectory);
44178
+ createStyleElements(settings) {
44179
+ const root = this._api.container.element.ownerDocument;
44180
+ BrowserUiFacade.createSharedStyleElement(root);
44181
+ // SmuFl Font Specific style
44182
+ const smuflFontSources = settings.core.smuflFontSources ?? CoreSettings.buildDefaultSmuflFontSources(settings.core.fontDirectory);
44183
+ // create a simple unique hash for the font source definition
44184
+ // as data urls might be used we don't want to just use the plain strings.
44185
+ const hash = BrowserUiFacade.cyrb53(smuflFontSources.values());
44186
+ // reuse existing style if available
44187
+ const registeredWebFonts = BrowserUiFacade._registeredWebFonts;
44188
+ if (registeredWebFonts.has(hash)) {
44189
+ const webFont = registeredWebFonts.get(hash);
44190
+ webFont.usages++;
44191
+ webFont.checker.fontLoaded.on(this.onFontLoaded.bind(this));
44192
+ this._webFont = webFont;
44193
+ return;
44194
+ }
44195
+ const fontSuffix = registeredWebFonts.size === 0 ? '' : String(registeredWebFonts.size);
44196
+ const familyName = `alphaTab${fontSuffix}`;
44197
+ const src = Array.from(smuflFontSources.entries())
44198
+ .map(e => `url(${JSON.stringify(e[1])}) format('${BrowserUiFacade.cssFormat(e[0])}')`)
44199
+ .join(',');
44200
+ const css = `
44201
+ @font-face {
44202
+ font-display: block;
44203
+ font-family: '${familyName}';
44204
+ src: ${src};
44205
+ font-weight: normal;
44206
+ font-style: normal;
44207
+ }
44208
+ .at-surface.at${fontSuffix} .at {
44209
+ font-family: '${familyName}';
44210
+ speak: none;
44211
+ font-style: normal;
44212
+ font-weight: normal;
44213
+ font-variant: normal;
44214
+ text-transform: none;
44215
+ line-height: 1;
44216
+ line-height: 1;
44217
+ -webkit-font-smoothing: antialiased;
44218
+ -moz-osx-font-smoothing: grayscale;
44219
+ font-size: ${Environment.MusicFontSize}px;
44220
+ overflow: visible !important;
44221
+ }`;
44222
+ const styleElement = root.createElement('style');
44223
+ styleElement.id = `alphaTabStyle${fontSuffix}`;
44224
+ styleElement.innerHTML = css;
44225
+ root.getElementsByTagName('head').item(0).appendChild(styleElement);
44226
+ const checker = new FontLoadingChecker([familyName]);
44227
+ checker.fontLoaded.on(this.onFontLoaded.bind(this));
44228
+ this._fontCheckers.set(familyName, checker);
44229
+ checker.checkForFontAvailability();
44230
+ settings.display.resources.smuflFont = new Font(familyName, Environment.MusicFontSize, FontStyle.Plain, FontWeight.Regular);
44231
+ const webFont = {
44232
+ hash,
44233
+ element: styleElement,
44234
+ fontSuffix,
44235
+ usages: 1,
44236
+ checker
44237
+ };
44238
+ registeredWebFonts.set(hash, webFont);
44239
+ this._webFont = webFont;
44240
+ }
44241
+ static cssFormat(format) {
44242
+ switch (format) {
44243
+ case FontFileFormat.EmbeddedOpenType:
44244
+ return 'embedded-opentype';
44245
+ case FontFileFormat.Woff:
44246
+ return 'woff';
44247
+ case FontFileFormat.Woff2:
44248
+ return 'woff2';
44249
+ case FontFileFormat.OpenType:
44250
+ return 'opentype';
44251
+ case FontFileFormat.TrueType:
44252
+ return 'truetype';
44253
+ case FontFileFormat.Svg:
44254
+ return 'svg';
44255
+ }
44256
+ }
44257
+ /**
44258
+ * cyrb53 (c) 2018 bryc (github.com/bryc)
44259
+ * License: Public domain (or MIT if needed). Attribution appreciated.
44260
+ * A fast and simple 53-bit string hash function with decent collision resistance.
44261
+ * Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity
44262
+ * @param str
44263
+ * @param seed
44264
+ * @returns
44265
+ */
44266
+ static cyrb53(strings, seed = 0) {
44267
+ let h1 = 0xdeadbeef ^ seed;
44268
+ let h2 = 0x41c6ce57 ^ seed;
44269
+ for (const str of strings) {
44270
+ for (let i = 0; i < str.length; i++) {
44271
+ const ch = str.charCodeAt(i);
44272
+ h1 = Math.imul(h1 ^ ch, 2654435761);
44273
+ h2 = Math.imul(h2 ^ ch, 1597334677);
44274
+ }
44275
+ }
44276
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
44277
+ h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
44278
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
44279
+ h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
44280
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
44281
+ }
44282
+ /**
44283
+ * Creates the default CSS styles used across all alphaTab instances.
44284
+ * @target web
44285
+ * @internal
44286
+ */
44287
+ static createSharedStyleElement(root) {
44288
+ let styleElement = root.getElementById('alphaTabStyle');
44289
+ if (!styleElement) {
44290
+ styleElement = document.createElement('style');
44291
+ styleElement.id = 'alphaTabStyleShared';
44292
+ const css = `
44293
+ .at-surface * {
44294
+ cursor: default;
44295
+ vertical-align: top;
44296
+ overflow: visible;
44297
+ }
44298
+ .at-surface-svg text {
44299
+ dominant-baseline: central;
44300
+ white-space:pre;
44301
+ }`;
44302
+ styleElement.innerHTML = css;
44303
+ document.getElementsByTagName('head').item(0).appendChild(styleElement);
44304
+ }
44084
44305
  }
44085
44306
  parseTracks(tracksData) {
44086
44307
  if (!tracksData) {
@@ -44462,6 +44683,7 @@ class BrowserUiFacade {
44462
44683
  return new BackingTrackPlayer(new AudioElementBackingTrackSynthOutput(), this._api.settings.player.bufferTimeInMilliseconds);
44463
44684
  }
44464
44685
  }
44686
+ BrowserUiFacade._registeredWebFonts = new Map();
44465
44687
 
44466
44688
  /**
44467
44689
  * Represents the progress of any data being loaded.
@@ -60719,9 +60941,9 @@ class VersionInfo {
60719
60941
  print(`build date: ${VersionInfo.date}`);
60720
60942
  }
60721
60943
  }
60722
- VersionInfo.version = '1.6.0-alpha.1416';
60723
- VersionInfo.date = '2025-05-19T16:41:23.634Z';
60724
- VersionInfo.commit = '4aff072e1be5292482c3d77f434dd1555b6cbb8d';
60944
+ VersionInfo.version = '1.6.0-alpha.1420';
60945
+ VersionInfo.date = '2025-05-22T03:03:04.057Z';
60946
+ VersionInfo.commit = '230bdd455a1906e6f334b63bad3b8cf773f890e6';
60725
60947
 
60726
60948
  /**
60727
60949
  * A factory for custom layout engines.
@@ -60753,59 +60975,6 @@ class RenderEngineFactory {
60753
60975
  * @partial
60754
60976
  */
60755
60977
  class Environment {
60756
- /**
60757
- * @target web
60758
- * @internal
60759
- */
60760
- static createStyleElement(elementDocument, fontDirectory) {
60761
- let styleElement = elementDocument.getElementById('alphaTabStyle');
60762
- if (!styleElement) {
60763
- if (!fontDirectory) {
60764
- Logger.error('AlphaTab', 'Font directory could not be detected, cannot create style element');
60765
- return;
60766
- }
60767
- styleElement = elementDocument.createElement('style');
60768
- styleElement.id = 'alphaTabStyle';
60769
- const css = `
60770
- @font-face {
60771
- font-display: block;
60772
- font-family: 'alphaTab';
60773
- src: url('${fontDirectory}Bravura.eot');
60774
- src: url('${fontDirectory}Bravura.eot?#iefix') format('embedded-opentype')
60775
- , url('${fontDirectory}Bravura.woff') format('woff')
60776
- , url('${fontDirectory}Bravura.otf') format('opentype')
60777
- , url('${fontDirectory}Bravura.svg#Bravura') format('svg');
60778
- font-weight: normal;
60779
- font-style: normal;
60780
- }
60781
- .at-surface * {
60782
- cursor: default;
60783
- vertical-align: top;
60784
- overflow: visible;
60785
- }
60786
- .at-surface-svg text {
60787
- dominant-baseline: central;
60788
- white-space:pre;
60789
- }
60790
- .at {
60791
- font-family: 'alphaTab';
60792
- speak: none;
60793
- font-style: normal;
60794
- font-weight: normal;
60795
- font-variant: normal;
60796
- text-transform: none;
60797
- line-height: 1;
60798
- line-height: 1;
60799
- -webkit-font-smoothing: antialiased;
60800
- -moz-osx-font-smoothing: grayscale;
60801
- font-size: ${Environment.MusicFontSize}px;
60802
- overflow: visible !important;
60803
- }`;
60804
- styleElement.innerHTML = css;
60805
- elementDocument.getElementsByTagName('head').item(0).appendChild(styleElement);
60806
- Environment.bravuraFontChecker.checkForFontAvailability();
60807
- }
60808
- }
60809
60978
  /**
60810
60979
  * @target web
60811
60980
  * @internal
@@ -60881,7 +61050,9 @@ class Environment {
60881
61050
  catch (e) {
60882
61051
  }
60883
61052
  // normal browser include as <script>
60884
- if ('document' in Environment.globalThis && document.currentScript) {
61053
+ if ('document' in Environment.globalThis &&
61054
+ document.currentScript &&
61055
+ document.currentScript instanceof HTMLScriptElement) {
60885
61056
  return document.currentScript.src;
60886
61057
  }
60887
61058
  return null;
@@ -61359,11 +61530,6 @@ Environment.scriptFile = Environment.detectScriptFile();
61359
61530
  * @target web
61360
61531
  */
61361
61532
  Environment.fontDirectory = Environment.detectFontDirectory();
61362
- /**
61363
- * @target web
61364
- * @internal
61365
- */
61366
- Environment.bravuraFontChecker = new FontLoadingChecker(['alphaTab']);
61367
61533
  Environment.renderEngines = Environment.createDefaultRenderEngines();
61368
61534
  /**
61369
61535
  * @internal
@@ -61374,12 +61540,58 @@ Environment.layoutEngines = Environment.createDefaultLayoutEngines();
61374
61540
  */
61375
61541
  Environment.staveProfiles = Environment.createDefaultStaveProfiles();
61376
61542
 
61543
+ /**
61544
+ * Lists the known file formats for font files.
61545
+ * @target web
61546
+ */
61547
+ var FontFileFormat;
61548
+ (function (FontFileFormat) {
61549
+ /**
61550
+ * .eot
61551
+ */
61552
+ FontFileFormat[FontFileFormat["EmbeddedOpenType"] = 0] = "EmbeddedOpenType";
61553
+ /**
61554
+ * .woff
61555
+ */
61556
+ FontFileFormat[FontFileFormat["Woff"] = 1] = "Woff";
61557
+ /**
61558
+ * .woff2
61559
+ */
61560
+ FontFileFormat[FontFileFormat["Woff2"] = 2] = "Woff2";
61561
+ /**
61562
+ * .otf
61563
+ */
61564
+ FontFileFormat[FontFileFormat["OpenType"] = 3] = "OpenType";
61565
+ /**
61566
+ * .ttf
61567
+ */
61568
+ FontFileFormat[FontFileFormat["TrueType"] = 4] = "TrueType";
61569
+ /**
61570
+ * .svg
61571
+ */
61572
+ FontFileFormat[FontFileFormat["Svg"] = 5] = "Svg";
61573
+ })(FontFileFormat || (FontFileFormat = {}));
61377
61574
  /**
61378
61575
  * All main settings of alphaTab controlling rather general aspects of its behavior.
61379
61576
  * @json
61380
61577
  * @json_declaration
61381
61578
  */
61382
61579
  class CoreSettings {
61580
+ /**
61581
+ * Builds the default SMuFL font sources for the usage with alphaTab in cases
61582
+ * where no custom {@link smuflFontSources} are provided.
61583
+ * @param fontDirectory The {@link fontDirectory} configured.
61584
+ * @target web
61585
+ */
61586
+ static buildDefaultSmuflFontSources(fontDirectory) {
61587
+ const map = new Map();
61588
+ // WOFF, WOFF2 and OTF should cover all our platform needs
61589
+ const prefix = fontDirectory ?? '';
61590
+ map.set(FontFileFormat.Woff2, `${prefix}Bravura.woff2`);
61591
+ map.set(FontFileFormat.Woff, `${prefix}Bravura.woff`);
61592
+ map.set(FontFileFormat.OpenType, `${prefix}Bravura.otf`);
61593
+ return map;
61594
+ }
61383
61595
  /**
61384
61596
  * @target web
61385
61597
  */
@@ -61405,12 +61617,27 @@ class CoreSettings {
61405
61617
  * where the Web Font files of [Bravura](https://github.com/steinbergmedia/bravura) are. Normally alphaTab expects
61406
61618
  * 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.
61407
61619
  * Alternatively also a global variable `ALPHATAB_FONT` can be set on the page before initializing alphaTab.
61620
+ *
61621
+ * Use {@link smuflFontSources} for more flexible font configuration.
61408
61622
  * @defaultValue `"${AlphaTabScriptFolder}/font/"`
61409
61623
  * @category Core - JavaScript Specific
61410
61624
  * @target web
61411
61625
  * @since 0.9.6
61412
61626
  */
61413
61627
  this.fontDirectory = null;
61628
+ /**
61629
+ * Defines the URLs from which to load the SMuFL compliant font files.
61630
+ * @remarks
61631
+ * These sources will be used to load and register the webfonts on the page so
61632
+ * they are available for rendering the music sheet. The sources can be set to any
61633
+ * CSS compatible URL which can be passed into `url()`.
61634
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#url
61635
+ * @defaultValue Bravura files located at {@link fontDirectory} .
61636
+ * @category Core - JavaScript Specific
61637
+ * @target web
61638
+ * @since 1.6.0
61639
+ */
61640
+ this.smuflFontSources = null;
61414
61641
  /**
61415
61642
  * The full URL to the input file to be loaded.
61416
61643
  * @remarks
@@ -65053,4 +65280,4 @@ const _jsonbarrel = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.definePropert
65053
65280
  __proto__: null
65054
65281
  }, Symbol.toStringTag, { value: 'Module' }));
65055
65282
 
65056
- export { AlphaTabApi, AlphaTabApiBase, AlphaTabError, AlphaTabErrorType, ConsoleLogger, CoreSettings, DisplaySettings, Environment, FileLoadError, FingeringMode, FormatError, ImporterSettings, LayoutMode, LogLevel, Logger, NotationElement, NotationMode, NotationSettings, PlayerMode, PlayerOutputMode, PlayerSettings, ProgressEventArgs, RenderEngineFactory, RenderingResources, ResizeEventArgs, ScrollMode, Settings, SlidePlaybackSettings, StaveProfile, SystemsLayoutMode, TabRhythmMode, VibratoPlaybackSettings, WebPlatform, _barrel$5 as exporter, _barrel$7 as importer, _barrel$6 as io, _jsonbarrel as json, VersionInfo as meta, _barrel$4 as midi, _barrel$3 as model, _barrel$1 as platform, _barrel$2 as rendering, _barrel as synth };
65283
+ export { AlphaTabApi, AlphaTabApiBase, AlphaTabError, AlphaTabErrorType, ConsoleLogger, CoreSettings, DisplaySettings, Environment, FileLoadError, FingeringMode, FontFileFormat, FormatError, ImporterSettings, LayoutMode, LogLevel, Logger, NotationElement, NotationMode, NotationSettings, PlayerMode, PlayerOutputMode, PlayerSettings, ProgressEventArgs, RenderEngineFactory, RenderingResources, ResizeEventArgs, ScrollMode, Settings, SlidePlaybackSettings, StaveProfile, SystemsLayoutMode, TabRhythmMode, VibratoPlaybackSettings, WebPlatform, _barrel$5 as exporter, _barrel$7 as importer, _barrel$6 as io, _jsonbarrel as json, VersionInfo as meta, _barrel$4 as midi, _barrel$3 as model, _barrel$1 as platform, _barrel$2 as rendering, _barrel as synth };