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

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.1869 (develop, build 1869)
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.1869";
194
+ static date = "2026-07-19T03:42:49.478Z";
195
+ static commit = "2889cac4e359a41b6f24a82034e6f6ba4f9f4c4f";
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--]);
@@ -48134,6 +48204,7 @@
48134
48204
  if (currentHandler !== void 0 && this._cursorHandlerMode === cursorHandlerMode) return;
48135
48205
  if (cursorHandlerMode) this._defaultCursorHandler = new ToNextBeatAnimatingCursorHandler();
48136
48206
  else this._defaultCursorHandler = new NonAnimatingCursorHandler();
48207
+ this._cursorHandlerMode = cursorHandlerMode;
48137
48208
  }
48138
48209
  _scrollHandlerMode = ScrollMode.Off;
48139
48210
  _scrollHandlerVertical = true;
@@ -48263,10 +48334,8 @@
48263
48334
  shouldScroll = !stop;
48264
48335
  shouldNotifyBeatChange = true;
48265
48336
  }
48266
- if (shouldScroll && !this._isBeatMouseDown && this.settings.player.scrollMode !== ScrollMode.Off) {
48267
- const handler = this.customScrollHandler ?? this._defaultScrollHandler;
48268
- if (handler) handler.onBeatCursorUpdating(beatBoundings, nextBeatBoundings === null ? void 0 : nextBeatBoundings, cursorMode, startBeatX, nextBeatX, duration);
48269
- }
48337
+ const handler = this.customScrollHandler ?? this._defaultScrollHandler;
48338
+ if (shouldScroll && !this._isBeatMouseDown && handler) handler.onBeatCursorUpdating(beatBoundings, nextBeatBoundings === null ? void 0 : nextBeatBoundings, cursorMode, startBeatX, nextBeatX, duration);
48270
48339
  if (shouldNotifyBeatChange) {
48271
48340
  this._onPlayedBeatChanged(beat);
48272
48341
  this._onActiveBeatsChanged(new ActiveBeatsChangedEventArgs(beatsToHighlight.map((i) => i.beat)));
@@ -48602,7 +48671,7 @@
48602
48671
  }
48603
48672
  _onBeatMouseDown(originalEvent, beat) {
48604
48673
  if (this._isDestroyed) return;
48605
- if (this._hasCursor && this.settings.player.enableUserInteraction) {
48674
+ if (this._hasCursor && this.settings.player.enablePlaybackRangeSelection) {
48606
48675
  this._selectionStart = { beat };
48607
48676
  this._selectionEnd = void 0;
48608
48677
  }
@@ -48618,10 +48687,12 @@
48618
48687
  }
48619
48688
  _onBeatMouseMove(originalEvent, beat) {
48620
48689
  if (this._isDestroyed) return;
48621
- if (this.settings.player.enableUserInteraction) {
48690
+ if (this.settings.player.enablePlaybackRangeSelection) {
48622
48691
  if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
48623
- this._selectionEnd = { beat };
48624
- this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48692
+ if (this._selectionStart?.beat !== beat) {
48693
+ this._selectionEnd = { beat };
48694
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48695
+ }
48625
48696
  }
48626
48697
  }
48627
48698
  this.beatMouseMove.trigger(beat);
@@ -48634,11 +48705,30 @@
48634
48705
  }
48635
48706
  _onBeatMouseUp(originalEvent, beat) {
48636
48707
  if (this._isDestroyed) return;
48637
- if (this._hasCursor && this.settings.player.enableUserInteraction) this.applyPlaybackRangeFromHighlight();
48708
+ if (this._hasCursor) {
48709
+ let shouldSeekToBeat = beat && this.settings.player.enableSeekToClick;
48710
+ if (this.settings.player.enablePlaybackRangeSelection) {
48711
+ if (this._internalApplyPlaybackRangeFromHighlight()) shouldSeekToBeat = false;
48712
+ }
48713
+ if (shouldSeekToBeat) this._seekToBeat(beat);
48714
+ }
48638
48715
  this.beatMouseUp.trigger(beat);
48639
48716
  this.uiFacade.triggerEvent(this.container, "beatMouseUp", beat, originalEvent);
48640
48717
  this._isBeatMouseDown = false;
48641
48718
  }
48719
+ _seekToBeat(beat) {
48720
+ const tickCache = this._tickCache;
48721
+ if (!tickCache) return;
48722
+ this._currentBeat = null;
48723
+ let beatTick = tickCache.getMasterBarStart(beat.voice.bar.masterBar) + (tickCache.getRelativeBeatPlaybackRange(beat)?.startTick ?? beat.playbackStart);
48724
+ const playbackRange = this.playbackRange;
48725
+ if (playbackRange) {
48726
+ if (beatTick < playbackRange.startTick) beatTick = playbackRange.startTick;
48727
+ else if (beatTick > playbackRange.endTick) beatTick = playbackRange.endTick;
48728
+ }
48729
+ if (this._player.state === PlayerState.Paused) this._cursorUpdateTick(beatTick, false, 1);
48730
+ this.tickPosition = beatTick;
48731
+ }
48642
48732
  _onNoteMouseUp(originalEvent, note) {
48643
48733
  if (this._isDestroyed) return;
48644
48734
  this.noteMouseUp.trigger(note);
@@ -48660,7 +48750,7 @@
48660
48750
  _setupClickHandling() {
48661
48751
  this.canvasElement.mouseDown.on((e) => {
48662
48752
  if (!e.isLeftMouseButton) return;
48663
- if (this.settings.player.enableUserInteraction) e.preventDefault();
48753
+ if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) e.preventDefault();
48664
48754
  const relX = e.getX(this.canvasElement);
48665
48755
  const relY = e.getY(this.canvasElement);
48666
48756
  const beat = this._renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null;
@@ -48687,7 +48777,7 @@
48687
48777
  });
48688
48778
  this.canvasElement.mouseUp.on((e) => {
48689
48779
  if (!this._isBeatMouseDown) return;
48690
- if (this.settings.player.enableUserInteraction) e.preventDefault();
48780
+ if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) e.preventDefault();
48691
48781
  const relX = e.getX(this.canvasElement);
48692
48782
  const relY = e.getY(this.canvasElement);
48693
48783
  const beat = this._renderer.boundsLookup?.getBeatAtPos(relX, relY) ?? null;
@@ -48698,7 +48788,7 @@
48698
48788
  } else this._onNoteMouseUp(e, null);
48699
48789
  });
