@coderline/alphatab 1.8.0-alpha.1636 → 1.8.0-alpha.1639

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.8.0-alpha.1636 (develop, build 1636)
2
+ * alphaTab v1.8.0-alpha.1639 (develop, build 1639)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.8.0-alpha.1636';
207
- static date = '2025-12-06T02:04:52.462Z';
208
- static commit = '49c68ed64403080028c852fe371cc6bdcca8dc3a';
206
+ static version = '1.8.0-alpha.1639';
207
+ static date = '2025-12-09T02:16:01.440Z';
208
+ static commit = 'af86866e4f9d89a3ccc34006d01473a549dbbe7f';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -12950,25 +12950,31 @@ class Bounds {
12950
12950
  /**
12951
12951
  * Gets or sets the X-position of the rectangle within the music notation.
12952
12952
  */
12953
- x = 0;
12953
+ x;
12954
12954
  /**
12955
12955
  * Gets or sets the Y-position of the rectangle within the music notation.
12956
12956
  */
12957
- y = 0;
12957
+ y;
12958
12958
  /**
12959
12959
  * Gets or sets the width of the rectangle.
12960
12960
  */
12961
- w = 0;
12961
+ w;
12962
12962
  /**
12963
12963
  * Gets or sets the height of the rectangle.
12964
12964
  */
12965
- h = 0;
12965
+ h;
12966
12966
  scaleWith(scale) {
12967
12967
  this.x *= scale;
12968
12968
  this.y *= scale;
12969
12969
  this.w *= scale;
12970
12970
  this.h *= scale;
12971
12971
  }
12972
+ constructor(x = 0, y = 0, w = 0, h = 0) {
12973
+ this.x = x;
12974
+ this.y = y;
12975
+ this.h = h;
12976
+ this.w = w;
12977
+ }
12972
12978
  }
12973
12979
 
