@coderline/alphatab 1.9.0-alpha.1860 → 1.9.0-alpha.1861

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.9.0-alpha.1860 (develop, build 1860)
2
+ * alphaTab v1.9.0-alpha.1861 (develop, build 1861)
3
3
  *
4
4
  * Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -190,9 +190,9 @@
190
190
  * @internal
191
191
  */
192
192
  var VersionInfo = class VersionInfo {
193
- static version = "1.9.0-alpha.1860";
194
- static date = "2026-07-10T14:22:26.590Z";
195
- static commit = "1efc2ecc10ca13f2bfbec400427822dc4bdfc160";
193
+ static version = "1.9.0-alpha.1861";
194
+ static date = "2026-07-11T03:34:14.452Z";
195
+ static commit = "18845390986e826fff8ae285ee3630080dbd6653";
196
196
  static print(print) {
197
197
  print(`alphaTab ${VersionInfo.version}`);
198
198
  print(`commit: ${VersionInfo.commit}`);
@@ -29201,11 +29201,45 @@
29201
29201
  * @since 0.9.7
29202
29202
  * @defaultValue `true`
29203
29203
  * @category Player
29204
+ * @json_read_only
29204
29205
  * @remarks
29205
29206
  * This setting configures whether alphaTab provides the default user interaction features like selection of the playback range and "seek on click".
29206
29207
  * By default users can select the desired playback range with the mouse and also jump to individual beats by click. This behavior can be contolled with this setting.
29208
+ * @deprecated Use {@link enableSeekToClick} and {@link enablePlaybackRangeSelection} individually
29209
+ */
29210
+ get enableUserInteraction() {
29211
+ return this.enableSeekToClick || this.enablePlaybackRangeSelection;
29212
+ }
29213
+ /**
29214
+ * @deprecated Use {@link enableSeekToClick} and {@link enablePlaybackRangeSelection} individually
29215
+ */
29216
+ set enableUserInteraction(value) {
29217
+ this.enableSeekToClick = value;
29218
+ this.enablePlaybackRangeSelection = value;
29219
+ this.resetPlaybackRangeOnClick = value;
29220
+ }
29221
+ /**
29222
+ * Whether the a click on the music sheet triggers a player seek to the note/beat at the
29223
+ * clicked location.
29224
+ * @since 1.9.0
29225
+ * @defaultValue `true`
29226
+ * @category Player
29227
+ */
29228
+ enableSeekToClick = true;
29229
+ /**
29230
+ * Whether user click and drag results in a selection defining the playback range.
29231
+ * @since 1.9.0
29232
+ * @defaultValue `true`
29233
+ * @category Player
29207
29234
  */
29208
- enableUserInteraction = true;
29235
+ enablePlaybackRangeSelection = true;
29236
+ /**
29237
+ * Whether a simple click (no range drag) should reset the current playback range.
29238
+ * @since 1.9.0
29239
+ * @defaultValue `true`
29240
+ * @category Player
29241
+ */
29242
+ resetPlaybackRangeOnClick = true;
29209
29243
  /**
29210
29244
  * The X-offset to add when scrolling.
29211
29245
  * @since 0.9.6
@@ -30362,7 +30396,9 @@
30362
30396
  o.set("enablecursor", obj.enableCursor);
30363
30397
  o.set("enableanimatedbeatcursor", obj.enableAnimatedBeatCursor);
30364
30398
  o.set("enableelementhighlighting", obj.enableElementHighlighting);
30365
- o.set("enableuserinteraction", obj.enableUserInteraction);
30399
+ o.set("enableseektoclick", obj.enableSeekToClick);
30400
+ o.set("enableplaybackrangeselection", obj.enablePlaybackRangeSelection);
30401
+ o.set("resetplaybackrangeonclick", obj.resetPlaybackRangeOnClick);
30366
30402
  o.set("scrolloffsetx", obj.scrollOffsetX);
30367
30403
  o.set("scrolloffsety", obj.scrollOffsetY);
30368
30404
  o.set("scrollmode", obj.scrollMode);
@@ -30405,6 +30441,15 @@
30405
30441
  case "enableuserinteraction":
30406
30442
  obj.enableUserInteraction = v;
30407
30443
  return true;
30444
+ case "enableseektoclick":
30445
+ obj.enableSeekToClick = v;
30446
+ return true;
30447
+ case "enableplaybackrangeselection":
30448
+ obj.enablePlaybackRangeSelection = v;
30449
+ return true;
30450
+ case "resetplaybackrangeonclick":
30451
+ obj.resetPlaybackRangeOnClick = v;
30452
+ return true;
30408
30453
  case "scrolloffsetx":
30409
30454
  obj.scrollOffsetX = v;
30410
30455
  return true;
@@ -45403,6 +45448,16 @@
45403
45448
  scaleToWidth(beatWidth) {
45404
45449
  this.width = beatWidth;
45405
45450
  }
45451
+ /**
45452
+ * Repositions this beat so its {@link onTimeX} anchor lands at `target`, used for the
45453
+ * centered full-bar note/rest (see {@link BarLayoutingInfo.isCenteredFullBar}). The default
45454
+ * shifts the whole container, matching the regular (non-centered) positioning formula.
45455
+ * {@link BeatContainerGlyph} overrides this to shift only its ink, keeping `x`/`width`
45456
+ * spanning the full bar so bounds lookups and skyline emission stay correct.
45457
+ */
45458
+ applyCenterOffset(target) {
45459
+ this.x = target - this.onTimeX;
45460
+ }
45406
45461
  };
45407
45462
  /**
45408
45463
  * @internal
@@ -45506,13 +45561,28 @@
45506
45561
  this.preNotes.renderer = this.renderer;
45507
45562
  this.preNotes.container = this;
45508
45563
  this.preNotes.doLayout();
45509
- this.onNotes.x = this.preNotes.x + this.preNotes.width;
45564
+ this._layoutOnsetX();
45510
45565
  this.onNotes.renderer = this.renderer;
45511
45566
  this.onNotes.container = this;
45512
45567
  this.onNotes.doLayout();
45513
45568
  this.createBeatTies();
45514
45569
  this.updateWidth();
45515
45570
  }
45571
+ /**
45572
+ * Resets `preNotes.x`/`onNotes.x` to their natural (un-centered) baseline.
45573
+ * Shared by {@link doLayout} and {@link applyCenterOffset} so the latter can be called
45574
+ * repeatedly (once per `_scaleToForce` pass) without compounding a previous offset.
45575
+ */
45576
+ _layoutOnsetX() {
45577
+ this.preNotes.x = 0;
45578
+ this.onNotes.x = this.preNotes.x + this.preNotes.width;
45579
+ }
45580
+ applyCenterOffset(target) {
45581
+ this._layoutOnsetX();
45582
+ const offset = target - this.onTimeX;
45583
+ this.preNotes.x = offset;
45584
+ this.onNotes.x = this.preNotes.x + this.preNotes.width;
45585
+ }
45516
45586
  createBeatTies() {
45517
45587
  let i = this.beat.notes.length - 1;
45518
45588
  while (i >= 0) this.createTies(this.beat.notes[i--]);
@@ -48602,7 +48672,7 @@
48602
48672
  }
48603
48673
  _onBeatMouseDown(originalEvent, beat) {
48604
48674
  if (this._isDestroyed) return;
48605
- if (this._hasCursor && this.settings.player.enableUserInteraction) {
48675
+ if (this._hasCursor && this.settings.player.enablePlaybackRangeSelection) {
48606
48676
  this._selectionStart = { beat };
48607
48677
  this._selectionEnd = void 0;
48608
48678
  }
@@ -48618,10 +48688,12 @@
48618
48688
  }
48619
48689
  _onBeatMouseMove(originalEvent, beat) {
48620
48690
  if (this._isDestroyed) return;
48621
- if (this.settings.player.enableUserInteraction) {
48691
+ if (this.settings.player.enablePlaybackRangeSelection) {
48622
48692
  if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
48623
- this._selectionEnd = { beat };
48624
- this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48693
+ if (this._selectionStart?.beat !== beat) {
48694
+ this._selectionEnd = { beat };
48695
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48696
+ }
48625
48697
  }
48626
48698
  }
48627
48699
  this.beatMouseMove.trigger(beat);
@@ -48634,11 +48706,30 @@
48634
48706
  }
48635
48707
  _onBeatMouseUp(originalEvent, beat) {
48636
48708
  if (this._isDestroyed) return;
48637
- if (this._hasCursor && this.settings.player.enableUserInteraction) this.applyPlaybackRangeFromHighlight();
48709
+ if (this._hasCursor) {
48710
+ let shouldSeekToBeat = beat && this.settings.player.enableSeekToClick;
48711
+ if (this.settings.player.enablePlaybackRangeSelection) {
48712
+ if (this._internalApplyPlaybackRangeFromHighlight()) shouldSeekToBeat = false;
48713
+ }
48714
+ if (shouldSeekToBeat) this._seekToBeat(beat);
48715
+ }
48638
48716
  this.beatMouseUp.trigger(beat);
48639
48717
  this.uiFacade.triggerEvent(this.container, "beatMouseUp", beat, originalEvent);
48640
48718
  this._isBeatMouseDown = false;
48641
48719
  }
48720
+ _seekToBeat(beat) {
48721
+ const tickCache = this._tickCache;
48722
+ if (!tickCache) return;
48723
+ this._currentBeat = null;
48724
+ let beatTick = tickCache.getMasterBarStart(beat.voice.bar.masterBar) + (tickCache.getRelativeBeatPlaybackRange(beat)?.startTick ?? beat.playbackStart);
48725
+ const playbackRange = this.playbackRange;
48726
+ if (playbackRange) {
48727
+ if (beatTick < playbackRange.startTick) beatTick = playbackRange.startTick;
48728
+ else if (beatTick > playbackRange.endTick) beatTick = playbackRange.endTick;
48729
+ }
48730
+ if (this._player.state === PlayerState.Paused) this._cursorUpdateTick(beatTick, false, 1);
48731
+ this.tickPosition = beatTick;
48732
+ }
48642
48733
  _onNoteMouseUp(originalEvent, note) {
48643
48734
  if (this._isDestroyed) return;
48644
48735
  this.noteMouseUp.trigger(note);
@@ -48660,7 +48751,7 @@
48660
48751
  _setupClickHandling() {
48661
48752
  this.canvasElement.mouseDown.on((e) => {
48662
48753
  if (!e.isLeftMouseButton) return;
48663
- if (this.settings.player.enableUserInteraction) e.preventDefault();
48754
+ if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) e.preventDefault();
48664
48755
  const relX = e.getX(this.canvasElement);
48665
48756
  const relY = e.getY(this.canvasElement);
48666
48757
  const beat = this._renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null;
@@ -48687,7 +48778,7 @@
48687
48778
  });
48688
48779
  this.canvasElement.mouseUp.on((e) => {
48689
48780
  if (!this._isBeatMouseDown) return;
48690
- if (this.settings.player.enableUserInteraction) e.preventDefault();
48781
+ if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) e.preventDefault();
48691
48782
  const relX = e.getX(this.canvasElement);
48692
48783
  const relY = e.getY(this.canvasElement);
48693
48784
  const beat = this._renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null;
@@ -48698,7 +48789,7 @@
48698
48789
  } else this._onNoteMouseUp(e, null);
48699
48790
  });
48700
48791
  this._renderer.postRenderFinished.on(() => {
48701
- if (!this._selectionStart || !this._hasCursor || !this.settings.player.enableUserInteraction) return;
48792
+ if (!this._selectionStart || !this._hasCursor || !this.settings.player.enablePlaybackRangeSelection) return;
48702
48793
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48703
48794
  });
48704
48795
  }