48700
48790
  this._renderer.postRenderFinished.on(() => {
48701
- if (!this._selectionStart || !this._hasCursor || !this.settings.player.enableUserInteraction) return;
48791
+ if (!this._selectionStart || !this._hasCursor || !this.settings.player.enablePlaybackRangeSelection) return;
48702
48792
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48703
48793
  });
48704
48794
  }
@@ -48790,6 +48880,9 @@
48790
48880
  * ```
48791
48881
  */
48792
48882
  applyPlaybackRangeFromHighlight() {
48883
+ this._internalApplyPlaybackRangeFromHighlight();
48884
+ }
48885
+ _internalApplyPlaybackRangeFromHighlight() {
48793
48886
  if (this._selectionEnd) {
48794
48887
  const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ?? this._selectionStart.beat.absolutePlaybackStart;
48795
48888
  if ((this._tickCache?.getBeatStart(this._selectionEnd.beat) ?? this._selectionEnd.beat.absolutePlaybackStart) < startTick) {
@@ -48797,27 +48890,32 @@
48797
48890
  this._selectionStart = this._selectionEnd;
48798
48891
  this._selectionEnd = t;
48799
48892
  }
48800
- }
48893
+ } else if (!this.settings.player.resetPlaybackRangeOnClick) return false;
48801
48894
  if (this._selectionStart && this._tickCache) {
48802
48895
  const tickCache = this._tickCache;
48803
48896
  const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
48804
48897
  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;
48898
+ let seekToStart = this.settings.player.enableSeekToClick;
48808
48899
  if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
48900
+ seekToStart = true;
48809
48901
  const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
48810
48902
  const endBeatPlaybackEnd = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat)?.endTick ?? this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
48811
48903
  const range = new PlaybackRange();
48812
48904
  range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
48813
48905
  range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
48814
48906
  this.playbackRange = range;
48815
- } else {
48907
+ } else if (this.settings.player.resetPlaybackRangeOnClick) {
48816
48908
  this._selectionStart = void 0;
48817
48909
  this.playbackRange = null;
48818
48910
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
48819
48911
  }
48912
+ if (seekToStart) {
48913
+ this._currentBeat = null;
48914
+ if (this._player.state === PlayerState.Paused) this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
48915
+ this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
48916
+ }
48820
48917
  }
48918
+ return true;
48821
48919
  }
48822
48920
  /**
48823
48921
  * Clears the highlight markers marking the currently selected playback range.
@@ -50103,7 +50201,8 @@
50103
50201
  settings.player.enableCursor = false;
50104
50202
  settings.player.playerMode = PlayerMode.Disabled;
50105
50203
  settings.player.enableElementHighlighting = false;
50106
- settings.player.enableUserInteraction = false;
50204
+ settings.player.enableSeekToClick = false;
50205
+ settings.player.enablePlaybackRangeSelection = false;
50107
50206
  settings.player.soundFont = null;
50108
50207
  settings.display.scale = .8;
50109
50208
  settings.display.stretchForce = .8;
@@ -50913,7 +51012,9 @@
50913
51012
  }
50914
51013
  handleMessage(e) {
50915
51014
  const data = e.data;
50916
- switch (data.cmd) {
51015
+ const cmd = data.cmd;
51016
+ if (!cmd) return;
51017
+ switch (cmd) {
50917
51018
  case "alphaSynth.initialize":
50918
51019
  AlphaSynthWorkerSynthOutput.preferredSampleRate = data.sampleRate;
50919
51020
  Logger.logLevel = data.logLevel;
@@ -51007,7 +51108,7 @@
51007
51108
  this._player.applyTranspositionPitches(data.transpositionPitches);
51008
51109
  break;
51009
51110
  }
51010
- if (data.cmd.startsWith("alphaSynth.exporter")) this._handleExporterMessage(e);
51111
+ if (cmd.startsWith("alphaSynth.exporter")) this._handleExporterMessage(e);
51011
51112
  }
51012
51113
  _handleExporterMessage(ev) {
51013
51114
  const data = ev.data;
@@ -54511,9 +54612,35 @@
54511
54612
  const force = this.renderer.layoutingInfo.spaceToForce(width);
54512
54613
  this._scaleToForce(force, true);
54513
54614
  }
54615
+ /**
54616
+ * `true` when every voice/track/staff contributes exactly one beat, of uniform duration,
54617
+ * spanning the bar's entire duration - i.e. a single full-bar note/rest. Common engraving
54618
+ * practice centers such notes horizontally within the bar rather than anchoring them right
54619
+ * after the pre-beat content (Behind Bars, p. 41; see #2464).
54620
+ */
54621
+ _isCenteredFullBar() {
54622
+ const masterBar = this.renderer.bar.masterBar;
54623
+ const layoutingInfo = this.renderer.layoutingInfo;
54624
+ const springs = layoutingInfo.springs;
54625
+ if (springs.size !== 1 || !springs.has(masterBar.start) || layoutingInfo.allGraceRods.size > 0) return false;
54626
+ const spring = springs.get(masterBar.start);
54627
+ return spring.allDurations.size === 1 && spring.longestDuration === masterBar.calculateDuration();
54628
+ }
54514
54629
  /** `emit=false`: positioning-only path; final skyline emission runs later via {@link scaleToWidth}. */
54515
54630
  _scaleToForce(force, emit) {
54516
54631
  this.width = this.renderer.layoutingInfo.calculateVoiceWidth(force);
54632
+ if (this._isCenteredFullBar()) {
54633
+ const barNumberWidth = this.renderer.barNumberWidth;
54634
+ const target = (this.width - barNumberWidth) / 2;
54635
+ for (const beatGlyphs of this.beatGlyphs.values()) {
54636
+ const soleBeatGlyph = beatGlyphs[0];
54637
+ soleBeatGlyph.x = 0;
54638
+ soleBeatGlyph.applyCenterOffset(target);
54639
+ soleBeatGlyph.scaleToWidth(this.width);
54640
+ if (emit) this._emitBeatContainerSkyline(soleBeatGlyph);
54641
+ }
54642
+ return;
54643
+ }
54517
54644
  const positions = this.renderer.layoutingInfo.buildOnTimePositions(force);
54518
54645
  for (const beatGlyphs of this.beatGlyphs.values()) for (let i = 0, j = beatGlyphs.length; i < j; i++) {
54519
54646
  const currentBeatGlyph = beatGlyphs[i];
@@ -60835,14 +60962,22 @@
60835
60962
  calculateBeamY(h, x) {
60836
60963
  return this.calculateBeamYWithDirection(h, x, this.getBeamDirection(h));
60837
60964
  }
60965
+ barNumberGlyph;
60966
+ get barNumberWidth() {
60967
+ return this.barNumberGlyph?.width ?? 0;
60968
+ }
60838
60969
  createPreBeatGlyphs() {
60839
60970
  super.createPreBeatGlyphs();
60971
+ this.barNumberGlyph = void 0;
60840
60972
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
60841
60973
  this.createLinePreBeatGlyphs();
60842
60974
  let hasSpaceAfterStartGlyphs = false;
60843
60975
  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));
60976
+ if (this.shouldCreateBarNumber()) {
60977
+ const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1);
60978
+ this.barNumberGlyph = barNumberGlyph;
60979
+ this.addPreBeatGlyph(barNumberGlyph);
60980
+ } else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
60846
60981
  }
60847
60982
  resolveClefDisplay() {
60848
60983
  return { isVisible: false };
@@ -64434,8 +64569,11 @@
64434
64569
  if (this.index === 0 || this.bar.masterBar.isRepeatStart && this.staff.isCascadePrimary) this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
64435
64570
  this.createLinePreBeatGlyphs();
64436
64571
  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));
64572
+ if (this.shouldCreateBarNumber()) {
64573
+ const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-.5), this.bar.index + 1);
64574
+ this.barNumberGlyph = barNumberGlyph;
64575
+ this.addPreBeatGlyph(barNumberGlyph);
64576
+ } else if (!hasSpaceAfterStartGlyphs) this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
64439
64577
  }
64440
64578
  createLinePreBeatGlyphs() {
64441
64579
  const timeSignatureDisplay = this.resolveTimeSignatureDisplay();