12974
12980
  /**
@@ -49454,16 +49460,6 @@ class ExternalMediaPlayer extends BackingTrackPlayer {
49454
49460
  }
49455
49461
  }
49456
49462
 
49457
- /**
49458
- * @internal
49459
- */
49460
- class SelectionInfo {
49461
- beat;
49462
- bounds = null;
49463
- constructor(beat) {
49464
- this.beat = beat;
49465
- }
49466
- }
49467
49463
  /**
49468
49464
  * This class represents the public API of alphaTab and provides all logic to display
49469
49465
  * a music sheet in any UI using the given {@link IUiFacade}
@@ -51563,8 +51559,8 @@ class AlphaTabApiBase {
51563
51559
  }
51564
51560
  _isBeatMouseDown = false;
51565
51561
  _isNoteMouseDown = false;
51566
- _selectionStart = null;
51567
- _selectionEnd = null;
51562
+ _selectionStart;
51563
+ _selectionEnd;
51568
51564
  /**
51569
51565
  * This event is fired whenever a the user presses the mouse button on a beat.
51570
51566
  * @eventProperty
@@ -51807,8 +51803,8 @@ class AlphaTabApiBase {
51807
51803
  return;
51808
51804
  }
51809
51805
  if (this._hasCursor && this.settings.player.enableUserInteraction) {
51810
- this._selectionStart = new SelectionInfo(beat);
51811
- this._selectionEnd = null;
51806
+ this._selectionStart = { beat };
51807
+ this._selectionEnd = undefined;
51812
51808
  }
51813
51809
  this._isBeatMouseDown = true;
51814
51810
  this.beatMouseDown.trigger(beat);
@@ -51828,7 +51824,7 @@ class AlphaTabApiBase {
51828
51824
  }
51829
51825
  if (this.settings.player.enableUserInteraction) {
51830
51826
  if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
51831
- this._selectionEnd = new SelectionInfo(beat);
51827
+ this._selectionEnd = { beat };
51832
51828
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51833
51829
  }
51834
51830
  }
@@ -51847,45 +51843,7 @@ class AlphaTabApiBase {
51847
51843
  return;
51848
51844
  }
51849
51845
  if (this._hasCursor && this.settings.player.enableUserInteraction) {
51850
- if (this._selectionEnd) {
51851
- const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ??
51852
- this._selectionStart.beat.absolutePlaybackStart;
51853
- const endTick = this._tickCache?.getBeatStart(this._selectionEnd.beat) ??
51854
- this._selectionEnd.beat.absolutePlaybackStart;
51855
- if (endTick < startTick) {
51856
- const t = this._selectionStart;
51857
- this._selectionStart = this._selectionEnd;
51858
- this._selectionEnd = t;
51859
- }
51860
- }
51861
- if (this._selectionStart && this._tickCache) {
51862
- // get the start and stop ticks (which consider properly repeats)
51863
- const tickCache = this._tickCache;
51864
- const realMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
51865
- // move to selection start
51866
- this._currentBeat = null; // reset current beat so it is updating the cursor
51867
- if (this._player.state === PlayerState.Paused) {
51868
- this._cursorUpdateTick(this._tickCache.getBeatStart(this._selectionStart.beat), false, 1);
51869
- }
51870
- this.tickPosition = realMasterBarStart + this._selectionStart.beat.playbackStart;
51871
- // set playback range
51872
- if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
51873
- const realMasterBarEnd = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
51874
- const range = new PlaybackRange();
51875
- range.startTick = realMasterBarStart + this._selectionStart.beat.playbackStart;
51876
- range.endTick =
51877
- realMasterBarEnd +
51878
- this._selectionEnd.beat.playbackStart +
51879
- this._selectionEnd.beat.playbackDuration -
51880
- 50;
51881
- this.playbackRange = range;
51882
- }
51883
- else {
51884
- this._selectionStart = null;
51885
- this.playbackRange = null;
51886
- this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51887
- }
51888
- }
51846
+ this.applyPlaybackRangeFromHighlight();
51889
51847
  }
51890
51848
  this.beatMouseUp.trigger(beat);
51891
51849
  this.uiFacade.triggerEvent(this.container, 'beatMouseUp', beat, originalEvent);
@@ -51907,13 +51865,13 @@ class AlphaTabApiBase {
51907
51865
  const startBeat = this._tickCache.findBeat(this._trackIndexLookup, range.startTick);
51908
51866
  const endBeat = this._tickCache.findBeat(this._trackIndexLookup, range.endTick);
51909
51867
  if (startBeat && endBeat) {
51910
- const selectionStart = new SelectionInfo(startBeat.beat);
51911
- const selectionEnd = new SelectionInfo(endBeat.beat);
51868
+ const selectionStart = { beat: startBeat.beat };
51869
+ const selectionEnd = { beat: endBeat.beat };
51912
51870
  this._cursorSelectRange(selectionStart, selectionEnd);
51913
51871
  }
51914
51872
  }
51915
51873
  else {
51916
- this._cursorSelectRange(null, null);
51874
+ this._cursorSelectRange(undefined, undefined);
51917
51875
  }
51918
51876
  }
51919
51877
  _setupClickHandling() {
@@ -51982,24 +51940,230 @@ class AlphaTabApiBase {
51982
51940
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51983
51941
  });
51984
51942
  }
51943
+ /**
51944
+ * Places the highlight markers at the specified start and end-beat range.
51945
+ * @param startBeat The start beat where the selection should start
51946
+ * @param endBeat The end beat where the selection should end.
51947
+ *
51948
+ * @remarks
51949
+ * Unlike actually setting {@link playbackRange} this method only places the selection markers without actually
51950
+ * changing the playback range. This method can be used when building custom selection systems (e.g. having draggable handles).
51951
+ *
51952
+ * @category Methods - Player
51953
+ * @since 1.8.0
51954
+ *
51955
+ * @example
51956
+ * JavaScript
51957
+ * ```js
51958
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
51959
+ * const startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0];
51960
+ * const endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0];
51961
+ * api.highlightPlaybackRange(startBeat, endBeat);
51962
+ * ```
51963
+ *
51964
+ * @example
51965
+ * C#
51966
+ * ```cs
51967
+ * var api = new AlphaTabApi<MyControl>(...);
51968
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[0], api.Score.Tracks[1] }, 1.5);
51969
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[2] }, 0.5);
51970
+ * var startBeat = api.Score.Tracks[0].Staves[0].Bars[0].Voices[0].Beats[0];
51971
+ * var endBeat = api.Score.Tracks[0].Staves[0].Bars[3].Voices[0].Beats[0];
51972
+ * api.HighlightPlaybackRange(startBeat, endBeat);
51973
+ * ```
51974
+ *
51975
+ * @example
51976
+ * Android
51977
+ * ```kotlin
51978
+ * val api = AlphaTabApi<MyControl>(...)
51979
+ * val startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0]
51980
+ * val endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0]
51981
+ * api.highlightPlaybackRange(startBeat, endBeat)
51982
+ * ```
51983
+ */
51984
+ highlightPlaybackRange(startBeat, endBeat) {
51985
+ this._selectionStart = { beat: startBeat };
51986
+ this._selectionEnd = { beat: endBeat };
51987
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51988
+ }
51989
+ /**
51990
+ * Applies the playback range from the currently highlighted range.
51991
+ *
51992
+ * @remarks
51993
+ * This method can be used when building custom selection systems (e.g. having draggable handles).
51994
+ *
51995
+ * @category Methods - Player
51996
+ * @since 1.8.0
51997
+ *
51998
+ * @example
51999
+ * JavaScript
52000
+ * ```js
52001
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52002
+ * const startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0];
52003
+ * const endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0];
52004
+ * api.highlightPlaybackRange(startBeat, endBeat);
52005
+ * api.applyPlaybackRangeFromHighlight();
52006
+ * ```
52007
+ *
52008
+ * @example
52009
+ * C#
52010
+ * ```cs
52011
+ * var api = new AlphaTabApi<MyControl>(...);
52012
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[0], api.Score.Tracks[1] }, 1.5);
52013
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[2] }, 0.5);
52014
+ * var startBeat = api.Score.Tracks[0].Staves[0].Bars[0].Voices[0].Beats[0];
52015
+ * var endBeat = api.Score.Tracks[0].Staves[0].Bars[3].Voices[0].Beats[0];
52016
+ * api.HighlightPlaybackRange(startBeat, endBeat);
52017
+ * api.ApplyPlaybackRangeFromHighlight();
52018
+ * ```
52019
+ *
52020
+ * @example
52021
+ * Android
52022
+ * ```kotlin
52023
+ * val api = AlphaTabApi<MyControl>(...)
52024
+ * val startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0]
52025
+ * val endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0]
52026
+ * api.highlightPlaybackRange(startBeat, endBeat)
52027
+ * api.applyPlaybackRangeFromHighlight()
52028
+ * ```
52029
+ */
52030
+ applyPlaybackRangeFromHighlight() {
52031
+ if (this._selectionEnd) {
52032
+ const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ??
52033
+ this._selectionStart.beat.absolutePlaybackStart;
52034
+ const endTick = this._tickCache?.getBeatStart(this._selectionEnd.beat) ??
52035
+ this._selectionEnd.beat.absolutePlaybackStart;
52036
+ if (endTick < startTick) {
52037
+ const t = this._selectionStart;
52038
+ this._selectionStart = this._selectionEnd;
52039
+ this._selectionEnd = t;
52040
+ }
52041
+ }
52042
+ if (this._selectionStart && this._tickCache) {
52043
+ // get the start and stop ticks (which consider properly repeats)
52044
+ const tickCache = this._tickCache;
52045
+ const realMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
52046
+ // move to selection start
52047
+ this._currentBeat = null; // reset current beat so it is updating the cursor
52048
+ if (this._player.state === PlayerState.Paused) {
52049
+ this._cursorUpdateTick(this._tickCache.getBeatStart(this._selectionStart.beat), false, 1);
52050
+ }
52051
+ this.tickPosition = realMasterBarStart + this._selectionStart.beat.playbackStart;
52052
+ // set playback range
52053
+ if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
52054
+ const realMasterBarEnd = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
52055
+ const range = new PlaybackRange();
52056
+ range.startTick = realMasterBarStart + this._selectionStart.beat.playbackStart;
52057
+ range.endTick =
52058
+ realMasterBarEnd +
52059
+ this._selectionEnd.beat.playbackStart +
52060
+ this._selectionEnd.beat.playbackDuration -
52061
+ 50;
52062
+ this.playbackRange = range;
52063
+ }
52064
+ else {
52065
+ this._selectionStart = undefined;
52066
+ this.playbackRange = null;
52067
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
52068
+ }
52069
+ }
52070
+ }
52071
+ /**
52072
+ * Clears the highlight markers marking the currently selected playback range.
52073
+ *
52074
+ * @remarks
52075
+ * Unlike actually setting {@link playbackRange} this method only clears the selection markers without actually
52076
+ * changing the playback range. This method can be used when building custom selection systems (e.g. having draggable handles).
52077
+ *
52078
+ * @category Methods - Player
52079
+ * @since 1.8.0
52080
+ *
52081
+ * @example
52082
+ * JavaScript
52083
+ * ```js
52084
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52085
+ * api.clearPlaybackRangeHighlight();
52086
+ * ```
52087
+ *
52088
+ * @example
52089
+ * C#
52090
+ * ```cs
52091
+ * var api = new AlphaTabApi<MyControl>(...);
52092
+ * api.clearPlaybackRangeHighlight();
52093
+ * ```
52094
+ *
52095
+ * @example
52096
+ * Android
52097
+ * ```kotlin
52098
+ * val api = AlphaTabApi<MyControl>(...)
52099
+ * api.clearPlaybackRangeHighlight()
52100
+ * ```
52101
+ */
52102
+ clearPlaybackRangeHighlight() {
52103
+ this._cursorSelectRange(undefined, undefined);
52104
+ }
52105
+ /**
52106
+ * This event is fired the shown highlights for the selected playback range changes.
52107
+ *
52108
+ * @remarks
52109
+ * This event is fired already during selection and not only when the selection is completed.
52110
+ * This event can be used to place additional custom selection markers (like drag handles).
52111
+ *
52112
+ * @eventProperty
52113
+ * @category Events - Player
52114
+ * @since 1.8.0
52115
+ *
52116
+ * @example
52117
+ * JavaScript
52118
+ * ```js
52119
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52120
+ * api.playbackRangeHighlightChanged.on(e => {
52121
+ * updateSelectionHandles(e);
52122
+ * });
52123
+ * ```
52124
+ *
52125
+ * @example
52126
+ * C#
52127
+ * ```cs
52128
+ * var api = new AlphaTabApi<MyControl>(...);
52129
+ * api.PlaybackRangeHighlightChanged.On(e =>
52130
+ * {
52131
+ * UpdateSelectionHandles(e);
52132
+ * });
52133
+ * ```
52134
+ *
52135
+ * @example
52136
+ * Android
52137
+ * ```kotlin
52138
+ * val api = AlphaTabApi<MyControl>(...)
52139
+ * api.playbackRangeHighlightChanged.on { e ->
52140
+ * updateSelectionHandles(e)
52141
+ * }
52142
+ * ```
52143
+ *
52144
+ */
52145
+ playbackRangeHighlightChanged = new EventEmitterOfT();
51985
52146
  _cursorSelectRange(startBeat, endBeat) {
51986
52147
  const cache = this._renderer.boundsLookup;
51987
52148
  if (!cache) {
52149
+ this.playbackRangeHighlightChanged.trigger({});
51988
52150
  return;
51989
52151
  }
51990
52152
  const selectionWrapper = this._selectionWrapper;
51991
52153
  if (!selectionWrapper) {
52154
+ this.playbackRangeHighlightChanged.trigger({});
51992
52155
  return;
51993
52156
  }
51994
52157
  selectionWrapper.clear();
51995
52158
  if (!startBeat || !endBeat || startBeat.beat === endBeat.beat) {
52159
+ this.playbackRangeHighlightChanged.trigger({});
51996
52160
  return;
51997
52161
  }
51998
52162
  if (!startBeat.bounds) {
51999
- startBeat.bounds = cache.findBeat(startBeat.beat);
52163
+ startBeat.bounds = cache.findBeat(startBeat.beat) ?? undefined;
52000
52164
  }
52001
52165
  if (!endBeat.bounds) {
52002
- endBeat.bounds = cache.findBeat(endBeat.beat);
52166
+ endBeat.bounds = cache.findBeat(endBeat.beat) ?? undefined;
52003
52167
  }
52004
52168
  const startTick = this._tickCache?.getBeatStart(startBeat.beat) ?? startBeat.beat.absolutePlaybackStart;
52005
52169
  const endTick = this._tickCache?.getBeatStart(endBeat.beat) ?? endBeat.beat.absolutePlaybackStart;
@@ -52008,7 +52172,17 @@ class AlphaTabApiBase {
52008
52172
  startBeat = endBeat;
52009
52173
  endBeat = t;
52010
52174
  }
52011
- const startX = startBeat.bounds.realBounds.x;
52175
+ const eventArgs = {
52176
+ startBeat: startBeat.beat,
52177
+ startBeatBounds: startBeat.bounds,
52178
+ endBeat: endBeat.beat,
52179
+ endBeatBounds: endBeat.bounds,
52180
+ highlightBlocks: []
52181
+ };
52182
+ let startX = startBeat.bounds.realBounds.x;
52183
+ if (startBeat.beat.index === 0) {
52184
+ startX = startBeat.bounds.barBounds.masterBarBounds.realBounds.x;
52185
+ }
52012
52186
  let endX = endBeat.bounds.realBounds.x + endBeat.bounds.realBounds.w;
52013
52187
  if (endBeat.beat.index === endBeat.beat.voice.beats.length - 1) {
52014
52188
  endX =
@@ -52025,18 +52199,24 @@ class AlphaTabApiBase {
52025
52199
  const staffEndX = startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.visualBounds.x +
52026
52200
  startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.visualBounds.w;
52027
52201
  const startSelection = this.uiFacade.createSelectionElement();
52028
- startSelection.setBounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, staffEndX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52202
+ const startSelectionBounds = new Bounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, staffEndX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52203
+ startSelection.setBounds(startSelectionBounds.x, startSelectionBounds.y, startSelectionBounds.w, startSelectionBounds.h);
52204
+ eventArgs.highlightBlocks.push(startSelectionBounds);
52029
52205
  selectionWrapper.appendChild(startSelection);
52030
52206
  const staffStartIndex = startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.index + 1;
52031
52207
  const staffEndIndex = endBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.index;
52032
52208
  for (let staffIndex = staffStartIndex; staffIndex < staffEndIndex; staffIndex++) {
52033
52209
  const staffBounds = cache.staffSystems[staffIndex];
52034
52210
  const middleSelection = this.uiFacade.createSelectionElement();
52035
- middleSelection.setBounds(staffStartX, staffBounds.visualBounds.y, staffEndX - staffStartX, staffBounds.visualBounds.h);
52211
+ const middleSelectionBounds = new Bounds(staffStartX, staffBounds.visualBounds.y, staffEndX - staffStartX, staffBounds.visualBounds.h);
52212
+ eventArgs.highlightBlocks.push(middleSelectionBounds);
52213
+ middleSelection.setBounds(middleSelectionBounds.x, middleSelectionBounds.y, middleSelectionBounds.w, middleSelectionBounds.h);
52036
52214
  selectionWrapper.appendChild(middleSelection);
52037
52215
  }
52038
52216
  const endSelection = this.uiFacade.createSelectionElement();
52039
- endSelection.setBounds(staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52217
+ const endSelectionBounds = new Bounds(staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52218
+ eventArgs.highlightBlocks.push(endSelectionBounds);
52219
+ endSelection.setBounds(endSelectionBounds.x, endSelectionBounds.y, endSelectionBounds.w, endSelectionBounds.h);
52040
52220
  selectionWrapper.appendChild(endSelection);
52041
52221
  }
52042
52222
  else {
@@ -52045,6 +52225,7 @@ class AlphaTabApiBase {
52045
52225
  selection.setBounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52046
52226
  selectionWrapper.appendChild(selection);
52047
52227
  }
52228
+ this.playbackRangeHighlightChanged.trigger(eventArgs);
52048
52229
  }
52049
52230
  /**
52050
52231
  * This event is fired whenever a new song is loaded.
@@ -56483,12 +56664,12 @@ class GroupedEffectGlyph extends EffectGlyph {
56483
56664
  this.endPosition = endPosition;
56484
56665
  }
56485
56666
  get isLinkedWithPrevious() {
56486
- return !!this.previousGlyph && this.previousGlyph.renderer.staff.system === this.renderer.staff.system;
56667
+ return !!this.previousGlyph && this.previousGlyph.renderer.staff?.system === this.renderer.staff.system;
56487
56668
  }
56488
56669
  get isLinkedWithNext() {
56489
56670
  return (!!this.nextGlyph &&
56490
56671
  this.nextGlyph.renderer.isFinalized &&
56491
- this.nextGlyph.renderer.staff.system === this.renderer.staff.system);
56672
+ this.nextGlyph.renderer.staff?.system === this.renderer.staff.system);
56492
56673
  }
56493
56674
  paint(cx, cy, canvas) {
56494
56675
  // if we are linked with the previous, the first glyph of the group will also render this one.
@@ -58876,6 +59057,437 @@ class LeftToRightLayoutingGlyphGroup extends GlyphGroup {
58876
59057
  }
58877
59058
  }
58878
59059
 
59060
+ /**
59061
+ * @internal
59062
+ */
59063
+ class TieGlyph extends Glyph {
59064
+ tieDirection = BeamDirection.Up;
59065
+ slurEffectId;
59066
+ isForEnd;
59067
+ constructor(slurEffectId, forEnd) {
59068
+ super(0, 0);
59069
+ this.slurEffectId = slurEffectId;
59070
+ this.isForEnd = forEnd;
59071
+ }
59072
+ _startX = 0;
59073
+ _startY = 0;
59074
+ _endX = 0;
59075
+ _endY = 0;
59076
+ _tieHeight = 0;
59077
+ _boundingBox;
59078
+ _shouldPaint = false;
59079
+ get checkForOverflow() {
59080
+ return this._shouldPaint && this._boundingBox !== undefined;
59081
+ }
59082
+ getBoundingBoxTop() {
59083
+ if (this._boundingBox) {
59084
+ return this._boundingBox.y;
59085
+ }
59086
+ return this._startY;
59087
+ }
59088
+ getBoundingBoxBottom() {
59089
+ if (this._boundingBox) {
59090
+ return this._boundingBox.y + this._boundingBox.h;
59091
+ }
59092
+ return this._startY;
59093
+ }
59094
+ doLayout() {
59095
+ this.width = 0;
59096
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59097
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59098
+ this._startX = 0;
59099
+ this._endX = 0;
59100
+ this._startY = 0;
59101
+ this._endY = 0;
59102
+ this.height = 0;
59103
+ // if we are on the tie start, we check if we
59104
+ // either can draw till the end note, or we just can draw till the bar end
59105
+ this.tieDirection = this.calculateTieDirection();
59106
+ const forEnd = this.isForEnd;
59107
+ this._shouldPaint = false;
59108
+ if (!forEnd) {
59109
+ if (startNoteRenderer !== endNoteRenderer) {
59110
+ this._startX = this.calculateStartX();
59111
+ this._startY = this.calculateStartY();
59112
+ if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
59113
+ const lastRendererInStaff = startNoteRenderer.staff.barRenderers[startNoteRenderer.staff.barRenderers.length - 1];
59114
+ this._endX = lastRendererInStaff.x + lastRendererInStaff.width;
59115
+ this._endY = this._startY;
59116
+ startNoteRenderer.scoreRenderer.layout.slurRegistry.startMultiSystemSlur(this);
59117
+ }
59118
+ else {
59119
+ this._endX = this.calculateEndX();
59120
+ this._endY = this.caclculateEndY();
59121
+ }
59122
+ }
59123
+ else {
59124
+ this._shouldPaint = true;
59125
+ this._startX = this.calculateStartX();
59126
+ this._endX = this.calculateEndX();
59127
+ this._startY = this.calculateStartY();
59128
+ this._endY = this.caclculateEndY();
59129
+ }
59130
+ this._shouldPaint = true;
59131
+ }
59132
+ else if (startNoteRenderer.staff !== endNoteRenderer.staff) {
59133
+ const firstRendererInStaff = startNoteRenderer.staff.barRenderers[0];
59134
+ this._startX = firstRendererInStaff.x;
59135
+ this._endX = this.calculateEndX();
59136
+ const startGlyph = startNoteRenderer.scoreRenderer.layout.slurRegistry.completeMultiSystemSlur(this);
59137
+ if (startGlyph) {
59138
+ this._startY = startGlyph.calculateMultiSystemSlurY(endNoteRenderer);
59139
+ }
59140
+ else {
59141
+ this._startY = this.caclculateEndY();
59142
+ }
59143
+ this._endY = this.caclculateEndY();
59144
+ this._shouldPaint = startNoteRenderer.staff !== endNoteRenderer.staff;
59145
+ }
59146
+ this._boundingBox = undefined;
59147
+ this.y = Math.min(this._startY, this._endY);
59148
+ if (this.shouldDrawBendSlur()) {
59149
+ this._tieHeight = 0; // TODO: Bend slur height to be considered?
59150
+ }
59151
+ else {
59152
+ this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
59153
+ const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
59154
+ this._boundingBox = tieBoundingBox;
59155
+ this.height = tieBoundingBox.h;
59156
+ if (this.tieDirection === BeamDirection.Up) {
59157
+ // the tie might go above `this.y` due to its shape
59158
+ // here we calculate how much this is so we can consider the
59159
+ // respective overflow
59160
+ const overlap = this.y - tieBoundingBox.y;
59161
+ if (overlap > 0) {
59162
+ this.y -= overlap;
59163
+ }
59164
+ }
59165
+ }
59166
+ }
59167
+ paint(cx, cy, canvas) {
59168
+ if (!this._shouldPaint) {
59169
+ return;
59170
+ }
59171
+ if (this.shouldDrawBendSlur()) {
59172
+ TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
59173
+ }
59174
+ else {
59175
+ TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
59176
+ }
59177
+ }
59178
+ getTieHeight(_startX, _startY, _endX, _endY) {
59179
+ return this.renderer.smuflMetrics.tieHeight;
59180
+ }
59181
+ calculateMultiSystemSlurY(renderer) {
59182
+ const startRenderer = this.lookupStartBeatRenderer();
59183
+ const startY = this.calculateStartY();
59184
+ const relY = startY - startRenderer.y;
59185
+ return renderer.y + relY;
59186
+ }
59187
+ shouldCreateMultiSystemSlur(renderer) {
59188
+ const endStaff = this.lookupEndBeatRenderer()?.staff;
59189
+ if (!endStaff) {
59190
+ return true;
59191
+ }
59192
+ return renderer.staff.system.index < endStaff.system.index;
59193
+ }
59194
+ static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
59195
+ const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59196
+ if (cp.length === 0) {
59197
+ return new Bounds(x1, y1, x2 - x1, y2 - y1);
59198
+ }
59199
+ // For a musical tie/slur, the extrema occur predictably near the midpoint
59200
+ // Evaluate at midpoint (t=0.5) and check endpoints
59201
+ const p0x = cp[0];
59202
+ const p0y = cp[1];
59203
+ const c1x = cp[2];
59204
+ const c1y = cp[3];
59205
+ const c2x = cp[4];
59206
+ const c2y = cp[5];
59207
+ const p1x = cp[6];
59208
+ const p1y = cp[7];
59209
+ // Evaluate at t=0.5 for midpoint
59210
+ const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
59211
+ const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
59212
+ // Bounds are simply min/max of start, end, and midpoint
59213
+ const xMin = Math.min(p0x, p1x, midX);
59214
+ const xMax = Math.max(p0x, p1x, midX);
59215
+ let yMin = Math.min(p0y, p1y, midY);
59216
+ let yMax = Math.max(p0y, p1y, midY);
59217
+ // Account for thickness of the tie/slur
59218
+ if (down) {
59219
+ yMax += size;
59220
+ }
59221
+ else {
59222
+ yMin -= size;
59223
+ }
59224
+ const b = new Bounds();
59225
+ b.x = xMin;
59226
+ b.y = yMin;
59227
+ b.w = xMax - xMin;
59228
+ b.h = yMax - yMin;
59229
+ return b;
59230
+ }
59231
+ static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
59232
+ if (x1 === x2 && y1 === y2) {
59233
+ return [];
59234
+ }
59235
+ // ensure endX > startX
59236
+ if (x2 < x1) {
59237
+ let t = x1;
59238
+ x1 = x2;
59239
+ x2 = t;
59240
+ t = y1;
59241
+ y1 = y2;
59242
+ y2 = t;
59243
+ }
59244
+ //
59245
+ // calculate control points
59246
+ //
59247
+ offset *= scale;
59248
+ size *= scale;
59249
+ if (down) {
59250
+ offset *= -1;
59251
+ size *= -1;
59252
+ }
59253
+ if (scale >= 1) {
59254
+ size *= 1.2;
59255
+ }
59256
+ // calculate control points on horizontal axis then rotate:
59257
+ /*
59258
+ cp1x/cpy1 cp2x/cpy2
59259
+ *----------------*
59260
+ / \
59261
+ / \
59262
+ x1/y1 * * x2/y2
59263
+
59264
+ cp3 and cp4 are simply with lower height
59265
+ */
59266
+ const dY = y2 - y1;
59267
+ const dX = x2 - x1;
59268
+ const length = Math.sqrt(dX * dX + dY * dY);
59269
+ let cp1x = x1 + length * 0.25;
59270
+ let cp1y = y1 - offset;
59271
+ let cp2x = x1 + length * 0.75;
59272
+ let cp2y = y1 - offset;
59273
+ let cp3x = x1 + length * 0.75;
59274
+ let cp3y = y1 - offset - size;
59275
+ let cp4x = x1 + length * 0.25;
59276
+ let cp4y = y1 - offset - size;
59277
+ const angle = Math.atan2(dY, dX);
59278
+ [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
59279
+ [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
59280
+ [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
59281
+ [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
59282
+ return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
59283
+ }
59284
+ static _rotate(x, y, rotateX, rotateY, angle) {
59285
+ const dx = x - rotateX;
59286
+ const dy = y - rotateY;
59287
+ const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
59288
+ const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
59289
+ return [rotateX + rx, rotateY + ry];
59290
+ }
59291
+ static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
59292
+ const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59293
+ canvas.beginPath();
59294
+ canvas.moveTo(cps[0], cps[1]);
59295
+ canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
59296
+ canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
59297
+ canvas.closePath();
59298
+ canvas.fill();
59299
+ }
59300
+ static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
59301
+ let normalVectorX = y2 - y1;
59302
+ let normalVectorY = x2 - x1;
59303
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59304
+ if (down) {
59305
+ normalVectorX *= -1;
59306
+ }
59307
+ else {
59308
+ normalVectorY *= -1;
59309
+ }
59310
+ // make to unit vector
59311
+ normalVectorX /= length;
59312
+ normalVectorY /= length;
59313
+ let offset = bendSlurHeight * scale;
59314
+ if (x2 - x1 < 20) {
59315
+ offset /= 2;
59316
+ }
59317
+ const centerY = (y2 + y1) / 2;
59318
+ const cp1Y = centerY + offset * normalVectorY;
59319
+ return cp1Y;
59320
+ }
59321
+ static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
59322
+ let normalVectorX = y2 - y1;
59323
+ let normalVectorY = x2 - x1;
59324
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59325
+ if (down) {
59326
+ normalVectorX *= -1;
59327
+ }
59328
+ else {
59329
+ normalVectorY *= -1;
59330
+ }
59331
+ // make to unit vector
59332
+ normalVectorX /= length;
59333
+ normalVectorY /= length;
59334
+ // center of connection
59335
+ // TODO: should be 1/3
59336
+ const centerX = (x2 + x1) / 2;
59337
+ const centerY = (y2 + y1) / 2;
59338
+ let offset = bendSlurHeight * scale;
59339
+ if (x2 - x1 < 20) {
59340
+ offset /= 2;
59341
+ }
59342
+ const cp1X = centerX + offset * normalVectorX;
59343
+ const cp1Y = centerY + offset * normalVectorY;
59344
+ canvas.beginPath();
59345
+ canvas.moveTo(x1, y1);
59346
+ canvas.lineTo(cp1X, cp1Y);
59347
+ canvas.lineTo(x2, y2);
59348
+ canvas.stroke();
59349
+ if (slurText) {
59350
+ const w = canvas.measureText(slurText).width;
59351
+ const textOffset = down ? 0 : -canvas.font.size;
59352
+ canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
59353
+ }
59354
+ }
59355
+ }
59356
+ /**
59357
+ * A common tie implementation using note details for positioning
59358
+ * @internal
59359
+ */
59360
+ class NoteTieGlyph extends TieGlyph {
59361
+ startNote;
59362
+ endNote;
59363
+ startNoteRenderer = null;
59364
+ endNoteRenderer = null;
59365
+ constructor(slurEffectId, startNote, endNote, forEnd) {
59366
+ super(slurEffectId, forEnd);
59367
+ this.startNote = startNote;
59368
+ this.endNote = endNote;
59369
+ }
59370
+ get isLeftHandTap() {
59371
+ return this.startNote === this.endNote;
59372
+ }
59373
+ getTieHeight(startX, startY, endX, endY) {
59374
+ if (this.isLeftHandTap) {
59375
+ return this.renderer.smuflMetrics.tieHeight;
59376
+ }
59377
+ return super.getTieHeight(startX, startY, endX, endY);
59378
+ }
59379
+ calculateTieDirection() {
59380
+ // invert direction (if stems go up, ties go down to not cross them)
59381
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat)) {
59382
+ case BeamDirection.Up:
59383
+ return BeamDirection.Down;
59384
+ default:
59385
+ return BeamDirection.Up;
59386
+ }
59387
+ }
59388
+ calculateStartX() {
59389
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59390
+ if (this.isLeftHandTap) {
59391
+ return this.calculateEndX() - startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
59392
+ }
59393
+ return startNoteRenderer.x + startNoteRenderer.getNoteX(this.startNote, this.getStartNotePosition());
59394
+ }
59395
+ getStartNotePosition() {
59396
+ return NoteXPosition.Center;
59397
+ }
59398
+ calculateStartY() {
59399
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59400
+ if (this.isLeftHandTap) {
59401
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
59402
+ }
59403
+ switch (this.tieDirection) {
59404
+ case BeamDirection.Up:
59405
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
59406
+ default:
59407
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
59408
+ }
59409
+ }
59410
+ calculateEndX() {
59411
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59412
+ if (!endNoteRenderer) {
59413
+ return this.calculateStartY() + this.renderer.smuflMetrics.leftHandTabTieWidth;
59414
+ }
59415
+ if (this.isLeftHandTap) {
59416
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
59417
+ }
59418
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
59419
+ }
59420
+ getEndNotePosition() {
59421
+ return NoteXPosition.Center;
59422
+ }
59423
+ caclculateEndY() {
59424
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59425
+ if (!endNoteRenderer) {
59426
+ return this.calculateStartY();
59427
+ }
59428
+ if (this.isLeftHandTap) {
59429
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
59430
+ }
59431
+ switch (this.tieDirection) {
59432
+ case BeamDirection.Up:
59433
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
59434
+ default:
59435
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
59436
+ }
59437
+ }
59438
+ lookupEndBeatRenderer() {
59439
+ if (!this.endNoteRenderer) {
59440
+ this.endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endNote.beat.voice.bar);
59441
+ }
59442
+ return this.endNoteRenderer;
59443
+ }
59444
+ lookupStartBeatRenderer() {
59445
+ if (!this.startNoteRenderer) {
59446
+ this.startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startNote.beat.voice.bar);
59447
+ }
59448
+ return this.startNoteRenderer;
59449
+ }
59450
+ shouldDrawBendSlur() {
59451
+ return false;
59452
+ }
59453
+ }
59454
+ /**
59455
+ * A tie glyph for continued multi-system ties/slurs
59456
+ * @internal
59457
+ */
59458
+ class ContinuationTieGlyph extends TieGlyph {
59459
+ _startTie;
59460
+ constructor(startTie) {
59461
+ super(startTie.slurEffectId, false);
59462
+ this._startTie = startTie;
59463
+ }
59464
+ lookupStartBeatRenderer() {
59465
+ return this.renderer;
59466
+ }
59467
+ lookupEndBeatRenderer() {
59468
+ return this.renderer;
59469
+ }
59470
+ shouldDrawBendSlur() {
59471
+ return false;
59472
+ }
59473
+ calculateTieDirection() {
59474
+ return this._startTie.tieDirection;
59475
+ }
59476
+ calculateStartY() {
59477
+ return this._startTie.calculateMultiSystemSlurY(this.renderer);
59478
+ }
59479
+ caclculateEndY() {
59480
+ return this.calculateStartY();
59481
+ }
59482
+ calculateStartX() {
59483
+ return this.renderer.staff.barRenderers[0].x;
59484
+ }
59485
+ calculateEndX() {
59486
+ const last = this.renderer.staff.barRenderers[this.renderer.staff.barRenderers.length - 1];
59487
+ return last.x + last.width;
59488
+ }
59489
+ }
59490
+
58879
59491
  /**
58880
59492
  * This glyph acts as container for handling
58881
59493
  * multiple voice rendering
@@ -59346,6 +59958,63 @@ class TuningGlyph extends GlyphGroup {
59346
59958
  }
59347
59959
  }
59348
59960
 
59961
+ /**
59962
+ * This registry keeps track of which slurs and ties were started and needs completion.
59963
+ * Slurs might span multiple systems, and in such cases we need to create additional
59964
+ * slur/ties in the intermediate and end system.
59965
+ *
59966
+ * @internal
59967
+ *
59968
+ */
59969
+ class SlurRegistry {
59970
+ _staffLookup = new Map();
59971
+ clear() {
59972
+ this._staffLookup.clear();
59973
+ }
59974
+ startMultiSystemSlur(startGlyph) {
59975
+ const staffId = SlurRegistry._staffId(startGlyph.renderer.staff);
59976
+ let container;
59977
+ if (!this._staffLookup.has(staffId)) {
59978
+ container = {
59979
+ startedSlurs: new Map()
59980
+ };
59981
+ this._staffLookup.set(staffId, container);
59982
+ }
59983
+ else {
59984
+ container = this._staffLookup.get(staffId);
59985
+ }
59986
+ container.startedSlurs.set(startGlyph.slurEffectId, { startGlyph });
59987
+ }
59988
+ static _staffId(staff) {
59989
+ return `${staff.modelStaff.index}.${staff.modelStaff.track.index}.${staff.staffId}`;
59990
+ }
59991
+ completeMultiSystemSlur(endGlyph) {
59992
+ const staffId = SlurRegistry._staffId(endGlyph.renderer.staff);
59993
+ if (!this._staffLookup.has(staffId)) {
59994
+ return undefined;
59995
+ }
59996
+ const container = this._staffLookup.get(staffId);
59997
+ if (container.startedSlurs.has(endGlyph.slurEffectId)) {
59998
+ const info = container.startedSlurs.get(endGlyph.slurEffectId);
59999
+ info.endGlyph = endGlyph;
60000
+ return info.startGlyph;
60001
+ }
60002
+ return undefined;
60003
+ }
60004
+ *getAllContinuations(renderer) {
60005
+ const staffId = SlurRegistry._staffId(renderer.staff);
60006
+ if (!this._staffLookup.has(staffId) || renderer.index > 0) {
60007
+ return;
60008
+ }
60009
+ const container = this._staffLookup.get(staffId);
60010
+ for (const g of container.startedSlurs.values()) {
60011
+ if (g.startGlyph.shouldCreateMultiSystemSlur(renderer)) {
60012
+ yield g.startGlyph;
60013
+ }
60014
+ }
60015
+ }
60016
+ }
60017
+
59349
60018
  /**
59350
60019
  * A Staff represents a single line within a StaffSystem.
59351
60020
  * It stores BarRenderer instances created from a given factory.
@@ -59467,7 +60136,6 @@ class RenderStaff {
59467
60136
  this._sharedLayoutData = new Map();
59468
60137
  const lastBar = this.barRenderers[this.barRenderers.length - 1];
59469
60138
  this.barRenderers.splice(this.barRenderers.length - 1, 1);
59470
- this.system.layout.unregisterBarRenderer(this.staffId, lastBar);
59471
60139
  this.topOverflow = 0;
59472
60140
  this.bottomOverflow = 0;
59473
60141
  for (const r of this.barRenderers) {
@@ -59560,23 +60228,23 @@ class RenderStaff {
59560
60228
  // changes in the overflows
59561
60229
  let needsSecondPass = false;
59562
60230
  let topOverflow = this.topOverflow;
59563
- for (let i = 0; i < this.barRenderers.length; i++) {
59564
- this.barRenderers[i].y = this.topPadding + topOverflow;
59565
- if (this.barRenderers[i].finalizeRenderer()) {
60231
+ for (const renderer of this.barRenderers) {
60232
+ renderer.registerMultiSystemSlurs(this.system.layout.slurRegistry.getAllContinuations(renderer));
60233
+ if (renderer.finalizeRenderer()) {
59566
60234
  needsSecondPass = true;
59567
60235
  }
59568
- this.height = Math.max(this.height, this.barRenderers[i].height);
60236
+ this.height = Math.max(this.height, renderer.height);
59569
60237
  }
59570
60238
  // 2nd pass: move renderers to correct position respecting the new overflows
59571
60239
  if (needsSecondPass) {
59572
60240
  topOverflow = this.topOverflow;
59573
60241
  // shift all the renderers to the new position to match required spacing
59574
- for (let i = 0; i < this.barRenderers.length; i++) {
59575
- this.barRenderers[i].y = this.topPadding + topOverflow;
60242
+ for (const renderer of this.barRenderers) {
60243
+ renderer.y = this.topPadding + topOverflow;
59576
60244
  }
59577
60245
  // finalize again (to align ties)
59578
- for (let i = 0; i < this.barRenderers.length; i++) {
59579
- this.barRenderers[i].finalizeRenderer();
60246
+ for (const renderer of this.barRenderers) {
60247
+ renderer.finalizeRenderer();
59580
60248
  }
59581
60249
  }
59582
60250
  if (this.height > 0) {
@@ -60182,6 +60850,7 @@ class StaffSystem {
60182
60850
  if (newBarDisplayScale > barDisplayScale) {
60183
60851
  barDisplayScale = newBarDisplayScale;
60184
60852
  }
60853
+ lastBar.afterReverted();
60185
60854
  }
60186
60855
  this.width -= width;
60187
60856
  this.computedWidth -= width;
@@ -60701,12 +61370,16 @@ class ScoreLayout {
60701
61370
  constructor(renderer) {
60702
61371
  this.renderer = renderer;
60703
61372
  }
61373
+ slurRegistry = new SlurRegistry();
60704
61374
  resize() {
60705
61375
  this._lazyPartials.clear();
61376
+ this.slurRegistry.clear();
60706
61377
  this.doResize();
60707
61378
  }
60708
61379
  layoutAndRender() {
60709
61380
  this._lazyPartials.clear();
61381
+ this.slurRegistry.clear();
61382
+ this._barRendererLookup.clear();
60710
61383
  this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
60711
61384
  const score = this.renderer.score;
60712
61385
  this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
@@ -60975,17 +61648,6 @@ class ScoreLayout {
60975
61648
  }
60976
61649
  }
60977
61650
  }
60978
- unregisterBarRenderer(key, renderer) {
60979
- if (this._barRendererLookup.has(key)) {
60980
- const lookup = this._barRendererLookup.get(key);
60981
- lookup.delete(renderer.bar.id);
60982
- if (renderer.additionalMultiRestBars) {
60983
- for (const b of renderer.additionalMultiRestBars) {
60984
- lookup.delete(b.id);
60985
- }
60986
- }
60987
- }
60988
- }
60989
61651
  getRendererForBar(key, bar) {
60990
61652
  const barRendererId = bar.id;
60991
61653
  if (this._barRendererLookup.has(key) && this._barRendererLookup.get(key).has(barRendererId)) {
@@ -61974,6 +62636,7 @@ class BarRendererBase {
61974
62636
  _voiceContainers = new Map();
61975
62637
  _postBeatGlyphs = new LeftToRightLayoutingGlyphGroup();
61976
62638
  _ties = [];
62639
+ _multiSystemSlurs;
61977
62640
  topEffects;
61978
62641
  bottomEffects;
61979
62642
  get nextRenderer() {
@@ -62126,6 +62789,11 @@ class BarRendererBase {
62126
62789
  }
62127
62790
  }
62128
62791
  _appliedLayoutingInfo = 0;
62792
+ afterReverted() {
62793
+ this.staff = undefined;
62794
+ this.registerMultiSystemSlurs(undefined);
62795
+ this.isFinalized = false;
62796
+ }
62129
62797
  afterStaffBarReverted() {
62130
62798
  this.topEffects.afterStaffBarReverted();
62131
62799
  this.bottomEffects.afterStaffBarReverted();
@@ -62171,13 +62839,26 @@ class BarRendererBase {
62171
62839
  return true;
62172
62840
  }
62173
62841
  isFinalized = false;
62174
- finalizeRenderer() {
62175
- this.isFinalized = true;
62842
+ registerMultiSystemSlurs(startedTies) {
62843
+ if (!startedTies) {
62844
+ this._multiSystemSlurs = undefined;
62845
+ return;
62846
+ }
62847
+ let ties = undefined;
62848
+ for (const g of startedTies) {
62849
+ const continuation = new ContinuationTieGlyph(g);
62850
+ continuation.renderer = this;
62851
+ continuation.tieDirection = g.tieDirection;
62852
+ if (!ties) {
62853
+ ties = [];
62854
+ }
62855
+ ties.push(continuation);
62856
+ }
62857
+ this._multiSystemSlurs = ties;
62858
+ }
62859
+ _finalizeTies(ties, barTop, barBottom) {
62176
62860
  let didChangeOverflows = false;
62177
- // allow spacing to be used for tie overflows
62178
- const barTop = this.y;
62179
- const barBottom = this.y + this.height;
62180
- for (const t of this._ties) {
62861
+ for (const t of ties) {
62181
62862
  const tie = t;
62182
62863
  tie.doLayout();
62183
62864
  if (t.checkForOverflow) {
@@ -62198,6 +62879,21 @@ class BarRendererBase {
62198
62879
  }
62199
62880
  }
62200
62881
  }
62882
+ return didChangeOverflows;
62883
+ }
62884
+ finalizeRenderer() {
62885
+ this.isFinalized = true;
62886
+ let didChangeOverflows = false;
62887
+ // allow spacing to be used for tie overflows
62888
+ const barTop = this.y;
62889
+ const barBottom = this.y + this.height;
62890
+ if (this._finalizeTies(this._ties, barTop, barBottom)) {
62891
+ didChangeOverflows = true;
62892
+ }
62893
+ const multiSystemSlurs = this._multiSystemSlurs;
62894
+ if (multiSystemSlurs && this._finalizeTies(multiSystemSlurs, barTop, barBottom)) {
62895
+ didChangeOverflows = true;
62896
+ }
62201
62897
  const topHeightChanged = this.topEffects.finalizeEffects();
62202
62898
  const bottomHeightChanged = this.bottomEffects.finalizeEffects();
62203
62899
  if (topHeightChanged || bottomHeightChanged) {
@@ -62378,6 +63074,16 @@ class BarRendererBase {
62378
63074
  }
62379
63075
  canvas.color = this.resources.mainGlyphColor;
62380
63076
  this._postBeatGlyphs.paint(cx + this.x, cy + this.y, canvas);
63077
+ this._paintMultiSystemSlurs(cx, cy, canvas);
63078
+ }
63079
+ _paintMultiSystemSlurs(cx, cy, canvas) {
63080
+ const multiSystemSlurs = this._multiSystemSlurs;
63081
+ if (!multiSystemSlurs) {
63082
+ return;
63083
+ }
63084
+ for (const slur of multiSystemSlurs) {
63085
+ slur.paint(cx, cy, canvas);
63086
+ }
62381
63087
  }
62382
63088
  paintBackground(cx, cy, canvas) {
62383
63089
  this.layoutingInfo.paint(cx + this.x + this._preBeatGlyphs.x + this._preBeatGlyphs.width, cy + this.y + this.height, canvas);
@@ -64456,6 +65162,13 @@ class PageViewLayout extends ScoreLayout {
64456
65162
  }
64457
65163
  }
64458
65164
  else {
65165
+ // clear out staves during re-layout, this info is outdated during
65166
+ // re-layout of the bars
65167
+ for (const r of this._allMasterBarRenderers) {
65168
+ for (const b of r.renderers) {
65169
+ b.afterReverted();
65170
+ }
65171
+ }
64459
65172
  this._systems = [];
64460
65173
  let currentIndex = 0;
64461
65174
  const maxWidth = this._maxWidth;
@@ -64917,7 +65630,7 @@ class BarLineGlyph extends LeftToRightLayoutingGlyphGroup {
64917
65630
  // as during layout things are still moving
64918
65631
  let actualLineHeight = this.height;
64919
65632
  const thisStaff = renderer.staff;
64920
- const allStaves = renderer.staff.system.allStaves;
65633
+ const allStaves = thisStaff.system.allStaves;
64921
65634
  let isExtended = false;
64922
65635
  if (this._extendToNextStaff && thisStaff.index < allStaves.length - 1) {
64923
65636
  const nextStaff = allStaves[thisStaff.index + 1];
@@ -65099,7 +65812,7 @@ class LineBarRenderer extends BarRendererBase {
65099
65812
  }
65100
65813
  // during system fitting it can happen that we have fraction widths
65101
65814
  // but to have lines until the full end-pixel we round up.
65102
- // this way we avoid holes,
65815
+ // this way we avoid holes,
65103
65816
  const lineWidth = this.width;
65104
65817
  // we want the lines to be exactly virtually aligned with the respective Y-position
65105
65818
  // for note heads to align correctly
@@ -65865,375 +66578,23 @@ class LineBarRenderer extends BarRendererBase {
65865
66578
  /**
65866
66579
  * @internal
65867
66580
  */