@@ -48790,6 +48881,9 @@
48790
48881
  * ```
48791
48882
  */
48792
48883
  applyPlaybackRangeFromHighlight() {
48884
+ this._internalApplyPlaybackRangeFromHighlight();
48885
+ }
48886
+ _internalApplyPlaybackRangeFromHighlight() {
48793
48887
  if (this._selectionEnd) {
48794
48888
  const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ?? this._selectionStart.beat.absolutePlaybackStart;
48795
48889
  if ((this._tickCache?.getBeatStart(this._selectionEnd.beat) ?? this._selectionEnd.beat.absolutePlaybackStart) < startTick) {
@@ -48797,27 +48891,32 @@
48797
48891
  this._selectionStart = this._selectionEnd;
48798
48892
  this._selectionEnd = t;
48799
48893
  }
48800
- }
48894
+ } else if (!this.settings.player.resetPlaybackRangeOnClick) return false;
48801
48895
  if (this._selectionStart && this._tickCache) {
48802
48896
  const tickCache = this._tickCache;
48803
48897
  const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
48804
48898
  const startBeatPlaybackStart = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat)?.startTick ?? this._selectionStart.beat.playbackStart;
48805
- this._currentBeat = null;
48806
- if (this._player.state === PlayerState.Paused) this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
48807
- this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
48899
+ let seekToStart = this.settings.player.enableSeekToClick;
48808
48900
  if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
48901
+ seekToStart = true;
48809
48902
  const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
48810
48903
  const endBeatPlaybackEnd = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat)?.endTick ?? this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
48811
48904
  const range = new PlaybackRange();
48812
48905
  range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
48813
48906
  range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
48814
48907
  this.playbackRange = range;
48815
- } else {
48908
+ } else if (this.settings.player.resetPlaybackRangeOnClick) {
48816
48909
  this._selectionStart = void 0;
48817
48910
  this.playbackRange = null;
48818
48911
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48819
48912
  }
48913
+ if (seekToStart) {
48914
+ this._currentBeat = null;
48915
+ if (this._player.state === PlayerState.Paused) this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
48916
+ this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
48917
+ }
48820
48918
  }
48919
+ return true;
48821
48920
  }
48822
48921
  /**
48823
48922
  * Clears the highlight markers marking the currently selected playback range.
@@ -50103,7 +50202,8 @@
50103
50202
  settings.player.enableCursor = false;
50104
50203
  settings.player.playerMode = PlayerMode.Disabled;
50105
50204
  settings.player.enableElementHighlighting = false;
50106
- settings.player.enableUserInteraction = false;
50205
+ settings.player.enableSeekToClick = false;
50206
+ settings.player.enablePlaybackRangeSelection = false;
50107
50207
  settings.player.soundFont = null;
50108
50208
  settings.display.scale = .8;
50109
50209
  settings.display.stretchForce = .8;
@@ -50913,7 +51013,9 @@
50913
51013
  }
50914
51014
  handleMessage(e) {
50915
51015
  const data = e.data;
50916
- switch (data.cmd) {
51016
+ const cmd = data.cmd;
51017
+ if (!cmd) return;
51018
+ switch (cmd) {
50917
51019
  case "alphaSynth.initialize":
50918
51020
  AlphaSynthWorkerSynthOutput.preferredSampleRate = data.sampleRate;
50919
51021
  Logger.logLevel = data.logLevel;
@@ -51007,7 +51109,7 @@
51007
51109
  this._player.applyTranspositionPitches(data.transpositionPitches);
51008
51110
  break;
51009
51111
  }
51010
- if (data.cmd.startsWith("alphaSynth.exporter")) this._handleExporterMessage(e);
51112
+ if (cmd.startsWith("alphaSynth.exporter")) this._handleExporterMessage(e);
51011
51113
  }
51012
51114
  _handleExporterMessage(ev) {
51013
51115
  const data = ev.data;
@@ -54511,9 +54613,35 @@
54511
54613
  const force = this.renderer.layoutingInfo.spaceToForce(width);
54512
54614
  this._scaleToForce(force, true);
54513
54615
  }
54616
+ /**
54617
+ * `true` when every voice/track/staff contributes exactly one beat, of uniform duration,
54618
+ * spanning the bar's entire duration - i.e. a single full-bar note/rest. Common engraving
54619
+ * practice centers such notes horizontally within the bar rather than anchoring them right
54620
+ * after the pre-beat content (Behind Bars, p. 41; see #2464).
54621
+ */
54622
+ _isCenteredFullBar() {
54623
+ const masterBar = this.renderer.bar.masterBar;
54624
+ const layoutingInfo = this.renderer.layoutingInfo;
54625
+ const springs = layoutingInfo.springs;
54626
+ if (springs.size !== 1 || !springs.has(masterBar.start) || layoutingInfo.allGraceRods.size > 0) return false;
54627
+ const spring = springs.get(masterBar.start);
54628
+ return spring.allDurations.size === 1 && spring.longestDuration === masterBar.calculateDuration();
54629
+ }
54514
54630
  /** `emit=false`: positioning-only path; final skyline emission runs later via {@link scaleToWidth}. */
54515
54631
  _scaleToForce(force, emit) {
54516
54632
  this.width = this.renderer.layoutingInfo.calculateVoiceWidth(force);
54633
+ if (this._isCenteredFullBar()) {
54634
+ const barNumberWidth = this.renderer.barNumberWidth;
54635
+ const target = (this.width - barNumberWidth) / 2;
54636
+ for (const beatGlyphs of this.beatGlyphs.values()) {
54637
+ const soleBeatGlyph = beatGlyphs[0];
54638
+ soleBeatGlyph.x = 0;
54639
+ soleBeatGlyph.applyCenterOffset(target);
54640
+ soleBeatGlyph.scaleToWidth(this.width);
54641
+ if (emit) this._emitBeatContainerSkyline(soleBeatGlyph);
54642
+ }
54643
+ return;
54644
+ }
54517
54645
  const positions = this.renderer.layoutingInfo.buildOnTimePositions(force);
54518
54646
  for (const beatGlyphs of this.beatGlyphs.values()) for (let i = 0, j = beatGlyphs.length; i < j; i++) {
54519
54647
  const currentBeatGlyph = beatGlyphs[i];
@@ -60835,14 +60963,22 @@
60835
60963
  calculateBeamY(h, x) {
60836
60964
  return this.calculateBeamYWithDirection(h, x, this.getBeamDirection(h));
60837
60965
  }
60966
+ barNumberGlyph;
60967
+ get barNumberWidth() {
60968
+ return this.barNumberGlyph?.width ?? 0;
60969
+ }
60838
60970
  createPreBeatGlyphs() {
60839
60971
  super.createPreBeatGlyphs();
60972
+ this.barNumberGlyph = void 0;
60840
60973
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
60841
60974
  this.createLinePreBeatGlyphs();
60842
60975
  let hasSpaceAfterStartGlyphs = false;
60843
60976
  if (this.index === 0) hasSpaceAfterStartGlyphs = this.createStartSpacing();
60844
- if (this.shouldCreateBarNumber()) this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1));
60845
- else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
60977
+ if (this.shouldCreateBarNumber()) {
60978
+ const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1);
60979
+ this.barNumberGlyph = barNumberGlyph;
60980
+ this.addPreBeatGlyph(barNumberGlyph);
60981
+ } else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
60846
60982
  }
60847
60983
  resolveClefDisplay() {
60848
60984
  return { isVisible: false };
@@ -64434,8 +64570,11 @@
64434
64570
  if (this.index === 0 || this.bar.masterBar.isRepeatStart && this.staff.isCascadePrimary) this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
64435
64571
  this.createLinePreBeatGlyphs();
64436
64572
  const hasSpaceAfterStartGlyphs = this.createStartSpacing();
64437
- if (this.shouldCreateBarNumber()) this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1));
64438
- else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
64573
+ if (this.shouldCreateBarNumber()) {
64574
+ const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1);
64575
+ this.barNumberGlyph = barNumberGlyph;
64576
+ this.addPreBeatGlyph(barNumberGlyph);
64577
+ } else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
64439
64578
  }
64440
64579
  createLinePreBeatGlyphs() {
64441
64580
  const timeSignatureDisplay = this.resolveTimeSignatureDisplay();