65868
- class TieGlyph extends Glyph {
65869
- startBeat;
65870
- endBeat;
65871
- yOffset = 0;
65872
- forEnd;
65873
- startNoteRenderer = null;
65874
- endNoteRenderer = null;
65875
- tieDirection = BeamDirection.Up;
65876
- constructor(startBeat, endBeat, forEnd) {
65877
- super(0, 0);
65878
- this.startBeat = startBeat;
65879
- this.endBeat = endBeat;
65880
- this.forEnd = forEnd;
65881
- }
65882
- _startX = 0;
65883
- _startY = 0;
65884
- _endX = 0;
65885
- _endY = 0;
65886
- _tieHeight = 0;
65887
- _shouldDraw = false;
65888
- _boundingBox;
65889
- get checkForOverflow() {
65890
- return this._boundingBox !== undefined;
65891
- }
65892
- getBoundingBoxTop() {
65893
- if (this._boundingBox) {
65894
- return this._boundingBox.y;
65895
- }
65896
- return this._startY;
65897
- }
65898
- getBoundingBoxBottom() {
65899
- if (this._boundingBox) {
65900
- return this._boundingBox.y + this._boundingBox.h;
65901
- }
65902
- return this._startY;
65903
- }
65904
- doLayout() {
65905
- this.width = 0;
65906
- // TODO fix nullability of start/end beat,
65907
- if (!this.endBeat) {
65908
- this._shouldDraw = false;
65909
- return;
65910
- }
65911
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
65912
- this.startNoteRenderer = startNoteRenderer;
65913
- const endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
65914
- this.endNoteRenderer = endNoteRenderer;
65915
- this._startX = 0;
65916
- this._endX = 0;
65917
- this._startY = 0;
65918
- this._endY = 0;
65919
- this.height = 0;
65920
- this._shouldDraw = false;
65921
- // if we are on the tie start, we check if we
65922
- // either can draw till the end note, or we just can draw till the bar end
65923
- this.tieDirection = !startNoteRenderer
65924
- ? this.getBeamDirection(this.endBeat, endNoteRenderer)
65925
- : this.getBeamDirection(this.startBeat, startNoteRenderer);
65926
- if (!this.forEnd && startNoteRenderer) {
65927
- // line break or bar break
65928
- if (startNoteRenderer !== endNoteRenderer) {
65929
- this._startX = startNoteRenderer.x + this.getStartX();
65930
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
65931
- // line break: to bar end
65932
- if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
65933
- this._endX = startNoteRenderer.x + startNoteRenderer.width;
65934
- this._endY = this._startY;
65935
- }
65936
- else {
65937
- this._endX = endNoteRenderer.x + this.getEndX();
65938
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65939
- }
65940
- }
65941
- else {
65942
- this._startX = startNoteRenderer.x + this.getStartX();
65943
- this._endX = endNoteRenderer.x + this.getEndX();
65944
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
65945
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65946
- }
65947
- this._shouldDraw = true;
65948
- }
65949
- else if (!startNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
65950
- this._startX = endNoteRenderer.x;
65951
- this._endX = endNoteRenderer.x + this.getEndX();
65952
- this._startY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65953
- this._endY = this._startY;
65954
- this._shouldDraw = true;
65955
- }
65956
- this._boundingBox = undefined;
65957
- if (this._shouldDraw) {
65958
- this.y = Math.min(this._startY, this._endY);
65959
- if (this.shouldDrawBendSlur()) {
65960
- this._tieHeight = 0; // TODO: Bend slur height to be considered?
65961
- }
65962
- else {
65963
- this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
65964
- const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
65965
- this._boundingBox = tieBoundingBox;
65966
- this.height = tieBoundingBox.h;
65967
- if (this.tieDirection === BeamDirection.Up) {
65968
- // the tie might go above `this.y` due to its shape
65969
- // here we calculate how much this is so we can consider the
65970
- // respective overflow
65971
- const overlap = this.y - tieBoundingBox.y;
65972
- if (overlap > 0) {
65973
- this.y -= overlap;
65974
- }
65975
- }
65976
- }
65977
- }
65978
- }
65979
- paint(cx, cy, canvas) {
65980
- if (this._shouldDraw) {
65981
- if (this.shouldDrawBendSlur()) {
65982
- TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
65983
- }
65984
- else {
65985
- TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
65986
- }
65987
- }
65988
- }
65989
- shouldDrawBendSlur() {
65990
- return false;
65991
- }
65992
- getTieHeight(_startX, _startY, _endX, _endY) {
65993
- return this.renderer.smuflMetrics.tieHeight;
65994
- }
65995
- getBeamDirection(_beat, _noteRenderer) {
65996
- return BeamDirection.Down;
65997
- }
65998
- getStartY() {
65999
- return 0;
66000
- }
66001
- getEndY() {
66002
- return 0;
66003
- }
66004
- getStartX() {
66005
- return 0;
66006
- }
66007
- getEndX() {
66008
- return 0;
66009
- }
66010
- static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
66011
- const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66012
- // For a musical tie/slur, the extrema occur predictably near the midpoint
66013
- // Evaluate at midpoint (t=0.5) and check endpoints
66014
- const p0x = cp[0];
66015
- const p0y = cp[1];
66016
- const c1x = cp[2];
66017
- const c1y = cp[3];
66018
- const c2x = cp[4];
66019
- const c2y = cp[5];
66020
- const p1x = cp[6];
66021
- const p1y = cp[7];
66022
- // Evaluate at t=0.5 for midpoint
66023
- const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
66024
- const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
66025
- // Bounds are simply min/max of start, end, and midpoint
66026
- const xMin = Math.min(p0x, p1x, midX);
66027
- const xMax = Math.max(p0x, p1x, midX);
66028
- let yMin = Math.min(p0y, p1y, midY);
66029
- let yMax = Math.max(p0y, p1y, midY);
66030
- // Account for thickness of the tie/slur
66031
- if (down) {
66032
- yMax += size;
66033
- }
66034
- else {
66035
- yMin -= size;
66036
- }
66037
- const b = new Bounds();
66038
- b.x = xMin;
66039
- b.y = yMin;
66040
- b.w = xMax - xMin;
66041
- b.h = yMax - yMin;
66042
- return b;
66043
- }
66044
- static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
66045
- if (x1 === x2 && y1 === y2) {
66046
- return [];
66047
- }
66048
- // ensure endX > startX
66049
- if (x2 < x1) {
66050
- let t = x1;
66051
- x1 = x2;
66052
- x2 = t;
66053
- t = y1;
66054
- y1 = y2;
66055
- y2 = t;
66056
- }
66057
- //
66058
- // calculate control points
66059
- //
66060
- offset *= scale;
66061
- size *= scale;
66062
- if (down) {
66063
- offset *= -1;
66064
- size *= -1;
66065
- }
66066
- if (scale >= 1) {
66067
- size *= 1.2;
66068
- }
66069
- // calculate control points on horizontal axis then rotate:
66070
- /*
66071
- cp1x/cpy1 cp2x/cpy2
66072
- *----------------*
66073
- / \
66074
- / \
66075
- x1/y1 * * x2/y2
66076
-
66077
- cp3 and cp4 are simply with lower height
66078
- */
66079
- const dY = y2 - y1;
66080
- const dX = x2 - x1;
66081
- const length = Math.sqrt(dX * dX + dY * dY);
66082
- let cp1x = x1 + length * 0.25;
66083
- let cp1y = y1 - offset;
66084
- let cp2x = x1 + length * 0.75;
66085
- let cp2y = y1 - offset;
66086
- let cp3x = x1 + length * 0.75;
66087
- let cp3y = y1 - offset - size;
66088
- let cp4x = x1 + length * 0.25;
66089
- let cp4y = y1 - offset - size;
66090
- const angle = Math.atan2(dY, dX);
66091
- [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
66092
- [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
66093
- [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
66094
- [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
66095
- return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
66096
- }
66097
- static _rotate(x, y, rotateX, rotateY, angle) {
66098
- const dx = x - rotateX;
66099
- const dy = y - rotateY;
66100
- const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
66101
- const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
66102
- return [rotateX + rx, rotateY + ry];
66103
- }
66104
- static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
66105
- const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66106
- canvas.beginPath();
66107
- canvas.moveTo(cps[0], cps[1]);
66108
- canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
66109
- canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
66110
- canvas.closePath();
66111
- canvas.fill();
66112
- }
66113
- static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
66114
- let normalVectorX = y2 - y1;
66115
- let normalVectorY = x2 - x1;
66116
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66117
- if (down) {
66118
- normalVectorX *= -1;
66119
- }
66120
- else {
66121
- normalVectorY *= -1;
66122
- }
66123
- // make to unit vector
66124
- normalVectorX /= length;
66125
- normalVectorY /= length;
66126
- let offset = bendSlurHeight * scale;
66127
- if (x2 - x1 < 20) {
66128
- offset /= 2;
66129
- }
66130
- const centerY = (y2 + y1) / 2;
66131
- const cp1Y = centerY + offset * normalVectorY;
66132
- return cp1Y;
66133
- }
66134
- static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
66135
- let normalVectorX = y2 - y1;
66136
- let normalVectorY = x2 - x1;
66137
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66138
- if (down) {
66139
- normalVectorX *= -1;
66140
- }
66141
- else {
66142
- normalVectorY *= -1;
66143
- }
66144
- // make to unit vector
66145
- normalVectorX /= length;
66146
- normalVectorY /= length;
66147
- // center of connection
66148
- // TODO: should be 1/3
66149
- const centerX = (x2 + x1) / 2;
66150
- const centerY = (y2 + y1) / 2;
66151
- let offset = bendSlurHeight * scale;
66152
- if (x2 - x1 < 20) {
66153
- offset /= 2;
66154
- }
66155
- const cp1X = centerX + offset * normalVectorX;
66156
- const cp1Y = centerY + offset * normalVectorY;
66157
- canvas.beginPath();
66158
- canvas.moveTo(x1, y1);
66159
- canvas.lineTo(cp1X, cp1Y);
66160
- canvas.lineTo(x2, y2);
66161
- canvas.stroke();
66162
- if (slurText) {
66163
- const w = canvas.measureText(slurText).width;
66164
- const textOffset = down ? 0 : -canvas.font.size;
66165
- canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
66166
- }
66167
- }
66168
- }
66169
-
66170
- /**
66171
- * @internal
66172
- */
66173
- class NumberedTieGlyph extends TieGlyph {
66174
- startNote;
66175
- endNote;
66176
- constructor(startNote, endNote, forEnd = false) {
66177
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
66178
- this.startNote = startNote;
66179
- this.endNote = endNote;
66180
- }
66181
- get _isLeftHandTap() {
66182
- return this.startNote === this.endNote;
66183
- }
66581
+ class NumberedTieGlyph extends NoteTieGlyph {
66184
66582
  shouldDrawBendSlur() {
66185
66583
  return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66186
66584
  !!this.startNote.bendOrigin &&
66187
66585
  this.startNote.isTieOrigin);
66188
66586
  }
66189
- doLayout() {
66190
- super.doLayout();
66191
- }
66192
- getBeamDirection(_beat, _noteRenderer) {
66587
+ calculateTieDirection() {
66193
66588
  return BeamDirection.Up;
66194
66589
  }
66195
- getStartY() {
66196
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66197
- }
66198
- getEndY() {
66199
- return this.getStartY();
66200
- }
66201
- getStartX() {
66202
- if (this._isLeftHandTap) {
66203
- return this.getEndX() - this.startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
66204
- }
66205
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66206
- }
66207
- getEndX() {
66208
- if (this._isLeftHandTap) {
66209
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66210
- }
66211
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66212
- }
66213
66590
  }
66214
66591
 
66215
66592
  /**
66216
66593
  * @internal
66217
66594
  */
66218
- class TabTieGlyph extends TieGlyph {
66219
- startNote;
66220
- endNote;
66221
- constructor(startNote, endNote, forEnd = false) {
66222
- super(startNote.beat, endNote.beat, forEnd);
66223
- this.startNote = startNote;
66224
- this.endNote = endNote;
66225
- }
66226
- get _isLeftHandTap() {
66227
- return this.startNote === this.endNote;
66228
- }
66229
- getTieHeight(startX, startY, endX, endY) {
66230
- if (this._isLeftHandTap) {
66231
- return this.startNoteRenderer.smuflMetrics.tieHeight;
66232
- }
66233
- return super.getTieHeight(startX, startY, endX, endY);
66234
- }
66235
- getBeamDirection(_beat, _noteRenderer) {
66236
- if (this._isLeftHandTap) {
66595
+ class TabTieGlyph extends NoteTieGlyph {
66596
+ calculateTieDirection() {
66597
+ if (this.isLeftHandTap) {
66237
66598
  return BeamDirection.Up;
66238
66599
  }
66239
66600
  return TabTieGlyph.getBeamDirectionForNote(this.startNote);
@@ -66241,54 +66602,25 @@ class TabTieGlyph extends TieGlyph {
66241
66602
  static getBeamDirectionForNote(note) {
66242
66603
  return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
66243
66604
  }
66244
- getStartY() {
66245
- if (this._isLeftHandTap) {
66246
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
66247
- }
66248
- if (this.tieDirection === BeamDirection.Up) {
66249
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66250
- }
66251
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
66252
- }
66253
- getEndY() {
66254
- return this.getStartY();
66255
- }
66256
- getStartX() {
66257
- if (this._isLeftHandTap) {
66258
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
66259
- }
66260
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66261
- }
66262
- getEndX() {
66263
- if (this._isLeftHandTap) {
66264
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66265
- }
66266
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66267
- }
66268
66605
  }
66269
66606
 
66270
66607
  /**
66271
66608
  * @internal
66272
66609
  */
66273
- class NumberedSlurGlyph extends TabTieGlyph {
66274
- _direction;
66610
+ class TabSlurGlyph extends TabTieGlyph {
66275
66611
  _forSlide;
66276
- constructor(startNote, endNote, forSlide, forEnd = false) {
66277
- super(startNote, endNote, forEnd);
66278
- this._direction = BeamDirection.Up;
66612
+ constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
66613
+ super(slurEffectId, startNote, endNote, forEnd);
66279
66614
  this._forSlide = forSlide;
66280
66615
  }
66281
66616
  getTieHeight(startX, _startY, endX, _endY) {
66282
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
66617
+ return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
66283
66618
  }
66284
66619
  tryExpand(startNote, endNote, forSlide, forEnd) {
66285
66620
  // same type required
66286
66621
  if (this._forSlide !== forSlide) {
66287
66622
  return false;
66288
66623
  }
66289
- if (this.forEnd !== forEnd) {
66290
- return false;
66291
- }
66292
66624
  // same start and endbeat
66293
66625
  if (this.startNote.beat.id !== startNote.beat.id) {
66294
66626
  return false;
@@ -66296,41 +66628,43 @@ class NumberedSlurGlyph extends TabTieGlyph {
66296
66628
  if (this.endNote.beat.id !== endNote.beat.id) {
66297
66629
  return false;
66298
66630
  }
66631
+ const isForEnd = this.renderer === this.lookupEndBeatRenderer();
66632
+ if (isForEnd !== forEnd) {
66633
+ return false;
66634
+ }
66635
+ // same draw direction
66636
+ if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
66637
+ return false;
66638
+ }
66299
66639
  // if we can expand, expand in correct direction
66300
- switch (this._direction) {
66640
+ switch (this.tieDirection) {
66301
66641
  case BeamDirection.Up:
66302
66642
  if (startNote.realValue > this.startNote.realValue) {
66303
66643
  this.startNote = startNote;
66304
- this.startBeat = startNote.beat;
66305
66644
  }
66306
66645
  if (endNote.realValue > this.endNote.realValue) {
66307
66646
  this.endNote = endNote;
66308
- this.endBeat = endNote.beat;
66309
66647
  }
66310
66648
  break;
66311
66649
  case BeamDirection.Down:
66312
66650
  if (startNote.realValue < this.startNote.realValue) {
66313
66651
  this.startNote = startNote;
66314
- this.startBeat = startNote.beat;
66315
66652
  }
66316
66653
  if (endNote.realValue < this.endNote.realValue) {
66317
66654
  this.endNote = endNote;
66318
- this.endBeat = endNote.beat;
66319
66655
  }
66320
66656
  break;
66321
66657
  }
66322
66658
  return true;
66323
66659
  }
66324
- paint(cx, cy, canvas) {
66325
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66326
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
66327
- const slurId = `numbered.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
66328
- const renderer = this.renderer;
66329
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
66330
- if (!isSlurRendered) {
66331
- renderer.staff.setSharedLayoutData(slurId, true);
66332
- super.paint(cx, cy, canvas);
66333
- }
66660
+ }
66661
+
66662
+ /**
66663
+ * @internal
66664
+ */
66665
+ class NumberedSlurGlyph extends TabSlurGlyph {
66666
+ calculateTieDirection() {
66667
+ return BeamDirection.Up;
66334
66668
  }
66335
66669
  }
66336
66670
 
@@ -66338,23 +66672,31 @@ class NumberedSlurGlyph extends TabTieGlyph {
66338
66672
  * @internal
66339
66673
  */
66340
66674
  class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66675
+ _slurs = new Map();
66341
66676
  _effectSlurs = [];
66677
+ doLayout() {
66678
+ this._slurs.clear();
66679
+ this._effectSlurs = [];
66680
+ super.doLayout();
66681
+ }
66342
66682
  createTies(n) {
66343
66683
  // create a tie if any effect requires it
66344
66684
  if (!n.isVisible) {
66345
66685
  return;
66346
66686
  }
66347
- if (n.isTieOrigin && n.tieDestination.isVisible) {
66348
- const tie = new NumberedTieGlyph(n, n.tieDestination, false);
66687
+ if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
66688
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
66349
66689
  this.addTie(tie);
66690
+ this._slurs.set(tie.slurEffectId, tie);
66350
66691
  }
66351
66692
  if (n.isTieDestination) {
66352
- const tie = new NumberedTieGlyph(n.tieOrigin, n, true);
66693
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
66353
66694
  this.addTie(tie);
66354
66695
  }
66355
- if (n.isLeftHandTapped && !n.isHammerPullDestination) {
66356
- const tapSlur = new NumberedTieGlyph(n, n, false);
66696
+ if (n.isLeftHandTapped && !n.isHammerPullDestination && !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
66697
+ const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
66357
66698
  this.addTie(tapSlur);
66699
+ this._slurs.set(tapSlur.slurEffectId, tapSlur);
66358
66700
  }
66359
66701
  // start effect slur on first beat
66360
66702
  if (n.isEffectSlurOrigin && n.effectSlurDestination) {
@@ -66366,9 +66708,11 @@ class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66366
66708
  }
66367
66709
  }
66368
66710
  if (!expanded) {
66369
- const effectSlur = new NumberedSlurGlyph(n, n.effectSlurDestination, false, false);
66711
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
66370
66712
  this._effectSlurs.push(effectSlur);
66371
66713
  this.addTie(effectSlur);
66714
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66715
+ this._slurs.set('numbered.slur.effect', effectSlur);
66372
66716
  }
66373
66717
  }
66374
66718
  // end effect slur on last beat
@@ -66381,9 +66725,11 @@ class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66381
66725
  }
66382
66726
  }
66383
66727
  if (!expanded) {
66384
- const effectSlur = new NumberedSlurGlyph(n.effectSlurOrigin, n, false, true);
66728
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
66385
66729
  this._effectSlurs.push(effectSlur);
66386
66730
  this.addTie(effectSlur);
66731
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66732
+ this._slurs.set('numbered.slur.effect', effectSlur);
66387
66733
  }
66388
66734
  }
66389
66735
  }
@@ -66776,10 +67122,11 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66776
67122
  if (sr.shortestDuration < this.container.beat.duration) {
66777
67123
  sr.shortestDuration = this.container.beat.duration;
66778
67124
  }
66779
- const glyphY = sr.getLineY(sr.getNoteLine());
66780
67125
  if (!this.container.beat.isEmpty) {
67126
+ const glyphY = sr.getLineY(0);
66781
67127
  let numberWithinOctave = '0';
66782
67128
  if (this.container.beat.notes.length > 0) {
67129
+ const note = this.container.beat.notes[0];
66783
67130
  const kst = this.renderer.bar.keySignatureType;
66784
67131
  const ks = this.renderer.bar.keySignature;
66785
67132
  const ksi = ks + 7;
@@ -66787,7 +67134,6 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66787
67134
  ? NumberedBeatGlyph.minorKeySignatureOneValues
66788
67135
  : NumberedBeatGlyph.majorKeySignatureOneValues;
66789
67136
  const oneNoteValue = oneNoteValues[ksi];
66790
- const note = this.container.beat.notes[0];
66791
67137
  if (note.isDead) {
66792
67138
  numberWithinOctave = 'X';
66793
67139
  }
@@ -66834,7 +67180,7 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66834
67180
  // Note dots
66835
67181
  if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
66836
67182
  for (let i = 0; i < this.container.beat.dots; i++) {
66837
- const dot = new AugmentationDotGlyph(0, sr.getLineY(0));
67183
+ const dot = new AugmentationDotGlyph(0, glyphY);
66838
67184
  dot.renderer = this.renderer;
66839
67185
  this.addEffect(dot);
66840
67186
  }
@@ -66862,7 +67208,7 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66862
67208
  numberOfQuarterNotes += numberOfAddedQuarters;
66863
67209
  }
66864
67210
  for (let i = 0; i < numberOfQuarterNotes - 1; i++) {
66865
- const dash = new NumberedDashGlyph(0, sr.getLineY(0), this.container.beat);
67211
+ const dash = new NumberedDashGlyph(0, glyphY, this.container.beat);
66866
67212
  dash.renderer = this.renderer;
66867
67213
  this.addNormal(dash);
66868
67214
  }
@@ -67332,7 +67678,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67332
67678
  }
67333
67679
  }
67334
67680
  }
67335
- getNoteLine() {
67681
+ getNoteLine(_note) {
67336
67682
  return 0;
67337
67683
  }
67338
67684
  get tupletOffset() {
@@ -69547,87 +69893,128 @@ class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
69547
69893
  * @internal
69548
69894
  */
69549
69895
  class ScoreLegatoGlyph extends TieGlyph {
69550
- constructor(startBeat, endBeat, forEnd = false) {
69551
- super(startBeat, endBeat, forEnd);
69896
+ startBeat;
69897
+ endBeat;
69898
+ startBeatRenderer = null;
69899
+ endBeatRenderer = null;
69900
+ constructor(slurEffectId, startBeat, endBeat, forEnd) {
69901
+ super(slurEffectId, forEnd);
69902
+ this.startBeat = startBeat;
69903
+ this.endBeat = endBeat;
69552
69904
  }
69553
69905
  doLayout() {
69554
69906
  super.doLayout();
69555
69907
  }
69556
- getBeamDirection(beat, noteRenderer) {
69557
- if (beat.isRest) {
69908
+ lookupStartBeatRenderer() {
69909
+ if (!this.startBeatRenderer) {
69910
+ this.startBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
69911
+ }
69912
+ return this.startBeatRenderer;
69913
+ }
69914
+ lookupEndBeatRenderer() {
69915
+ if (!this.endBeatRenderer) {
69916
+ this.endBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
69917
+ }
69918
+ return this.endBeatRenderer;
69919
+ }
69920
+ shouldDrawBendSlur() {
69921
+ return false;
69922
+ }
69923
+ calculateTieDirection() {
69924
+ if (this.startBeat.isRest) {
69558
69925
  return BeamDirection.Up;
69559
69926
  }
69560
69927
  // invert direction (if stems go up, ties go down to not cross them)
69561
- switch (noteRenderer.getBeatDirection(beat)) {
69928
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startBeat)) {
69562
69929
  case BeamDirection.Up:
69563
69930
  return BeamDirection.Down;
69564
69931
  default:
69565
69932
  return BeamDirection.Up;
69566
69933
  }
69567
69934
  }
69568
- getStartY() {
69935
+ calculateStartX() {
69936
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69937
+ return startBeatRenderer.x + startBeatRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69938
+ }
69939
+ calculateStartY() {
69940
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69569
69941
  if (this.startBeat.isRest) {
69570
- // below all lines
69571
- return this.startNoteRenderer.getScoreY(9);
69942
+ switch (this.tieDirection) {
69943
+ case BeamDirection.Up:
69944
+ return (startBeatRenderer.y +
69945
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxTop());
69946
+ default:
69947
+ return (startBeatRenderer.y +
69948
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxBottom());
69949
+ }
69572
69950
  }
69573
69951
  switch (this.tieDirection) {
69574
69952
  case BeamDirection.Up:
69575
69953
  // below lowest note
69576
- return this.startNoteRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69954
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69577
69955
  default:
69578
- return this.startNoteRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69956
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69579
69957
  }
69580
69958
  }
69581
- getEndY() {
69582
- const endNoteScoreRenderer = this.endNoteRenderer;
69959
+ calculateEndX() {
69960
+ const endBeatRenderer = this.lookupEndBeatRenderer();
69961
+ if (!endBeatRenderer) {
69962
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
69963
+ }
69964
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69965
+ return (endBeatRenderer.x +
69966
+ endBeatRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69967
+ ? BeatXPosition.Stem
69968
+ : BeatXPosition.MiddleNotes));
69969
+ }
69970
+ caclculateEndY() {
69971
+ const endBeatRenderer = this.lookupEndBeatRenderer();
69972
+ if (!endBeatRenderer) {
69973
+ return this.calculateStartY();
69974
+ }
69583
69975
  if (this.endBeat.isRest) {
69584
69976
  switch (this.tieDirection) {
69585
69977
  case BeamDirection.Up:
69586
- return endNoteScoreRenderer.getScoreY(9);
69978
+ return (endBeatRenderer.y + endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxTop());
69587
69979
  default:
69588
- return endNoteScoreRenderer.getScoreY(0);
69980
+ return (endBeatRenderer.y +
69981
+ endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxBottom());
69589
69982
  }
69590
69983
  }
69591
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69592
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
69984
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startBeat);
69985
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69593
69986
  if (startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None) {
69594
69987
  if (endBeamDirection === this.tieDirection) {
69595
69988
  switch (this.tieDirection) {
69596
69989
  case BeamDirection.Up:
69597
69990
  // stem upper end
69598
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem);
69991
+ return (endBeatRenderer.y +
69992
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem));
69599
69993
  default:
69600
69994
  // stem lower end
69601
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem);
69995
+ return (endBeatRenderer.y +
69996
+ endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem));
69602
69997
  }
69603
69998
  }
69604
69999
  switch (this.tieDirection) {
69605
70000
  case BeamDirection.Up:
69606
70001
  // stem upper end
69607
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem);
70002
+ return (endBeatRenderer.y +
70003
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem));
69608
70004
  default:
69609
70005
  // stem lower end
69610
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem);
70006
+ return (endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem));
69611
70007
  }
69612
70008
  }
69613
70009
  switch (this.tieDirection) {
69614
70010
  case BeamDirection.Up:
69615
70011
  // below lowest note
69616
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
70012
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
69617
70013
  default:
69618
70014
  // above highest note
69619
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
70015
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
69620
70016
  }
69621
70017
  }
69622
- getStartX() {
69623
- return this.startNoteRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69624
- }
69625
- getEndX() {
69626
- const endBeamDirection = this.endNoteRenderer.getBeatDirection(this.endBeat);
69627
- return this.endNoteRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69628
- ? BeatXPosition.Stem
69629
- : BeatXPosition.MiddleNotes);
69630
- }
69631
70018
  }
69632
70019
 
69633
70020
  /**
@@ -69827,142 +70214,106 @@ class ScoreSlideLineGlyph extends Glyph {
69827
70214
  /**
69828
70215
  * @internal
69829
70216
  */
69830
- class ScoreSlurGlyph extends ScoreLegatoGlyph {
69831
- _startNote;
69832
- _endNote;
69833
- constructor(startNote, endNote, forEnd = false) {
69834
- super(startNote.beat, endNote.beat, forEnd);
69835
- this._startNote = startNote;
69836
- this._endNote = endNote;
70217
+ class ScoreTieGlyph extends NoteTieGlyph {
70218
+ shouldDrawBendSlur() {
70219
+ return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70220
+ !!this.startNote.bendOrigin &&
70221
+ this.startNote.isTieOrigin);
70222
+ }
70223
+ calculateStartX() {
70224
+ if (this.isLeftHandTap) {
70225
+ return this.calculateEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70226
+ }
70227
+ return this.renderer.x + this.renderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
69837
70228
  }
70229
+ calculateEndX() {
70230
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70231
+ if (!endNoteRenderer) {
70232
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70233
+ }
70234
+ if (this.isLeftHandTap) {
70235
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70236
+ }
70237
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70238
+ }
70239
+ }
70240
+
70241
+ /**
70242
+ * @internal
70243
+ */
70244
+ class ScoreSlurGlyph extends ScoreTieGlyph {
69838
70245
  getTieHeight(startX, _startY, endX, _endY) {
69839
- return Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
70246
+ return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
69840
70247
  }
69841
- getStartY() {
70248
+ calculateStartX() {
70249
+ return (this.renderer.x +
70250
+ (this._isStartCentered()
70251
+ ? this.renderer.getBeatX(this.startNote.beat, BeatXPosition.MiddleNotes)
70252
+ : this.renderer.getNoteX(this.startNote, NoteXPosition.Right)));
70253
+ }
70254
+ calculateStartY() {
69842
70255
  if (this._isStartCentered()) {
69843
70256
  switch (this.tieDirection) {
69844
70257
  case BeamDirection.Up:
69845
- // below lowest note
69846
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Top);
70258
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Top);
69847
70259
  default:
69848
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Bottom);
70260
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Bottom);
69849
70261
  }
69850
70262
  }
69851
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Center);
70263
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Center);
69852
70264
  }
69853
- getEndY() {
70265
+ calculateEndX() {
70266
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70267
+ if (!endNoteRenderer) {
70268
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70269
+ }
70270
+ if (this._isEndCentered()) {
70271
+ if (this._isEndOnStem()) {
70272
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.Stem);
70273
+ }
70274
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
70275
+ }
70276
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70277
+ }
70278
+ caclculateEndY() {
70279
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70280
+ if (!endNoteRenderer) {
70281
+ return this.calculateStartY();
70282
+ }
69854
70283
  if (this._isEndCentered()) {
69855
70284
  if (this._isEndOnStem()) {
69856
70285
  switch (this.tieDirection) {
69857
70286
  case BeamDirection.Up:
69858
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.TopWithStem);
70287
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.TopWithStem);
69859
70288
  default:
69860
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.BottomWithStem);
70289
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.BottomWithStem);
69861
70290
  }
69862
70291
  }
69863
70292
  switch (this.tieDirection) {
69864
70293
  case BeamDirection.Up:
69865
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Top);
70294
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
69866
70295
  default:
69867
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Bottom);
70296
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
69868
70297
  }
69869
70298
  }
69870
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Center);
70299
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
69871
70300
  }
69872
70301
  _isStartCentered() {
69873
- return ((this._startNote === this._startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
69874
- (this._startNote === this._startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70302
+ return ((this.startNote === this.startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70303
+ (this.startNote === this.startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
69875
70304
  }
69876
70305
  _isEndCentered() {
69877
- return (this._startNote.beat.graceType === GraceType.None &&
69878
- ((this._endNote === this._endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
69879
- (this._endNote === this._endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70306
+ return (this.startNote.beat.graceType === GraceType.None &&
70307
+ ((this.endNote === this.endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70308
+ (this.endNote === this.endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
69880
70309
  }
69881
70310
  _isEndOnStem() {
69882
- const endNoteScoreRenderer = this.endNoteRenderer;
69883
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69884
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
69885
- return startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None;
69886
- }
69887
- getStartX() {
69888
- return this._isStartCentered()
69889
- ? this.startNoteRenderer.getBeatX(this._startNote.beat, BeatXPosition.MiddleNotes)
69890
- : this.startNoteRenderer.getNoteX(this._startNote, NoteXPosition.Right);
69891
- }
69892
- getEndX() {
69893
- if (this._isEndCentered()) {
69894
- if (this._isEndOnStem()) {
69895
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.Stem);
69896
- }
69897
- return this.endNoteRenderer.getNoteX(this._endNote, NoteXPosition.Center);
69898
- }
69899
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.PreNotes);
69900
- }
69901
- }
69902
-
69903
- /**
69904
- * @internal
69905
- */
69906
- class ScoreTieGlyph extends TieGlyph {
69907
- startNote;
69908
- endNote;
69909
- constructor(startNote, endNote, forEnd = false) {
69910
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
69911
- this.startNote = startNote;
69912
- this.endNote = endNote;
69913
- }
69914
- shouldDrawBendSlur() {
69915
- return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
69916
- !!this.startNote.bendOrigin &&
69917
- this.startNote.isTieOrigin);
69918
- }
69919
- doLayout() {
69920
- super.doLayout();
69921
- }
69922
- getBeamDirection(beat, noteRenderer) {
69923
- // invert direction (if stems go up, ties go down to not cross them)
69924
- switch (noteRenderer.getBeatDirection(beat)) {
69925
- case BeamDirection.Up:
69926
- return BeamDirection.Down;
69927
- default:
69928
- return BeamDirection.Up;
69929
- }
69930
- }
69931
- getStartY() {
69932
- if (this.startBeat.isRest) {
69933
- // below all lines
69934
- return this.startNoteRenderer.getScoreY(9);
69935
- }
69936
- switch (this.tieDirection) {
69937
- case BeamDirection.Up:
69938
- // below lowest note
69939
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
69940
- default:
69941
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
69942
- }
69943
- }
69944
- getEndY() {
69945
- const endNoteScoreRenderer = this.endNoteRenderer;
69946
- if (this.endBeat.isRest) {
69947
- switch (this.tieDirection) {
69948
- case BeamDirection.Up:
69949
- return endNoteScoreRenderer.getScoreY(9);
69950
- default:
69951
- return endNoteScoreRenderer.getScoreY(0);
69952
- }
69953
- }
69954
- switch (this.tieDirection) {
69955
- case BeamDirection.Up:
69956
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Top);
69957
- default:
69958
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
69959
- }
69960
- }
69961
- getStartX() {
69962
- return this.startNoteRenderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
69963
- }
69964
- getEndX() {
69965
- return this.endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70311
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat);
70312
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70313
+ const endBeamDirection = endBeatRenderer
70314
+ ? endBeatRenderer.getBeatDirection(this.endNote.beat)
70315
+ : startBeamDirection;
70316
+ return startBeamDirection !== endBeamDirection && this.startNote.beat.graceType === GraceType.None;
69966
70317
  }
69967
70318
  }
69968
70319
 
@@ -70012,12 +70363,11 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70012
70363
  n.beat.graceType !== GraceType.BendGrace &&
70013
70364
  n.tieDestination &&
70014
70365
  n.tieDestination.isVisible) {
70015
- // tslint:disable-next-line: no-unnecessary-type-assertion
70016
- const tie = new ScoreTieGlyph(n, n.tieDestination, false);
70366
+ const tie = new ScoreTieGlyph(`score.tie.${n.id}`, n, n.tieDestination, false);
70017
70367
  this.addTie(tie);
70018
70368
  }
70019
70369
  if (n.isTieDestination && !n.tieOrigin.hasBend && !n.beat.hasWhammyBar) {
70020
- const tie = new ScoreTieGlyph(n.tieOrigin, n, true);
70370
+ const tie = new ScoreTieGlyph(`score.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
70021
70371
  this.addTie(tie);
70022
70372
  }
70023
70373
  // TODO: depending on the type we have other positioning
@@ -70027,17 +70377,16 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70027
70377
  this.addTie(l);
70028
70378
  }
70029
70379
  if (n.isSlurOrigin && n.slurDestination && n.slurDestination.isVisible) {
70030
- // tslint:disable-next-line: no-unnecessary-type-assertion
70031
- const tie = new ScoreSlurGlyph(n, n.slurDestination, false);
70380
+ const tie = new ScoreSlurGlyph(`score.slur.${n.id}`, n, n.slurDestination, false);
70032
70381
  this.addTie(tie);
70033
70382
  }
70034
70383
  if (n.isSlurDestination) {
70035
- const tie = new ScoreSlurGlyph(n.slurOrigin, n, true);
70384
+ const tie = new ScoreSlurGlyph(`score.slur.${n.slurOrigin.id}`, n.slurOrigin, n, true);
70036
70385
  this.addTie(tie);
70037
70386
  }
70038
70387
  // start effect slur on first beat
70039
70388
  if (!this._effectSlur && n.isEffectSlurOrigin && n.effectSlurDestination) {
70040
- const effectSlur = new ScoreSlurGlyph(n, n.effectSlurDestination, false);
70389
+ const effectSlur = new ScoreSlurGlyph(`score.slur.effect.${n.beat.id}`, n, n.effectSlurDestination, false);
70041
70390
  this._effectSlur = effectSlur;
70042
70391
  this.addTie(effectSlur);
70043
70392
  }
@@ -70046,7 +70395,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70046
70395
  const direction = this.onNotes.beamingHelper.direction;
70047
70396
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70048
70397
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70049
- const effectEndSlur = new ScoreSlurGlyph(startNote, endNote, true);
70398
+ const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
70050
70399
  this._effectEndSlur = effectEndSlur;
70051
70400
  this.addTie(effectEndSlur);
70052
70401
  }
@@ -70068,7 +70417,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70068
70417
  while (destination.nextBeat && destination.nextBeat.isLegatoDestination) {
70069
70418
  destination = destination.nextBeat;
70070
70419
  }
70071
- this.addTie(new ScoreLegatoGlyph(this.beat, destination, false));
70420
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${this.beat.id}`, this.beat, destination, false));
70072
70421
  }
70073
70422
  }
70074
70423
  else if (this.beat.isLegatoDestination) {
@@ -70078,7 +70427,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70078
70427
  while (origin.previousBeat && origin.previousBeat.isLegatoOrigin) {
70079
70428
  origin = origin.previousBeat;
70080
70429
  }
70081
- this.addTie(new ScoreLegatoGlyph(origin, this.beat, true));
70430
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${origin.id}`, origin, this.beat, true));
70082
70431
  }
70083
70432
  }
70084
70433
  }
@@ -70458,6 +70807,9 @@ class ScoreBarRenderer extends LineBarRenderer {
70458
70807
  this.addBeatGlyph(container);
70459
70808
  }
70460
70809
  }
70810
+ getNoteLine(note) {
70811
+ return this.accidentalHelper.getNoteSteps(note) / 2;
70812
+ }
70461
70813
  getNoteSteps(n) {
70462
70814
  return this.accidentalHelper.getNoteSteps(n);
70463
70815
  }
@@ -70514,43 +70866,15 @@ class ScoreBarRendererFactory extends BarRendererFactory {
70514
70866
  /**
70515
70867
  * @internal
70516
70868
  */
70517
- class SlashTieGlyph extends TieGlyph {
70518
- startNote;
70519
- endNote;
70520
- constructor(startNote, endNote, forEnd = false) {
70521
- super(startNote.beat, endNote.beat, forEnd);
70522
- this.startNote = startNote;
70523
- this.endNote = endNote;
70524
- }
70525
- get _isLeftHandTap() {
70526
- return this.startNote === this.endNote;
70527
- }
70528
- getTieHeight(startX, startY, endX, endY) {
70529
- if (this._isLeftHandTap) {
70530
- return this.startNoteRenderer.smuflMetrics.tieHeight;
70531
- }
70532
- return super.getTieHeight(startX, startY, endX, endY);
70533
- }
70534
- getBeamDirection(_beat, _noteRenderer) {
70535
- return BeamDirection.Down;
70536
- }
70537
- static getBeamDirectionForNote(_note) {
70869
+ class SlashTieGlyph extends NoteTieGlyph {
70870
+ calculateTieDirection() {
70538
70871
  return BeamDirection.Down;
70539
70872
  }
70540
- getStartY() {
70541
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
70873
+ getStartNotePosition() {
70874
+ return NoteXPosition.Right;
70542
70875
  }
70543
- getEndY() {
70544
- return this.getStartY();
70545
- }
70546
- getStartX() {
70547
- if (this._isLeftHandTap) {
70548
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70549
- }
70550
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Right);
70551
- }
70552
- getEndX() {
70553
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70876
+ getEndNotePosition() {
70877
+ return NoteXPosition.Left;
70554
70878
  }
70555
70879
  }
70556
70880
 
@@ -70565,12 +70889,12 @@ class SlashBeatContainerGlyph extends BeatContainerGlyph {
70565
70889
  return;
70566
70890
  }
70567
70891
  if (!this._tiedNoteTie && n.isTieOrigin && n.tieDestination.isVisible) {
70568
- const tie = new SlashTieGlyph(n, n.tieDestination, false);
70892
+ const tie = new SlashTieGlyph('slash.tie', n, n.tieDestination, false);
70569
70893
  this._tiedNoteTie = tie;
70570
70894
  this.addTie(tie);
70571
70895
  }
70572
70896
  if (!this._tiedNoteTie && n.isTieDestination) {
70573
- const tie = new SlashTieGlyph(n.tieOrigin, n, true);
70897
+ const tie = new SlashTieGlyph('slash.tie', n.tieOrigin, n, true);
70574
70898
  this._tiedNoteTie = tie;
70575
70899
  this.addTie(tie);
70576
70900
  }
@@ -70697,8 +71021,7 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
70697
71021
  doLayout() {
70698
71022
  // create glyphs
70699
71023
  const sr = this.renderer;
70700
- const line = sr.getNoteLine();
70701
- const glyphY = sr.getLineY(line);
71024
+ const glyphY = sr.getLineY(0);
70702
71025
  if (this.container.beat.deadSlapped) {
70703
71026
  const deadSlapped = new DeadSlappedBeatGlyph();
70704
71027
  deadSlapped.renderer = this.renderer;
@@ -70731,7 +71054,7 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
70731
71054
  //
70732
71055
  if (this.container.beat.dots > 0) {
70733
71056
  for (let i = 0; i < this.container.beat.dots; i++) {
70734
- this.addEffect(new AugmentationDotGlyph(0, sr.getLineY(sr.getNoteLine()) - sr.getLineHeight(0.5)));
71057
+ this.addEffect(new AugmentationDotGlyph(0, glyphY - sr.getLineHeight(0.5)));
70735
71058
  }
70736
71059
  }
70737
71060
  super.doLayout();
@@ -70813,7 +71136,7 @@ class SlashBarRenderer extends LineBarRenderer {
70813
71136
  this.registerOverflowTop(this.tupletSize);
70814
71137
  }
70815
71138
  }
70816
- getNoteLine() {
71139
+ getNoteLine(_note) {
70817
71140
  return 0;
70818
71141
  }
70819
71142
  getFlagTopY(beat, _direction) {
@@ -71150,77 +71473,6 @@ class TabSlideLineGlyph extends Glyph {
71150
71473
  }
71151
71474
  }
71152
71475
 
71153
- /**
71154
- * @internal
71155
- */
71156
- class TabSlurGlyph extends TabTieGlyph {
71157
- _direction;
71158
- _forSlide;
71159
- constructor(startNote, endNote, forSlide, forEnd = false) {
71160
- super(startNote, endNote, forEnd);
71161
- this._direction = TabTieGlyph.getBeamDirectionForNote(startNote);
71162
- this._forSlide = forSlide;
71163
- }
71164
- getTieHeight(startX, _startY, endX, _endY) {
71165
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
71166
- }
71167
- tryExpand(startNote, endNote, forSlide, forEnd) {
71168
- // same type required
71169
- if (this._forSlide !== forSlide) {
71170
- return false;
71171
- }
71172
- if (this.forEnd !== forEnd) {
71173
- return false;
71174
- }
71175
- // same start and endbeat
71176
- if (this.startNote.beat.id !== startNote.beat.id) {
71177
- return false;
71178
- }
71179
- if (this.endNote.beat.id !== endNote.beat.id) {
71180
- return false;
71181
- }
71182
- // same draw direction
71183
- if (this._direction !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
71184
- return false;
71185
- }
71186
- // if we can expand, expand in correct direction
71187
- switch (this._direction) {
71188
- case BeamDirection.Up:
71189
- if (startNote.realValue > this.startNote.realValue) {
71190
- this.startNote = startNote;
71191
- this.startBeat = startNote.beat;
71192
- }
71193
- if (endNote.realValue > this.endNote.realValue) {
71194
- this.endNote = endNote;
71195
- this.endBeat = endNote.beat;
71196
- }
71197
- break;
71198
- case BeamDirection.Down:
71199
- if (startNote.realValue < this.startNote.realValue) {
71200
- this.startNote = startNote;
71201
- this.startBeat = startNote.beat;
71202
- }
71203
- if (endNote.realValue < this.endNote.realValue) {
71204
- this.endNote = endNote;
71205
- this.endBeat = endNote.beat;
71206
- }
71207
- break;
71208
- }
71209
- return true;
71210
- }
71211
- paint(cx, cy, canvas) {
71212
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
71213
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
71214
- const slurId = `tab.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
71215
- const renderer = this.renderer;
71216
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
71217
- if (!isSlurRendered) {
71218
- renderer.staff.setSharedLayoutData(slurId, true);
71219
- super.paint(cx, cy, canvas);
71220
- }
71221
- }
71222
- }
71223
-
71224
71476
  /**
71225
71477
  * @internal
71226
71478
  */
@@ -71245,15 +71497,15 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71245
71497
  }
71246
71498
  const renderer = this.renderer;
71247
71499
  if (n.isTieOrigin && renderer.showTiedNotes && n.tieDestination.isVisible) {
71248
- const tie = new TabTieGlyph(n, n.tieDestination, false);
71500
+ const tie = new TabTieGlyph(`tab.tie.${n.id}`, n, n.tieDestination, false);
71249
71501
  this.addTie(tie);
71250
71502
  }
71251
71503
  if (n.isTieDestination && renderer.showTiedNotes) {
71252
- const tie = new TabTieGlyph(n.tieOrigin, n, true);
71504
+ const tie = new TabTieGlyph(`tab.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
71253
71505
  this.addTie(tie);
71254
71506
  }
71255
71507
  if (n.isLeftHandTapped && !n.isHammerPullDestination) {
71256
- const tapSlur = new TabTieGlyph(n, n, false);
71508
+ const tapSlur = new TabTieGlyph(`tab.tie.leftHandTap.${n.id}`, n, n, false);
71257
71509
  this.addTie(tapSlur);
71258
71510
  }
71259
71511
  // start effect slur on first beat
@@ -71266,7 +71518,7 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71266
71518
  }
71267
71519
  }
71268
71520
  if (!expanded) {
71269
- const effectSlur = new TabSlurGlyph(n, n.effectSlurDestination, false, false);
71521
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.id}`, n, n.effectSlurDestination, false, false);
71270
71522
  this._effectSlurs.push(effectSlur);
71271
71523
  this.addTie(effectSlur);
71272
71524
  }
@@ -71281,7 +71533,7 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71281
71533
  }
71282
71534
  }
71283
71535
  if (!expanded) {
71284
- const effectSlur = new TabSlurGlyph(n.effectSlurOrigin, n, false, true);
71536
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.effectSlurOrigin.id}`, n.effectSlurOrigin, n, false, true);
71285
71537
  this._effectSlurs.push(effectSlur);
71286
71538
  this.addTie(effectSlur);
71287
71539
  }
@@ -71700,7 +71952,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71700
71952
  }
71701
71953
  else {
71702
71954
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
71703
- const y = tabRenderer.getTabY(line);
71955
+ const y = tabRenderer.getLineY(line);
71704
71956
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71705
71957
  this.restGlyph = restGlyph;
71706
71958
  restGlyph.beat = this.container.beat;
@@ -71759,8 +72011,8 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71759
72011
  _createNoteGlyph(n) {
71760
72012
  const tr = this.renderer;
71761
72013
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
71762
- const l = n.beat.voice.bar.staff.tuning.length - n.string;
71763
- noteNumberGlyph.y = tr.getTabY(l);
72014
+ const l = tr.getNoteLine(n);
72015
+ noteNumberGlyph.y = tr.getLineY(l);
71764
72016
  noteNumberGlyph.renderer = this.renderer;
71765
72017
  noteNumberGlyph.doLayout();
71766
72018
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
@@ -71951,17 +72203,8 @@ class TabBarRenderer extends LineBarRenderer {
71951
72203
  }
71952
72204
  return mode;
71953
72205
  }
71954
- /**
71955
- * Gets the relative y position of the given steps relative to first line.
71956
- * @param line the line of the particular string where 0 is the most top line
71957
- * @param correction
71958
- * @returns
71959
- */
71960
- getTabY(line) {
71961
- return super.getLineY(line);
71962
- }
71963
- getTabHeight(line) {
71964
- return super.getLineHeight(line);
72206
+ getNoteLine(note) {
72207
+ return this.bar.staff.tuning.length - note.string;
71965
72208
  }
71966
72209
  minString = Number.NaN;
71967
72210
  maxString = Number.NaN;
@@ -72050,7 +72293,7 @@ class TabBarRenderer extends LineBarRenderer {
72050
72293
  if (this.isFirstOfLine) {
72051
72294
  const center = (this.bar.staff.tuning.length - 1) / 2;
72052
72295
  this.createStartSpacing();
72053
- this.addPreBeatGlyph(new TabClefGlyph(0, this.getTabY(center)));
72296
+ this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
72054
72297
  }
72055
72298
  // Time Signature
72056
72299
  if (this.showTimeSignature &&
@@ -72071,7 +72314,7 @@ class TabBarRenderer extends LineBarRenderer {
72071
72314
  _createTimeSignatureGlyphs() {
72072
72315
  this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
72073
72316
  const lines = (this.bar.staff.tuning.length + 1) / 2 - 1;
72074
- this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getTabY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72317
+ this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getLineY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72075
72318
  }
72076
72319
  createVoiceGlyphs(v) {
72077
72320
  super.createVoiceGlyphs(v);