@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.
package/dist/alphaTab.js CHANGED
@@ -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
  *
@@ -209,9 +209,9 @@
209
209
  * @internal
210
210
  */
211
211
  class VersionInfo {
212
- static version = '1.8.0-alpha.1636';
213
- static date = '2025-12-06T02:04:52.462Z';
214
- static commit = '49c68ed64403080028c852fe371cc6bdcca8dc3a';
212
+ static version = '1.8.0-alpha.1639';
213
+ static date = '2025-12-09T02:16:01.440Z';
214
+ static commit = 'af86866e4f9d89a3ccc34006d01473a549dbbe7f';
215
215
  static print(print) {
216
216
  print(`alphaTab ${VersionInfo.version}`);
217
217
  print(`commit: ${VersionInfo.commit}`);
@@ -12956,25 +12956,31 @@
12956
12956
  /**
12957
12957
  * Gets or sets the X-position of the rectangle within the music notation.
12958
12958
  */
12959
- x = 0;
12959
+ x;
12960
12960
  /**
12961
12961
  * Gets or sets the Y-position of the rectangle within the music notation.
12962
12962
  */
12963
- y = 0;
12963
+ y;
12964
12964
  /**
12965
12965
  * Gets or sets the width of the rectangle.
12966
12966
  */
12967
- w = 0;
12967
+ w;
12968
12968
  /**
12969
12969
  * Gets or sets the height of the rectangle.
12970
12970
  */
12971
- h = 0;
12971
+ h;
12972
12972
  scaleWith(scale) {
12973
12973
  this.x *= scale;
12974
12974
  this.y *= scale;
12975
12975
  this.w *= scale;
12976
12976
  this.h *= scale;
12977
12977
  }
12978
+ constructor(x = 0, y = 0, w = 0, h = 0) {
12979
+ this.x = x;
12980
+ this.y = y;
12981
+ this.h = h;
12982
+ this.w = w;
12983
+ }
12978
12984
  }
12979
12985
 
12980
12986
  /**
@@ -49460,16 +49466,6 @@
49460
49466
  }
49461
49467
  }
49462
49468
 
49463
- /**
49464
- * @internal
49465
- */
49466
- class SelectionInfo {
49467
- beat;
49468
- bounds = null;
49469
- constructor(beat) {
49470
- this.beat = beat;
49471
- }
49472
- }
49473
49469
  /**
49474
49470
  * This class represents the public API of alphaTab and provides all logic to display
49475
49471
  * a music sheet in any UI using the given {@link IUiFacade}
@@ -51569,8 +51565,8 @@
51569
51565
  }
51570
51566
  _isBeatMouseDown = false;
51571
51567
  _isNoteMouseDown = false;
51572
- _selectionStart = null;
51573
- _selectionEnd = null;
51568
+ _selectionStart;
51569
+ _selectionEnd;
51574
51570
  /**
51575
51571
  * This event is fired whenever a the user presses the mouse button on a beat.
51576
51572
  * @eventProperty
@@ -51813,8 +51809,8 @@
51813
51809
  return;
51814
51810
  }
51815
51811
  if (this._hasCursor && this.settings.player.enableUserInteraction) {
51816
- this._selectionStart = new SelectionInfo(beat);
51817
- this._selectionEnd = null;
51812
+ this._selectionStart = { beat };
51813
+ this._selectionEnd = undefined;
51818
51814
  }
51819
51815
  this._isBeatMouseDown = true;
51820
51816
  this.beatMouseDown.trigger(beat);
@@ -51834,7 +51830,7 @@
51834
51830
  }
51835
51831
  if (this.settings.player.enableUserInteraction) {
51836
51832
  if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
51837
- this._selectionEnd = new SelectionInfo(beat);
51833
+ this._selectionEnd = { beat };
51838
51834
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51839
51835
  }
51840
51836
  }
@@ -51853,45 +51849,7 @@
51853
51849
  return;
51854
51850
  }
51855
51851
  if (this._hasCursor && this.settings.player.enableUserInteraction) {
51856
- if (this._selectionEnd) {
51857
- const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ??
51858
- this._selectionStart.beat.absolutePlaybackStart;
51859
- const endTick = this._tickCache?.getBeatStart(this._selectionEnd.beat) ??
51860
- this._selectionEnd.beat.absolutePlaybackStart;
51861
- if (endTick < startTick) {
51862
- const t = this._selectionStart;
51863
- this._selectionStart = this._selectionEnd;
51864
- this._selectionEnd = t;
51865
- }
51866
- }
51867
- if (this._selectionStart && this._tickCache) {
51868
- // get the start and stop ticks (which consider properly repeats)
51869
- const tickCache = this._tickCache;
51870
- const realMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
51871
- // move to selection start
51872
- this._currentBeat = null; // reset current beat so it is updating the cursor
51873
- if (this._player.state === PlayerState.Paused) {
51874
- this._cursorUpdateTick(this._tickCache.getBeatStart(this._selectionStart.beat), false, 1);
51875
- }
51876
- this.tickPosition = realMasterBarStart + this._selectionStart.beat.playbackStart;
51877
- // set playback range
51878
- if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
51879
- const realMasterBarEnd = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
51880
- const range = new PlaybackRange();
51881
- range.startTick = realMasterBarStart + this._selectionStart.beat.playbackStart;
51882
- range.endTick =
51883
- realMasterBarEnd +
51884
- this._selectionEnd.beat.playbackStart +
51885
- this._selectionEnd.beat.playbackDuration -
51886
- 50;
51887
- this.playbackRange = range;
51888
- }
51889
- else {
51890
- this._selectionStart = null;
51891
- this.playbackRange = null;
51892
- this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51893
- }
51894
- }
51852
+ this.applyPlaybackRangeFromHighlight();
51895
51853
  }
51896
51854
  this.beatMouseUp.trigger(beat);
51897
51855
  this.uiFacade.triggerEvent(this.container, 'beatMouseUp', beat, originalEvent);
@@ -51913,13 +51871,13 @@
51913
51871
  const startBeat = this._tickCache.findBeat(this._trackIndexLookup, range.startTick);
51914
51872
  const endBeat = this._tickCache.findBeat(this._trackIndexLookup, range.endTick);
51915
51873
  if (startBeat && endBeat) {
51916
- const selectionStart = new SelectionInfo(startBeat.beat);
51917
- const selectionEnd = new SelectionInfo(endBeat.beat);
51874
+ const selectionStart = { beat: startBeat.beat };
51875
+ const selectionEnd = { beat: endBeat.beat };
51918
51876
  this._cursorSelectRange(selectionStart, selectionEnd);
51919
51877
  }
51920
51878
  }
51921
51879
  else {
51922
- this._cursorSelectRange(null, null);
51880
+ this._cursorSelectRange(undefined, undefined);
51923
51881
  }
51924
51882
  }
51925
51883
  _setupClickHandling() {
@@ -51988,24 +51946,230 @@
51988
51946
  this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51989
51947
  });
51990
51948
  }
51949
+ /**
51950
+ * Places the highlight markers at the specified start and end-beat range.
51951
+ * @param startBeat The start beat where the selection should start
51952
+ * @param endBeat The end beat where the selection should end.
51953
+ *
51954
+ * @remarks
51955
+ * Unlike actually setting {@link playbackRange} this method only places the selection markers without actually
51956
+ * changing the playback range. This method can be used when building custom selection systems (e.g. having draggable handles).
51957
+ *
51958
+ * @category Methods - Player
51959
+ * @since 1.8.0
51960
+ *
51961
+ * @example
51962
+ * JavaScript
51963
+ * ```js
51964
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
51965
+ * const startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0];
51966
+ * const endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0];
51967
+ * api.highlightPlaybackRange(startBeat, endBeat);
51968
+ * ```
51969
+ *
51970
+ * @example
51971
+ * C#
51972
+ * ```cs
51973
+ * var api = new AlphaTabApi<MyControl>(...);
51974
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[0], api.Score.Tracks[1] }, 1.5);
51975
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[2] }, 0.5);
51976
+ * var startBeat = api.Score.Tracks[0].Staves[0].Bars[0].Voices[0].Beats[0];
51977
+ * var endBeat = api.Score.Tracks[0].Staves[0].Bars[3].Voices[0].Beats[0];
51978
+ * api.HighlightPlaybackRange(startBeat, endBeat);
51979
+ * ```
51980
+ *
51981
+ * @example
51982
+ * Android
51983
+ * ```kotlin
51984
+ * val api = AlphaTabApi<MyControl>(...)
51985
+ * val startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0]
51986
+ * val endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0]
51987
+ * api.highlightPlaybackRange(startBeat, endBeat)
51988
+ * ```
51989
+ */
51990
+ highlightPlaybackRange(startBeat, endBeat) {
51991
+ this._selectionStart = { beat: startBeat };
51992
+ this._selectionEnd = { beat: endBeat };
51993
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
51994
+ }
51995
+ /**
51996
+ * Applies the playback range from the currently highlighted range.
51997
+ *
51998
+ * @remarks
51999
+ * This method can be used when building custom selection systems (e.g. having draggable handles).
52000
+ *
52001
+ * @category Methods - Player
52002
+ * @since 1.8.0
52003
+ *
52004
+ * @example
52005
+ * JavaScript
52006
+ * ```js
52007
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52008
+ * const startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0];
52009
+ * const endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0];
52010
+ * api.highlightPlaybackRange(startBeat, endBeat);
52011
+ * api.applyPlaybackRangeFromHighlight();
52012
+ * ```
52013
+ *
52014
+ * @example
52015
+ * C#
52016
+ * ```cs
52017
+ * var api = new AlphaTabApi<MyControl>(...);
52018
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[0], api.Score.Tracks[1] }, 1.5);
52019
+ * api.ChangeTrackVolume(new Track[] { api.Score.Tracks[2] }, 0.5);
52020
+ * var startBeat = api.Score.Tracks[0].Staves[0].Bars[0].Voices[0].Beats[0];
52021
+ * var endBeat = api.Score.Tracks[0].Staves[0].Bars[3].Voices[0].Beats[0];
52022
+ * api.HighlightPlaybackRange(startBeat, endBeat);
52023
+ * api.ApplyPlaybackRangeFromHighlight();
52024
+ * ```
52025
+ *
52026
+ * @example
52027
+ * Android
52028
+ * ```kotlin
52029
+ * val api = AlphaTabApi<MyControl>(...)
52030
+ * val startBeat = api.score.tracks[0].staves[0].bars[0].voices[0].beats[0]
52031
+ * val endBeat = api.score.tracks[0].staves[0].bars[3].voices[0].beats[0]
52032
+ * api.highlightPlaybackRange(startBeat, endBeat)
52033
+ * api.applyPlaybackRangeFromHighlight()
52034
+ * ```
52035
+ */
52036
+ applyPlaybackRangeFromHighlight() {
52037
+ if (this._selectionEnd) {
52038
+ const startTick = this._tickCache?.getBeatStart(this._selectionStart.beat) ??
52039
+ this._selectionStart.beat.absolutePlaybackStart;
52040
+ const endTick = this._tickCache?.getBeatStart(this._selectionEnd.beat) ??
52041
+ this._selectionEnd.beat.absolutePlaybackStart;
52042
+ if (endTick < startTick) {
52043
+ const t = this._selectionStart;
52044
+ this._selectionStart = this._selectionEnd;
52045
+ this._selectionEnd = t;
52046
+ }
52047
+ }
52048
+ if (this._selectionStart && this._tickCache) {
52049
+ // get the start and stop ticks (which consider properly repeats)
52050
+ const tickCache = this._tickCache;
52051
+ const realMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
52052
+ // move to selection start
52053
+ this._currentBeat = null; // reset current beat so it is updating the cursor
52054
+ if (this._player.state === PlayerState.Paused) {
52055
+ this._cursorUpdateTick(this._tickCache.getBeatStart(this._selectionStart.beat), false, 1);
52056
+ }
52057
+ this.tickPosition = realMasterBarStart + this._selectionStart.beat.playbackStart;
52058
+ // set playback range
52059
+ if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
52060
+ const realMasterBarEnd = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
52061
+ const range = new PlaybackRange();
52062
+ range.startTick = realMasterBarStart + this._selectionStart.beat.playbackStart;
52063
+ range.endTick =
52064
+ realMasterBarEnd +
52065
+ this._selectionEnd.beat.playbackStart +
52066
+ this._selectionEnd.beat.playbackDuration -
52067
+ 50;
52068
+ this.playbackRange = range;
52069
+ }
52070
+ else {
52071
+ this._selectionStart = undefined;
52072
+ this.playbackRange = null;
52073
+ this._cursorSelectRange(this._selectionStart, this._selectionEnd);
52074
+ }
52075
+ }
52076
+ }
52077
+ /**
52078
+ * Clears the highlight markers marking the currently selected playback range.
52079
+ *
52080
+ * @remarks
52081
+ * Unlike actually setting {@link playbackRange} this method only clears the selection markers without actually
52082
+ * changing the playback range. This method can be used when building custom selection systems (e.g. having draggable handles).
52083
+ *
52084
+ * @category Methods - Player
52085
+ * @since 1.8.0
52086
+ *
52087
+ * @example
52088
+ * JavaScript
52089
+ * ```js
52090
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52091
+ * api.clearPlaybackRangeHighlight();
52092
+ * ```
52093
+ *
52094
+ * @example
52095
+ * C#
52096
+ * ```cs
52097
+ * var api = new AlphaTabApi<MyControl>(...);
52098
+ * api.clearPlaybackRangeHighlight();
52099
+ * ```
52100
+ *
52101
+ * @example
52102
+ * Android
52103
+ * ```kotlin
52104
+ * val api = AlphaTabApi<MyControl>(...)
52105
+ * api.clearPlaybackRangeHighlight()
52106
+ * ```
52107
+ */
52108
+ clearPlaybackRangeHighlight() {
52109
+ this._cursorSelectRange(undefined, undefined);
52110
+ }
52111
+ /**
52112
+ * This event is fired the shown highlights for the selected playback range changes.
52113
+ *
52114
+ * @remarks
52115
+ * This event is fired already during selection and not only when the selection is completed.
52116
+ * This event can be used to place additional custom selection markers (like drag handles).
52117
+ *
52118
+ * @eventProperty
52119
+ * @category Events - Player
52120
+ * @since 1.8.0
52121
+ *
52122
+ * @example
52123
+ * JavaScript
52124
+ * ```js
52125
+ * const api = new alphaTab.AlphaTabApi(document.querySelector('#alphaTab'));
52126
+ * api.playbackRangeHighlightChanged.on(e => {
52127
+ * updateSelectionHandles(e);
52128
+ * });
52129
+ * ```
52130
+ *
52131
+ * @example
52132
+ * C#
52133
+ * ```cs
52134
+ * var api = new AlphaTabApi<MyControl>(...);
52135
+ * api.PlaybackRangeHighlightChanged.On(e =>
52136
+ * {
52137
+ * UpdateSelectionHandles(e);
52138
+ * });
52139
+ * ```
52140
+ *
52141
+ * @example
52142
+ * Android
52143
+ * ```kotlin
52144
+ * val api = AlphaTabApi<MyControl>(...)
52145
+ * api.playbackRangeHighlightChanged.on { e ->
52146
+ * updateSelectionHandles(e)
52147
+ * }
52148
+ * ```
52149
+ *
52150
+ */
52151
+ playbackRangeHighlightChanged = new EventEmitterOfT();
51991
52152
  _cursorSelectRange(startBeat, endBeat) {
51992
52153
  const cache = this._renderer.boundsLookup;
51993
52154
  if (!cache) {
52155
+ this.playbackRangeHighlightChanged.trigger({});
51994
52156
  return;
51995
52157
  }
51996
52158
  const selectionWrapper = this._selectionWrapper;
51997
52159
  if (!selectionWrapper) {
52160
+ this.playbackRangeHighlightChanged.trigger({});
51998
52161
  return;
51999
52162
  }
52000
52163
  selectionWrapper.clear();
52001
52164
  if (!startBeat || !endBeat || startBeat.beat === endBeat.beat) {
52165
+ this.playbackRangeHighlightChanged.trigger({});
52002
52166
  return;
52003
52167
  }
52004
52168
  if (!startBeat.bounds) {
52005
- startBeat.bounds = cache.findBeat(startBeat.beat);
52169
+ startBeat.bounds = cache.findBeat(startBeat.beat) ?? undefined;
52006
52170
  }
52007
52171
  if (!endBeat.bounds) {
52008
- endBeat.bounds = cache.findBeat(endBeat.beat);
52172
+ endBeat.bounds = cache.findBeat(endBeat.beat) ?? undefined;
52009
52173
  }
52010
52174
  const startTick = this._tickCache?.getBeatStart(startBeat.beat) ?? startBeat.beat.absolutePlaybackStart;
52011
52175
  const endTick = this._tickCache?.getBeatStart(endBeat.beat) ?? endBeat.beat.absolutePlaybackStart;
@@ -52014,7 +52178,17 @@
52014
52178
  startBeat = endBeat;
52015
52179
  endBeat = t;
52016
52180
  }
52017
- const startX = startBeat.bounds.realBounds.x;
52181
+ const eventArgs = {
52182
+ startBeat: startBeat.beat,
52183
+ startBeatBounds: startBeat.bounds,
52184
+ endBeat: endBeat.beat,
52185
+ endBeatBounds: endBeat.bounds,
52186
+ highlightBlocks: []
52187
+ };
52188
+ let startX = startBeat.bounds.realBounds.x;
52189
+ if (startBeat.beat.index === 0) {
52190
+ startX = startBeat.bounds.barBounds.masterBarBounds.realBounds.x;
52191
+ }
52018
52192
  let endX = endBeat.bounds.realBounds.x + endBeat.bounds.realBounds.w;
52019
52193
  if (endBeat.beat.index === endBeat.beat.voice.beats.length - 1) {
52020
52194
  endX =
@@ -52031,18 +52205,24 @@
52031
52205
  const staffEndX = startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.visualBounds.x +
52032
52206
  startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.visualBounds.w;
52033
52207
  const startSelection = this.uiFacade.createSelectionElement();
52034
- startSelection.setBounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, staffEndX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52208
+ const startSelectionBounds = new Bounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, staffEndX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52209
+ startSelection.setBounds(startSelectionBounds.x, startSelectionBounds.y, startSelectionBounds.w, startSelectionBounds.h);
52210
+ eventArgs.highlightBlocks.push(startSelectionBounds);
52035
52211
  selectionWrapper.appendChild(startSelection);
52036
52212
  const staffStartIndex = startBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.index + 1;
52037
52213
  const staffEndIndex = endBeat.bounds.barBounds.masterBarBounds.staffSystemBounds.index;
52038
52214
  for (let staffIndex = staffStartIndex; staffIndex < staffEndIndex; staffIndex++) {
52039
52215
  const staffBounds = cache.staffSystems[staffIndex];
52040
52216
  const middleSelection = this.uiFacade.createSelectionElement();
52041
- middleSelection.setBounds(staffStartX, staffBounds.visualBounds.y, staffEndX - staffStartX, staffBounds.visualBounds.h);
52217
+ const middleSelectionBounds = new Bounds(staffStartX, staffBounds.visualBounds.y, staffEndX - staffStartX, staffBounds.visualBounds.h);
52218
+ eventArgs.highlightBlocks.push(middleSelectionBounds);
52219
+ middleSelection.setBounds(middleSelectionBounds.x, middleSelectionBounds.y, middleSelectionBounds.w, middleSelectionBounds.h);
52042
52220
  selectionWrapper.appendChild(middleSelection);
52043
52221
  }
52044
52222
  const endSelection = this.uiFacade.createSelectionElement();
52045
- endSelection.setBounds(staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52223
+ const endSelectionBounds = new Bounds(staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - staffStartX, endBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52224
+ eventArgs.highlightBlocks.push(endSelectionBounds);
52225
+ endSelection.setBounds(endSelectionBounds.x, endSelectionBounds.y, endSelectionBounds.w, endSelectionBounds.h);
52046
52226
  selectionWrapper.appendChild(endSelection);
52047
52227
  }
52048
52228
  else {
@@ -52051,6 +52231,7 @@
52051
52231
  selection.setBounds(startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.y, endX - startX, startBeat.bounds.barBounds.masterBarBounds.visualBounds.h);
52052
52232
  selectionWrapper.appendChild(selection);
52053
52233
  }
52234
+ this.playbackRangeHighlightChanged.trigger(eventArgs);
52054
52235
  }
52055
52236
  /**
52056
52237
  * This event is fired whenever a new song is loaded.
@@ -56489,12 +56670,12 @@
56489
56670
  this.endPosition = endPosition;
56490
56671
  }
56491
56672
  get isLinkedWithPrevious() {
56492
- return !!this.previousGlyph && this.previousGlyph.renderer.staff.system === this.renderer.staff.system;
56673
+ return !!this.previousGlyph && this.previousGlyph.renderer.staff?.system === this.renderer.staff.system;
56493
56674
  }
56494
56675
  get isLinkedWithNext() {
56495
56676
  return (!!this.nextGlyph &&
56496
56677
  this.nextGlyph.renderer.isFinalized &&
56497
- this.nextGlyph.renderer.staff.system === this.renderer.staff.system);
56678
+ this.nextGlyph.renderer.staff?.system === this.renderer.staff.system);
56498
56679
  }
56499
56680
  paint(cx, cy, canvas) {
56500
56681
  // if we are linked with the previous, the first glyph of the group will also render this one.
@@ -58882,6 +59063,437 @@
58882
59063
  }
58883
59064
  }
58884
59065
 
59066
+ /**
59067
+ * @internal
59068
+ */
59069
+ class TieGlyph extends Glyph {
59070
+ tieDirection = BeamDirection.Up;
59071
+ slurEffectId;
59072
+ isForEnd;
59073
+ constructor(slurEffectId, forEnd) {
59074
+ super(0, 0);
59075
+ this.slurEffectId = slurEffectId;
59076
+ this.isForEnd = forEnd;
59077
+ }
59078
+ _startX = 0;
59079
+ _startY = 0;
59080
+ _endX = 0;
59081
+ _endY = 0;
59082
+ _tieHeight = 0;
59083
+ _boundingBox;
59084
+ _shouldPaint = false;
59085
+ get checkForOverflow() {
59086
+ return this._shouldPaint && this._boundingBox !== undefined;
59087
+ }
59088
+ getBoundingBoxTop() {
59089
+ if (this._boundingBox) {
59090
+ return this._boundingBox.y;
59091
+ }
59092
+ return this._startY;
59093
+ }
59094
+ getBoundingBoxBottom() {
59095
+ if (this._boundingBox) {
59096
+ return this._boundingBox.y + this._boundingBox.h;
59097
+ }
59098
+ return this._startY;
59099
+ }
59100
+ doLayout() {
59101
+ this.width = 0;
59102
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59103
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59104
+ this._startX = 0;
59105
+ this._endX = 0;
59106
+ this._startY = 0;
59107
+ this._endY = 0;
59108
+ this.height = 0;
59109
+ // if we are on the tie start, we check if we
59110
+ // either can draw till the end note, or we just can draw till the bar end
59111
+ this.tieDirection = this.calculateTieDirection();
59112
+ const forEnd = this.isForEnd;
59113
+ this._shouldPaint = false;
59114
+ if (!forEnd) {
59115
+ if (startNoteRenderer !== endNoteRenderer) {
59116
+ this._startX = this.calculateStartX();
59117
+ this._startY = this.calculateStartY();
59118
+ if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
59119
+ const lastRendererInStaff = startNoteRenderer.staff.barRenderers[startNoteRenderer.staff.barRenderers.length - 1];
59120
+ this._endX = lastRendererInStaff.x + lastRendererInStaff.width;
59121
+ this._endY = this._startY;
59122
+ startNoteRenderer.scoreRenderer.layout.slurRegistry.startMultiSystemSlur(this);
59123
+ }
59124
+ else {
59125
+ this._endX = this.calculateEndX();
59126
+ this._endY = this.caclculateEndY();
59127
+ }
59128
+ }
59129
+ else {
59130
+ this._shouldPaint = true;
59131
+ this._startX = this.calculateStartX();
59132
+ this._endX = this.calculateEndX();
59133
+ this._startY = this.calculateStartY();
59134
+ this._endY = this.caclculateEndY();
59135
+ }
59136
+ this._shouldPaint = true;
59137
+ }
59138
+ else if (startNoteRenderer.staff !== endNoteRenderer.staff) {
59139
+ const firstRendererInStaff = startNoteRenderer.staff.barRenderers[0];
59140
+ this._startX = firstRendererInStaff.x;
59141
+ this._endX = this.calculateEndX();
59142
+ const startGlyph = startNoteRenderer.scoreRenderer.layout.slurRegistry.completeMultiSystemSlur(this);
59143
+ if (startGlyph) {
59144
+ this._startY = startGlyph.calculateMultiSystemSlurY(endNoteRenderer);
59145
+ }
59146
+ else {
59147
+ this._startY = this.caclculateEndY();
59148
+ }
59149
+ this._endY = this.caclculateEndY();
59150
+ this._shouldPaint = startNoteRenderer.staff !== endNoteRenderer.staff;
59151
+ }
59152
+ this._boundingBox = undefined;
59153
+ this.y = Math.min(this._startY, this._endY);
59154
+ if (this.shouldDrawBendSlur()) {
59155
+ this._tieHeight = 0; // TODO: Bend slur height to be considered?
59156
+ }
59157
+ else {
59158
+ this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
59159
+ const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
59160
+ this._boundingBox = tieBoundingBox;
59161
+ this.height = tieBoundingBox.h;
59162
+ if (this.tieDirection === BeamDirection.Up) {
59163
+ // the tie might go above `this.y` due to its shape
59164
+ // here we calculate how much this is so we can consider the
59165
+ // respective overflow
59166
+ const overlap = this.y - tieBoundingBox.y;
59167
+ if (overlap > 0) {
59168
+ this.y -= overlap;
59169
+ }
59170
+ }
59171
+ }
59172
+ }
59173
+ paint(cx, cy, canvas) {
59174
+ if (!this._shouldPaint) {
59175
+ return;
59176
+ }
59177
+ if (this.shouldDrawBendSlur()) {
59178
+ TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
59179
+ }
59180
+ else {
59181
+ 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);
59182
+ }
59183
+ }
59184
+ getTieHeight(_startX, _startY, _endX, _endY) {
59185
+ return this.renderer.smuflMetrics.tieHeight;
59186
+ }
59187
+ calculateMultiSystemSlurY(renderer) {
59188
+ const startRenderer = this.lookupStartBeatRenderer();
59189
+ const startY = this.calculateStartY();
59190
+ const relY = startY - startRenderer.y;
59191
+ return renderer.y + relY;
59192
+ }
59193
+ shouldCreateMultiSystemSlur(renderer) {
59194
+ const endStaff = this.lookupEndBeatRenderer()?.staff;
59195
+ if (!endStaff) {
59196
+ return true;
59197
+ }
59198
+ return renderer.staff.system.index < endStaff.system.index;
59199
+ }
59200
+ static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
59201
+ const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59202
+ if (cp.length === 0) {
59203
+ return new Bounds(x1, y1, x2 - x1, y2 - y1);
59204
+ }
59205
+ // For a musical tie/slur, the extrema occur predictably near the midpoint
59206
+ // Evaluate at midpoint (t=0.5) and check endpoints
59207
+ const p0x = cp[0];
59208
+ const p0y = cp[1];
59209
+ const c1x = cp[2];
59210
+ const c1y = cp[3];
59211
+ const c2x = cp[4];
59212
+ const c2y = cp[5];
59213
+ const p1x = cp[6];
59214
+ const p1y = cp[7];
59215
+ // Evaluate at t=0.5 for midpoint
59216
+ const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
59217
+ const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
59218
+ // Bounds are simply min/max of start, end, and midpoint
59219
+ const xMin = Math.min(p0x, p1x, midX);
59220
+ const xMax = Math.max(p0x, p1x, midX);
59221
+ let yMin = Math.min(p0y, p1y, midY);
59222
+ let yMax = Math.max(p0y, p1y, midY);
59223
+ // Account for thickness of the tie/slur
59224
+ if (down) {
59225
+ yMax += size;
59226
+ }
59227
+ else {
59228
+ yMin -= size;
59229
+ }
59230
+ const b = new Bounds();
59231
+ b.x = xMin;
59232
+ b.y = yMin;
59233
+ b.w = xMax - xMin;
59234
+ b.h = yMax - yMin;
59235
+ return b;
59236
+ }
59237
+ static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
59238
+ if (x1 === x2 && y1 === y2) {
59239
+ return [];
59240
+ }
59241
+ // ensure endX > startX
59242
+ if (x2 < x1) {
59243
+ let t = x1;
59244
+ x1 = x2;
59245
+ x2 = t;
59246
+ t = y1;
59247
+ y1 = y2;
59248
+ y2 = t;
59249
+ }
59250
+ //
59251
+ // calculate control points
59252
+ //
59253
+ offset *= scale;
59254
+ size *= scale;
59255
+ if (down) {
59256
+ offset *= -1;
59257
+ size *= -1;
59258
+ }
59259
+ if (scale >= 1) {
59260
+ size *= 1.2;
59261
+ }
59262
+ // calculate control points on horizontal axis then rotate:
59263
+ /*
59264
+ cp1x/cpy1 cp2x/cpy2
59265
+ *----------------*
59266
+ / \
59267
+ / \
59268
+ x1/y1 * * x2/y2
59269
+
59270
+ cp3 and cp4 are simply with lower height
59271
+ */
59272
+ const dY = y2 - y1;
59273
+ const dX = x2 - x1;
59274
+ const length = Math.sqrt(dX * dX + dY * dY);
59275
+ let cp1x = x1 + length * 0.25;
59276
+ let cp1y = y1 - offset;
59277
+ let cp2x = x1 + length * 0.75;
59278
+ let cp2y = y1 - offset;
59279
+ let cp3x = x1 + length * 0.75;
59280
+ let cp3y = y1 - offset - size;
59281
+ let cp4x = x1 + length * 0.25;
59282
+ let cp4y = y1 - offset - size;
59283
+ const angle = Math.atan2(dY, dX);
59284
+ [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
59285
+ [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
59286
+ [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
59287
+ [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
59288
+ return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
59289
+ }
59290
+ static _rotate(x, y, rotateX, rotateY, angle) {
59291
+ const dx = x - rotateX;
59292
+ const dy = y - rotateY;
59293
+ const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
59294
+ const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
59295
+ return [rotateX + rx, rotateY + ry];
59296
+ }
59297
+ static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
59298
+ const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59299
+ canvas.beginPath();
59300
+ canvas.moveTo(cps[0], cps[1]);
59301
+ canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
59302
+ canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
59303
+ canvas.closePath();
59304
+ canvas.fill();
59305
+ }
59306
+ static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
59307
+ let normalVectorX = y2 - y1;
59308
+ let normalVectorY = x2 - x1;
59309
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59310
+ if (down) {
59311
+ normalVectorX *= -1;
59312
+ }
59313
+ else {
59314
+ normalVectorY *= -1;
59315
+ }
59316
+ // make to unit vector
59317
+ normalVectorX /= length;
59318
+ normalVectorY /= length;
59319
+ let offset = bendSlurHeight * scale;
59320
+ if (x2 - x1 < 20) {
59321
+ offset /= 2;
59322
+ }
59323
+ const centerY = (y2 + y1) / 2;
59324
+ const cp1Y = centerY + offset * normalVectorY;
59325
+ return cp1Y;
59326
+ }
59327
+ static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
59328
+ let normalVectorX = y2 - y1;
59329
+ let normalVectorY = x2 - x1;
59330
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59331
+ if (down) {
59332
+ normalVectorX *= -1;
59333
+ }
59334
+ else {
59335
+ normalVectorY *= -1;
59336
+ }
59337
+ // make to unit vector
59338
+ normalVectorX /= length;
59339
+ normalVectorY /= length;
59340
+ // center of connection
59341
+ // TODO: should be 1/3
59342
+ const centerX = (x2 + x1) / 2;
59343
+ const centerY = (y2 + y1) / 2;
59344
+ let offset = bendSlurHeight * scale;
59345
+ if (x2 - x1 < 20) {
59346
+ offset /= 2;
59347
+ }
59348
+ const cp1X = centerX + offset * normalVectorX;
59349
+ const cp1Y = centerY + offset * normalVectorY;
59350
+ canvas.beginPath();
59351
+ canvas.moveTo(x1, y1);
59352
+ canvas.lineTo(cp1X, cp1Y);
59353
+ canvas.lineTo(x2, y2);
59354
+ canvas.stroke();
59355
+ if (slurText) {
59356
+ const w = canvas.measureText(slurText).width;
59357
+ const textOffset = down ? 0 : -canvas.font.size;
59358
+ canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
59359
+ }
59360
+ }
59361
+ }
59362
+ /**
59363
+ * A common tie implementation using note details for positioning
59364
+ * @internal
59365
+ */
59366
+ class NoteTieGlyph extends TieGlyph {
59367
+ startNote;
59368
+ endNote;
59369
+ startNoteRenderer = null;
59370
+ endNoteRenderer = null;
59371
+ constructor(slurEffectId, startNote, endNote, forEnd) {
59372
+ super(slurEffectId, forEnd);
59373
+ this.startNote = startNote;
59374
+ this.endNote = endNote;
59375
+ }
59376
+ get isLeftHandTap() {
59377
+ return this.startNote === this.endNote;
59378
+ }
59379
+ getTieHeight(startX, startY, endX, endY) {
59380
+ if (this.isLeftHandTap) {
59381
+ return this.renderer.smuflMetrics.tieHeight;
59382
+ }
59383
+ return super.getTieHeight(startX, startY, endX, endY);
59384
+ }
59385
+ calculateTieDirection() {
59386
+ // invert direction (if stems go up, ties go down to not cross them)
59387
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat)) {
59388
+ case BeamDirection.Up:
59389
+ return BeamDirection.Down;
59390
+ default:
59391
+ return BeamDirection.Up;
59392
+ }
59393
+ }
59394
+ calculateStartX() {
59395
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59396
+ if (this.isLeftHandTap) {
59397
+ return this.calculateEndX() - startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
59398
+ }
59399
+ return startNoteRenderer.x + startNoteRenderer.getNoteX(this.startNote, this.getStartNotePosition());
59400
+ }
59401
+ getStartNotePosition() {
59402
+ return NoteXPosition.Center;
59403
+ }
59404
+ calculateStartY() {
59405
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59406
+ if (this.isLeftHandTap) {
59407
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
59408
+ }
59409
+ switch (this.tieDirection) {
59410
+ case BeamDirection.Up:
59411
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
59412
+ default:
59413
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
59414
+ }
59415
+ }
59416
+ calculateEndX() {
59417
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59418
+ if (!endNoteRenderer) {
59419
+ return this.calculateStartY() + this.renderer.smuflMetrics.leftHandTabTieWidth;
59420
+ }
59421
+ if (this.isLeftHandTap) {
59422
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
59423
+ }
59424
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
59425
+ }
59426
+ getEndNotePosition() {
59427
+ return NoteXPosition.Center;
59428
+ }
59429
+ caclculateEndY() {
59430
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59431
+ if (!endNoteRenderer) {
59432
+ return this.calculateStartY();
59433
+ }
59434
+ if (this.isLeftHandTap) {
59435
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
59436
+ }
59437
+ switch (this.tieDirection) {
59438
+ case BeamDirection.Up:
59439
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
59440
+ default:
59441
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
59442
+ }
59443
+ }
59444
+ lookupEndBeatRenderer() {
59445
+ if (!this.endNoteRenderer) {
59446
+ this.endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endNote.beat.voice.bar);
59447
+ }
59448
+ return this.endNoteRenderer;
59449
+ }
59450
+ lookupStartBeatRenderer() {
59451
+ if (!this.startNoteRenderer) {
59452
+ this.startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startNote.beat.voice.bar);
59453
+ }
59454
+ return this.startNoteRenderer;
59455
+ }
59456
+ shouldDrawBendSlur() {
59457
+ return false;
59458
+ }
59459
+ }
59460
+ /**
59461
+ * A tie glyph for continued multi-system ties/slurs
59462
+ * @internal
59463
+ */
59464
+ class ContinuationTieGlyph extends TieGlyph {
59465
+ _startTie;
59466
+ constructor(startTie) {
59467
+ super(startTie.slurEffectId, false);
59468
+ this._startTie = startTie;
59469
+ }
59470
+ lookupStartBeatRenderer() {
59471
+ return this.renderer;
59472
+ }
59473
+ lookupEndBeatRenderer() {
59474
+ return this.renderer;
59475
+ }
59476
+ shouldDrawBendSlur() {
59477
+ return false;
59478
+ }
59479
+ calculateTieDirection() {
59480
+ return this._startTie.tieDirection;
59481
+ }
59482
+ calculateStartY() {
59483
+ return this._startTie.calculateMultiSystemSlurY(this.renderer);
59484
+ }
59485
+ caclculateEndY() {
59486
+ return this.calculateStartY();
59487
+ }
59488
+ calculateStartX() {
59489
+ return this.renderer.staff.barRenderers[0].x;
59490
+ }
59491
+ calculateEndX() {
59492
+ const last = this.renderer.staff.barRenderers[this.renderer.staff.barRenderers.length - 1];
59493
+ return last.x + last.width;
59494
+ }
59495
+ }
59496
+
58885
59497
  /**
58886
59498
  * This glyph acts as container for handling
58887
59499
  * multiple voice rendering
@@ -59352,6 +59964,63 @@
59352
59964
  }
59353
59965
  }
59354
59966
 
59967
+ /**
59968
+ * This registry keeps track of which slurs and ties were started and needs completion.
59969
+ * Slurs might span multiple systems, and in such cases we need to create additional
59970
+ * slur/ties in the intermediate and end system.
59971
+ *
59972
+ * @internal
59973
+ *
59974
+ */
59975
+ class SlurRegistry {
59976
+ _staffLookup = new Map();
59977
+ clear() {
59978
+ this._staffLookup.clear();
59979
+ }
59980
+ startMultiSystemSlur(startGlyph) {
59981
+ const staffId = SlurRegistry._staffId(startGlyph.renderer.staff);
59982
+ let container;
59983
+ if (!this._staffLookup.has(staffId)) {
59984
+ container = {
59985
+ startedSlurs: new Map()
59986
+ };
59987
+ this._staffLookup.set(staffId, container);
59988
+ }
59989
+ else {
59990
+ container = this._staffLookup.get(staffId);
59991
+ }
59992
+ container.startedSlurs.set(startGlyph.slurEffectId, { startGlyph });
59993
+ }
59994
+ static _staffId(staff) {
59995
+ return `${staff.modelStaff.index}.${staff.modelStaff.track.index}.${staff.staffId}`;
59996
+ }
59997
+ completeMultiSystemSlur(endGlyph) {
59998
+ const staffId = SlurRegistry._staffId(endGlyph.renderer.staff);
59999
+ if (!this._staffLookup.has(staffId)) {
60000
+ return undefined;
60001
+ }
60002
+ const container = this._staffLookup.get(staffId);
60003
+ if (container.startedSlurs.has(endGlyph.slurEffectId)) {
60004
+ const info = container.startedSlurs.get(endGlyph.slurEffectId);
60005
+ info.endGlyph = endGlyph;
60006
+ return info.startGlyph;
60007
+ }
60008
+ return undefined;
60009
+ }
60010
+ *getAllContinuations(renderer) {
60011
+ const staffId = SlurRegistry._staffId(renderer.staff);
60012
+ if (!this._staffLookup.has(staffId) || renderer.index > 0) {
60013
+ return;
60014
+ }
60015
+ const container = this._staffLookup.get(staffId);
60016
+ for (const g of container.startedSlurs.values()) {
60017
+ if (g.startGlyph.shouldCreateMultiSystemSlur(renderer)) {
60018
+ yield g.startGlyph;
60019
+ }
60020
+ }
60021
+ }
60022
+ }
60023
+
59355
60024
  /**
59356
60025
  * A Staff represents a single line within a StaffSystem.
59357
60026
  * It stores BarRenderer instances created from a given factory.
@@ -59473,7 +60142,6 @@
59473
60142
  this._sharedLayoutData = new Map();
59474
60143
  const lastBar = this.barRenderers[this.barRenderers.length - 1];
59475
60144
  this.barRenderers.splice(this.barRenderers.length - 1, 1);
59476
- this.system.layout.unregisterBarRenderer(this.staffId, lastBar);
59477
60145
  this.topOverflow = 0;
59478
60146
  this.bottomOverflow = 0;
59479
60147
  for (const r of this.barRenderers) {
@@ -59566,23 +60234,23 @@
59566
60234
  // changes in the overflows
59567
60235
  let needsSecondPass = false;
59568
60236
  let topOverflow = this.topOverflow;
59569
- for (let i = 0; i < this.barRenderers.length; i++) {
59570
- this.barRenderers[i].y = this.topPadding + topOverflow;
59571
- if (this.barRenderers[i].finalizeRenderer()) {
60237
+ for (const renderer of this.barRenderers) {
60238
+ renderer.registerMultiSystemSlurs(this.system.layout.slurRegistry.getAllContinuations(renderer));
60239
+ if (renderer.finalizeRenderer()) {
59572
60240
  needsSecondPass = true;
59573
60241
  }
59574
- this.height = Math.max(this.height, this.barRenderers[i].height);
60242
+ this.height = Math.max(this.height, renderer.height);
59575
60243
  }
59576
60244
  // 2nd pass: move renderers to correct position respecting the new overflows
59577
60245
  if (needsSecondPass) {
59578
60246
  topOverflow = this.topOverflow;
59579
60247
  // shift all the renderers to the new position to match required spacing
59580
- for (let i = 0; i < this.barRenderers.length; i++) {
59581
- this.barRenderers[i].y = this.topPadding + topOverflow;
60248
+ for (const renderer of this.barRenderers) {
60249
+ renderer.y = this.topPadding + topOverflow;
59582
60250
  }
59583
60251
  // finalize again (to align ties)
59584
- for (let i = 0; i < this.barRenderers.length; i++) {
59585
- this.barRenderers[i].finalizeRenderer();
60252
+ for (const renderer of this.barRenderers) {
60253
+ renderer.finalizeRenderer();
59586
60254
  }
59587
60255
  }
59588
60256
  if (this.height > 0) {
@@ -60188,6 +60856,7 @@
60188
60856
  if (newBarDisplayScale > barDisplayScale) {
60189
60857
  barDisplayScale = newBarDisplayScale;
60190
60858
  }
60859
+ lastBar.afterReverted();
60191
60860
  }
60192
60861
  this.width -= width;
60193
60862
  this.computedWidth -= width;
@@ -60707,12 +61376,16 @@
60707
61376
  constructor(renderer) {
60708
61377
  this.renderer = renderer;
60709
61378
  }
61379
+ slurRegistry = new SlurRegistry();
60710
61380
  resize() {
60711
61381
  this._lazyPartials.clear();
61382
+ this.slurRegistry.clear();
60712
61383
  this.doResize();
60713
61384
  }
60714
61385
  layoutAndRender() {
60715
61386
  this._lazyPartials.clear();
61387
+ this.slurRegistry.clear();
61388
+ this._barRendererLookup.clear();
60716
61389
  this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
60717
61390
  const score = this.renderer.score;
60718
61391
  this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
@@ -60981,17 +61654,6 @@
60981
61654
  }
60982
61655
  }
60983
61656
  }
60984
- unregisterBarRenderer(key, renderer) {
60985
- if (this._barRendererLookup.has(key)) {
60986
- const lookup = this._barRendererLookup.get(key);
60987
- lookup.delete(renderer.bar.id);
60988
- if (renderer.additionalMultiRestBars) {
60989
- for (const b of renderer.additionalMultiRestBars) {
60990
- lookup.delete(b.id);
60991
- }
60992
- }
60993
- }
60994
- }
60995
61657
  getRendererForBar(key, bar) {
60996
61658
  const barRendererId = bar.id;
60997
61659
  if (this._barRendererLookup.has(key) && this._barRendererLookup.get(key).has(barRendererId)) {
@@ -61980,6 +62642,7 @@
61980
62642
  _voiceContainers = new Map();
61981
62643
  _postBeatGlyphs = new LeftToRightLayoutingGlyphGroup();
61982
62644
  _ties = [];
62645
+ _multiSystemSlurs;
61983
62646
  topEffects;
61984
62647
  bottomEffects;
61985
62648
  get nextRenderer() {
@@ -62132,6 +62795,11 @@
62132
62795
  }
62133
62796
  }
62134
62797
  _appliedLayoutingInfo = 0;
62798
+ afterReverted() {
62799
+ this.staff = undefined;
62800
+ this.registerMultiSystemSlurs(undefined);
62801
+ this.isFinalized = false;
62802
+ }
62135
62803
  afterStaffBarReverted() {
62136
62804
  this.topEffects.afterStaffBarReverted();
62137
62805
  this.bottomEffects.afterStaffBarReverted();
@@ -62177,13 +62845,26 @@
62177
62845
  return true;
62178
62846
  }
62179
62847
  isFinalized = false;
62180
- finalizeRenderer() {
62181
- this.isFinalized = true;
62848
+ registerMultiSystemSlurs(startedTies) {
62849
+ if (!startedTies) {
62850
+ this._multiSystemSlurs = undefined;
62851
+ return;
62852
+ }
62853
+ let ties = undefined;
62854
+ for (const g of startedTies) {
62855
+ const continuation = new ContinuationTieGlyph(g);
62856
+ continuation.renderer = this;
62857
+ continuation.tieDirection = g.tieDirection;
62858
+ if (!ties) {
62859
+ ties = [];
62860
+ }
62861
+ ties.push(continuation);
62862
+ }
62863
+ this._multiSystemSlurs = ties;
62864
+ }
62865
+ _finalizeTies(ties, barTop, barBottom) {
62182
62866
  let didChangeOverflows = false;
62183
- // allow spacing to be used for tie overflows
62184
- const barTop = this.y;
62185
- const barBottom = this.y + this.height;
62186
- for (const t of this._ties) {
62867
+ for (const t of ties) {
62187
62868
  const tie = t;
62188
62869
  tie.doLayout();
62189
62870
  if (t.checkForOverflow) {
@@ -62204,6 +62885,21 @@
62204
62885
  }
62205
62886
  }
62206
62887
  }
62888
+ return didChangeOverflows;
62889
+ }
62890
+ finalizeRenderer() {
62891
+ this.isFinalized = true;
62892
+ let didChangeOverflows = false;
62893
+ // allow spacing to be used for tie overflows
62894
+ const barTop = this.y;
62895
+ const barBottom = this.y + this.height;
62896
+ if (this._finalizeTies(this._ties, barTop, barBottom)) {
62897
+ didChangeOverflows = true;
62898
+ }
62899
+ const multiSystemSlurs = this._multiSystemSlurs;
62900
+ if (multiSystemSlurs && this._finalizeTies(multiSystemSlurs, barTop, barBottom)) {
62901
+ didChangeOverflows = true;
62902
+ }
62207
62903
  const topHeightChanged = this.topEffects.finalizeEffects();
62208
62904
  const bottomHeightChanged = this.bottomEffects.finalizeEffects();
62209
62905
  if (topHeightChanged || bottomHeightChanged) {
@@ -62384,6 +63080,16 @@
62384
63080
  }
62385
63081
  canvas.color = this.resources.mainGlyphColor;
62386
63082
  this._postBeatGlyphs.paint(cx + this.x, cy + this.y, canvas);
63083
+ this._paintMultiSystemSlurs(cx, cy, canvas);
63084
+ }
63085
+ _paintMultiSystemSlurs(cx, cy, canvas) {
63086
+ const multiSystemSlurs = this._multiSystemSlurs;
63087
+ if (!multiSystemSlurs) {
63088
+ return;
63089
+ }
63090
+ for (const slur of multiSystemSlurs) {
63091
+ slur.paint(cx, cy, canvas);
63092
+ }
62387
63093
  }
62388
63094
  paintBackground(cx, cy, canvas) {
62389
63095
  this.layoutingInfo.paint(cx + this.x + this._preBeatGlyphs.x + this._preBeatGlyphs.width, cy + this.y + this.height, canvas);
@@ -64462,6 +65168,13 @@
64462
65168
  }
64463
65169
  }
64464
65170
  else {
65171
+ // clear out staves during re-layout, this info is outdated during
65172
+ // re-layout of the bars
65173
+ for (const r of this._allMasterBarRenderers) {
65174
+ for (const b of r.renderers) {
65175
+ b.afterReverted();
65176
+ }
65177
+ }
64465
65178
  this._systems = [];
64466
65179
  let currentIndex = 0;
64467
65180
  const maxWidth = this._maxWidth;
@@ -64923,7 +65636,7 @@
64923
65636
  // as during layout things are still moving
64924
65637
  let actualLineHeight = this.height;
64925
65638
  const thisStaff = renderer.staff;
64926
- const allStaves = renderer.staff.system.allStaves;
65639
+ const allStaves = thisStaff.system.allStaves;
64927
65640
  let isExtended = false;
64928
65641
  if (this._extendToNextStaff && thisStaff.index < allStaves.length - 1) {
64929
65642
  const nextStaff = allStaves[thisStaff.index + 1];
@@ -65105,7 +65818,7 @@
65105
65818
  }
65106
65819
  // during system fitting it can happen that we have fraction widths
65107
65820
  // but to have lines until the full end-pixel we round up.
65108
- // this way we avoid holes,
65821
+ // this way we avoid holes,
65109
65822
  const lineWidth = this.width;
65110
65823
  // we want the lines to be exactly virtually aligned with the respective Y-position
65111
65824
  // for note heads to align correctly
@@ -65871,375 +66584,23 @@
65871
66584
  /**
65872
66585
  * @internal
65873
66586
  */
65874
- class TieGlyph extends Glyph {
65875
- startBeat;
65876
- endBeat;
65877
- yOffset = 0;
65878
- forEnd;
65879
- startNoteRenderer = null;
65880
- endNoteRenderer = null;
65881
- tieDirection = BeamDirection.Up;
65882
- constructor(startBeat, endBeat, forEnd) {
65883
- super(0, 0);
65884
- this.startBeat = startBeat;
65885
- this.endBeat = endBeat;
65886
- this.forEnd = forEnd;
65887
- }
65888
- _startX = 0;
65889
- _startY = 0;
65890
- _endX = 0;
65891
- _endY = 0;
65892
- _tieHeight = 0;
65893
- _shouldDraw = false;
65894
- _boundingBox;
65895
- get checkForOverflow() {
65896
- return this._boundingBox !== undefined;
65897
- }
65898
- getBoundingBoxTop() {
65899
- if (this._boundingBox) {
65900
- return this._boundingBox.y;
65901
- }
65902
- return this._startY;
65903
- }
65904
- getBoundingBoxBottom() {
65905
- if (this._boundingBox) {
65906
- return this._boundingBox.y + this._boundingBox.h;
65907
- }
65908
- return this._startY;
65909
- }
65910
- doLayout() {
65911
- this.width = 0;
65912
- // TODO fix nullability of start/end beat,
65913
- if (!this.endBeat) {
65914
- this._shouldDraw = false;
65915
- return;
65916
- }
65917
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
65918
- this.startNoteRenderer = startNoteRenderer;
65919
- const endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
65920
- this.endNoteRenderer = endNoteRenderer;
65921
- this._startX = 0;
65922
- this._endX = 0;
65923
- this._startY = 0;
65924
- this._endY = 0;
65925
- this.height = 0;
65926
- this._shouldDraw = false;
65927
- // if we are on the tie start, we check if we
65928
- // either can draw till the end note, or we just can draw till the bar end
65929
- this.tieDirection = !startNoteRenderer
65930
- ? this.getBeamDirection(this.endBeat, endNoteRenderer)
65931
- : this.getBeamDirection(this.startBeat, startNoteRenderer);
65932
- if (!this.forEnd && startNoteRenderer) {
65933
- // line break or bar break
65934
- if (startNoteRenderer !== endNoteRenderer) {
65935
- this._startX = startNoteRenderer.x + this.getStartX();
65936
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
65937
- // line break: to bar end
65938
- if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
65939
- this._endX = startNoteRenderer.x + startNoteRenderer.width;
65940
- this._endY = this._startY;
65941
- }
65942
- else {
65943
- this._endX = endNoteRenderer.x + this.getEndX();
65944
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65945
- }
65946
- }
65947
- else {
65948
- this._startX = startNoteRenderer.x + this.getStartX();
65949
- this._endX = endNoteRenderer.x + this.getEndX();
65950
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
65951
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65952
- }
65953
- this._shouldDraw = true;
65954
- }
65955
- else if (!startNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
65956
- this._startX = endNoteRenderer.x;
65957
- this._endX = endNoteRenderer.x + this.getEndX();
65958
- this._startY = endNoteRenderer.y + this.getEndY() + this.yOffset;
65959
- this._endY = this._startY;
65960
- this._shouldDraw = true;
65961
- }
65962
- this._boundingBox = undefined;
65963
- if (this._shouldDraw) {
65964
- this.y = Math.min(this._startY, this._endY);
65965
- if (this.shouldDrawBendSlur()) {
65966
- this._tieHeight = 0; // TODO: Bend slur height to be considered?
65967
- }
65968
- else {
65969
- this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
65970
- const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
65971
- this._boundingBox = tieBoundingBox;
65972
- this.height = tieBoundingBox.h;
65973
- if (this.tieDirection === BeamDirection.Up) {
65974
- // the tie might go above `this.y` due to its shape
65975
- // here we calculate how much this is so we can consider the
65976
- // respective overflow
65977
- const overlap = this.y - tieBoundingBox.y;
65978
- if (overlap > 0) {
65979
- this.y -= overlap;
65980
- }
65981
- }
65982
- }
65983
- }
65984
- }
65985
- paint(cx, cy, canvas) {
65986
- if (this._shouldDraw) {
65987
- if (this.shouldDrawBendSlur()) {
65988
- TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
65989
- }
65990
- else {
65991
- 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);
65992
- }
65993
- }
65994
- }
65995
- shouldDrawBendSlur() {
65996
- return false;
65997
- }
65998
- getTieHeight(_startX, _startY, _endX, _endY) {
65999
- return this.renderer.smuflMetrics.tieHeight;
66000
- }
66001
- getBeamDirection(_beat, _noteRenderer) {
66002
- return BeamDirection.Down;
66003
- }
66004
- getStartY() {
66005
- return 0;
66006
- }
66007
- getEndY() {
66008
- return 0;
66009
- }
66010
- getStartX() {
66011
- return 0;
66012
- }
66013
- getEndX() {
66014
- return 0;
66015
- }
66016
- static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
66017
- const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66018
- // For a musical tie/slur, the extrema occur predictably near the midpoint
66019
- // Evaluate at midpoint (t=0.5) and check endpoints
66020
- const p0x = cp[0];
66021
- const p0y = cp[1];
66022
- const c1x = cp[2];
66023
- const c1y = cp[3];
66024
- const c2x = cp[4];
66025
- const c2y = cp[5];
66026
- const p1x = cp[6];
66027
- const p1y = cp[7];
66028
- // Evaluate at t=0.5 for midpoint
66029
- const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
66030
- const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
66031
- // Bounds are simply min/max of start, end, and midpoint
66032
- const xMin = Math.min(p0x, p1x, midX);
66033
- const xMax = Math.max(p0x, p1x, midX);
66034
- let yMin = Math.min(p0y, p1y, midY);
66035
- let yMax = Math.max(p0y, p1y, midY);
66036
- // Account for thickness of the tie/slur
66037
- if (down) {
66038
- yMax += size;
66039
- }
66040
- else {
66041
- yMin -= size;
66042
- }
66043
- const b = new Bounds();
66044
- b.x = xMin;
66045
- b.y = yMin;
66046
- b.w = xMax - xMin;
66047
- b.h = yMax - yMin;
66048
- return b;
66049
- }
66050
- static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
66051
- if (x1 === x2 && y1 === y2) {
66052
- return [];
66053
- }
66054
- // ensure endX > startX
66055
- if (x2 < x1) {
66056
- let t = x1;
66057
- x1 = x2;
66058
- x2 = t;
66059
- t = y1;
66060
- y1 = y2;
66061
- y2 = t;
66062
- }
66063
- //
66064
- // calculate control points
66065
- //
66066
- offset *= scale;
66067
- size *= scale;
66068
- if (down) {
66069
- offset *= -1;
66070
- size *= -1;
66071
- }
66072
- if (scale >= 1) {
66073
- size *= 1.2;
66074
- }
66075
- // calculate control points on horizontal axis then rotate:
66076
- /*
66077
- cp1x/cpy1 cp2x/cpy2
66078
- *----------------*
66079
- / \
66080
- / \
66081
- x1/y1 * * x2/y2
66082
-
66083
- cp3 and cp4 are simply with lower height
66084
- */
66085
- const dY = y2 - y1;
66086
- const dX = x2 - x1;
66087
- const length = Math.sqrt(dX * dX + dY * dY);
66088
- let cp1x = x1 + length * 0.25;
66089
- let cp1y = y1 - offset;
66090
- let cp2x = x1 + length * 0.75;
66091
- let cp2y = y1 - offset;
66092
- let cp3x = x1 + length * 0.75;
66093
- let cp3y = y1 - offset - size;
66094
- let cp4x = x1 + length * 0.25;
66095
- let cp4y = y1 - offset - size;
66096
- const angle = Math.atan2(dY, dX);
66097
- [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
66098
- [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
66099
- [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
66100
- [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
66101
- return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
66102
- }
66103
- static _rotate(x, y, rotateX, rotateY, angle) {
66104
- const dx = x - rotateX;
66105
- const dy = y - rotateY;
66106
- const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
66107
- const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
66108
- return [rotateX + rx, rotateY + ry];
66109
- }
66110
- static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
66111
- const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66112
- canvas.beginPath();
66113
- canvas.moveTo(cps[0], cps[1]);
66114
- canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
66115
- canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
66116
- canvas.closePath();
66117
- canvas.fill();
66118
- }
66119
- static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
66120
- let normalVectorX = y2 - y1;
66121
- let normalVectorY = x2 - x1;
66122
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66123
- if (down) {
66124
- normalVectorX *= -1;
66125
- }
66126
- else {
66127
- normalVectorY *= -1;
66128
- }
66129
- // make to unit vector
66130
- normalVectorX /= length;
66131
- normalVectorY /= length;
66132
- let offset = bendSlurHeight * scale;
66133
- if (x2 - x1 < 20) {
66134
- offset /= 2;
66135
- }
66136
- const centerY = (y2 + y1) / 2;
66137
- const cp1Y = centerY + offset * normalVectorY;
66138
- return cp1Y;
66139
- }
66140
- static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
66141
- let normalVectorX = y2 - y1;
66142
- let normalVectorY = x2 - x1;
66143
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66144
- if (down) {
66145
- normalVectorX *= -1;
66146
- }
66147
- else {
66148
- normalVectorY *= -1;
66149
- }
66150
- // make to unit vector
66151
- normalVectorX /= length;
66152
- normalVectorY /= length;
66153
- // center of connection
66154
- // TODO: should be 1/3
66155
- const centerX = (x2 + x1) / 2;
66156
- const centerY = (y2 + y1) / 2;
66157
- let offset = bendSlurHeight * scale;
66158
- if (x2 - x1 < 20) {
66159
- offset /= 2;
66160
- }
66161
- const cp1X = centerX + offset * normalVectorX;
66162
- const cp1Y = centerY + offset * normalVectorY;
66163
- canvas.beginPath();
66164
- canvas.moveTo(x1, y1);
66165
- canvas.lineTo(cp1X, cp1Y);
66166
- canvas.lineTo(x2, y2);
66167
- canvas.stroke();
66168
- if (slurText) {
66169
- const w = canvas.measureText(slurText).width;
66170
- const textOffset = down ? 0 : -canvas.font.size;
66171
- canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
66172
- }
66173
- }
66174
- }
66175
-
66176
- /**
66177
- * @internal
66178
- */
66179
- class NumberedTieGlyph extends TieGlyph {
66180
- startNote;
66181
- endNote;
66182
- constructor(startNote, endNote, forEnd = false) {
66183
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
66184
- this.startNote = startNote;
66185
- this.endNote = endNote;
66186
- }
66187
- get _isLeftHandTap() {
66188
- return this.startNote === this.endNote;
66189
- }
66587
+ class NumberedTieGlyph extends NoteTieGlyph {
66190
66588
  shouldDrawBendSlur() {
66191
66589
  return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66192
66590
  !!this.startNote.bendOrigin &&
66193
66591
  this.startNote.isTieOrigin);
66194
66592
  }
66195
- doLayout() {
66196
- super.doLayout();
66197
- }
66198
- getBeamDirection(_beat, _noteRenderer) {
66593
+ calculateTieDirection() {
66199
66594
  return BeamDirection.Up;
66200
66595
  }
66201
- getStartY() {
66202
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66203
- }
66204
- getEndY() {
66205
- return this.getStartY();
66206
- }
66207
- getStartX() {
66208
- if (this._isLeftHandTap) {
66209
- return this.getEndX() - this.startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
66210
- }
66211
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66212
- }
66213
- getEndX() {
66214
- if (this._isLeftHandTap) {
66215
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66216
- }
66217
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66218
- }
66219
66596
  }
66220
66597
 
66221
66598
  /**
66222
66599
  * @internal
66223
66600
  */
66224
- class TabTieGlyph extends TieGlyph {
66225
- startNote;
66226
- endNote;
66227
- constructor(startNote, endNote, forEnd = false) {
66228
- super(startNote.beat, endNote.beat, forEnd);
66229
- this.startNote = startNote;
66230
- this.endNote = endNote;
66231
- }
66232
- get _isLeftHandTap() {
66233
- return this.startNote === this.endNote;
66234
- }
66235
- getTieHeight(startX, startY, endX, endY) {
66236
- if (this._isLeftHandTap) {
66237
- return this.startNoteRenderer.smuflMetrics.tieHeight;
66238
- }
66239
- return super.getTieHeight(startX, startY, endX, endY);
66240
- }
66241
- getBeamDirection(_beat, _noteRenderer) {
66242
- if (this._isLeftHandTap) {
66601
+ class TabTieGlyph extends NoteTieGlyph {
66602
+ calculateTieDirection() {
66603
+ if (this.isLeftHandTap) {
66243
66604
  return BeamDirection.Up;
66244
66605
  }
66245
66606
  return TabTieGlyph.getBeamDirectionForNote(this.startNote);
@@ -66247,54 +66608,25 @@
66247
66608
  static getBeamDirectionForNote(note) {
66248
66609
  return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
66249
66610
  }
66250
- getStartY() {
66251
- if (this._isLeftHandTap) {
66252
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
66253
- }
66254
- if (this.tieDirection === BeamDirection.Up) {
66255
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66256
- }
66257
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
66258
- }
66259
- getEndY() {
66260
- return this.getStartY();
66261
- }
66262
- getStartX() {
66263
- if (this._isLeftHandTap) {
66264
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
66265
- }
66266
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66267
- }
66268
- getEndX() {
66269
- if (this._isLeftHandTap) {
66270
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66271
- }
66272
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66273
- }
66274
66611
  }
66275
66612
 
66276
66613
  /**
66277
66614
  * @internal
66278
66615
  */
66279
- class NumberedSlurGlyph extends TabTieGlyph {
66280
- _direction;
66616
+ class TabSlurGlyph extends TabTieGlyph {
66281
66617
  _forSlide;
66282
- constructor(startNote, endNote, forSlide, forEnd = false) {
66283
- super(startNote, endNote, forEnd);
66284
- this._direction = BeamDirection.Up;
66618
+ constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
66619
+ super(slurEffectId, startNote, endNote, forEnd);
66285
66620
  this._forSlide = forSlide;
66286
66621
  }
66287
66622
  getTieHeight(startX, _startY, endX, _endY) {
66288
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
66623
+ return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
66289
66624
  }
66290
66625
  tryExpand(startNote, endNote, forSlide, forEnd) {
66291
66626
  // same type required
66292
66627
  if (this._forSlide !== forSlide) {
66293
66628
  return false;
66294
66629
  }
66295
- if (this.forEnd !== forEnd) {
66296
- return false;
66297
- }
66298
66630
  // same start and endbeat
66299
66631
  if (this.startNote.beat.id !== startNote.beat.id) {
66300
66632
  return false;
@@ -66302,41 +66634,43 @@
66302
66634
  if (this.endNote.beat.id !== endNote.beat.id) {
66303
66635
  return false;
66304
66636
  }
66637
+ const isForEnd = this.renderer === this.lookupEndBeatRenderer();
66638
+ if (isForEnd !== forEnd) {
66639
+ return false;
66640
+ }
66641
+ // same draw direction
66642
+ if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
66643
+ return false;
66644
+ }
66305
66645
  // if we can expand, expand in correct direction
66306
- switch (this._direction) {
66646
+ switch (this.tieDirection) {
66307
66647
  case BeamDirection.Up:
66308
66648
  if (startNote.realValue > this.startNote.realValue) {
66309
66649
  this.startNote = startNote;
66310
- this.startBeat = startNote.beat;
66311
66650
  }
66312
66651
  if (endNote.realValue > this.endNote.realValue) {
66313
66652
  this.endNote = endNote;
66314
- this.endBeat = endNote.beat;
66315
66653
  }
66316
66654
  break;
66317
66655
  case BeamDirection.Down:
66318
66656
  if (startNote.realValue < this.startNote.realValue) {
66319
66657
  this.startNote = startNote;
66320
- this.startBeat = startNote.beat;
66321
66658
  }
66322
66659
  if (endNote.realValue < this.endNote.realValue) {
66323
66660
  this.endNote = endNote;
66324
- this.endBeat = endNote.beat;
66325
66661
  }
66326
66662
  break;
66327
66663
  }
66328
66664
  return true;
66329
66665
  }
66330
- paint(cx, cy, canvas) {
66331
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66332
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
66333
- const slurId = `numbered.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
66334
- const renderer = this.renderer;
66335
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
66336
- if (!isSlurRendered) {
66337
- renderer.staff.setSharedLayoutData(slurId, true);
66338
- super.paint(cx, cy, canvas);
66339
- }
66666
+ }
66667
+
66668
+ /**
66669
+ * @internal
66670
+ */
66671
+ class NumberedSlurGlyph extends TabSlurGlyph {
66672
+ calculateTieDirection() {
66673
+ return BeamDirection.Up;
66340
66674
  }
66341
66675
  }
66342
66676
 
@@ -66344,23 +66678,31 @@
66344
66678
  * @internal
66345
66679
  */
66346
66680
  class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66681
+ _slurs = new Map();
66347
66682
  _effectSlurs = [];
66683
+ doLayout() {
66684
+ this._slurs.clear();
66685
+ this._effectSlurs = [];
66686
+ super.doLayout();
66687
+ }
66348
66688
  createTies(n) {
66349
66689
  // create a tie if any effect requires it
66350
66690
  if (!n.isVisible) {
66351
66691
  return;
66352
66692
  }
66353
- if (n.isTieOrigin && n.tieDestination.isVisible) {
66354
- const tie = new NumberedTieGlyph(n, n.tieDestination, false);
66693
+ if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
66694
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
66355
66695
  this.addTie(tie);
66696
+ this._slurs.set(tie.slurEffectId, tie);
66356
66697
  }
66357
66698
  if (n.isTieDestination) {
66358
- const tie = new NumberedTieGlyph(n.tieOrigin, n, true);
66699
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
66359
66700
  this.addTie(tie);
66360
66701
  }
66361
- if (n.isLeftHandTapped && !n.isHammerPullDestination) {
66362
- const tapSlur = new NumberedTieGlyph(n, n, false);
66702
+ if (n.isLeftHandTapped && !n.isHammerPullDestination && !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
66703
+ const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
66363
66704
  this.addTie(tapSlur);
66705
+ this._slurs.set(tapSlur.slurEffectId, tapSlur);
66364
66706
  }
66365
66707
  // start effect slur on first beat
66366
66708
  if (n.isEffectSlurOrigin && n.effectSlurDestination) {
@@ -66372,9 +66714,11 @@
66372
66714
  }
66373
66715
  }
66374
66716
  if (!expanded) {
66375
- const effectSlur = new NumberedSlurGlyph(n, n.effectSlurDestination, false, false);
66717
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
66376
66718
  this._effectSlurs.push(effectSlur);
66377
66719
  this.addTie(effectSlur);
66720
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66721
+ this._slurs.set('numbered.slur.effect', effectSlur);
66378
66722
  }
66379
66723
  }
66380
66724
  // end effect slur on last beat
@@ -66387,9 +66731,11 @@
66387
66731
  }
66388
66732
  }
66389
66733
  if (!expanded) {
66390
- const effectSlur = new NumberedSlurGlyph(n.effectSlurOrigin, n, false, true);
66734
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
66391
66735
  this._effectSlurs.push(effectSlur);
66392
66736
  this.addTie(effectSlur);
66737
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66738
+ this._slurs.set('numbered.slur.effect', effectSlur);
66393
66739
  }
66394
66740
  }
66395
66741
  }
@@ -66782,10 +67128,11 @@
66782
67128
  if (sr.shortestDuration < this.container.beat.duration) {
66783
67129
  sr.shortestDuration = this.container.beat.duration;
66784
67130
  }
66785
- const glyphY = sr.getLineY(sr.getNoteLine());
66786
67131
  if (!this.container.beat.isEmpty) {
67132
+ const glyphY = sr.getLineY(0);
66787
67133
  let numberWithinOctave = '0';
66788
67134
  if (this.container.beat.notes.length > 0) {
67135
+ const note = this.container.beat.notes[0];
66789
67136
  const kst = this.renderer.bar.keySignatureType;
66790
67137
  const ks = this.renderer.bar.keySignature;
66791
67138
  const ksi = ks + 7;
@@ -66793,7 +67140,6 @@
66793
67140
  ? NumberedBeatGlyph.minorKeySignatureOneValues
66794
67141
  : NumberedBeatGlyph.majorKeySignatureOneValues;
66795
67142
  const oneNoteValue = oneNoteValues[ksi];
66796
- const note = this.container.beat.notes[0];
66797
67143
  if (note.isDead) {
66798
67144
  numberWithinOctave = 'X';
66799
67145
  }
@@ -66840,7 +67186,7 @@
66840
67186
  // Note dots
66841
67187
  if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
66842
67188
  for (let i = 0; i < this.container.beat.dots; i++) {
66843
- const dot = new AugmentationDotGlyph(0, sr.getLineY(0));
67189
+ const dot = new AugmentationDotGlyph(0, glyphY);
66844
67190
  dot.renderer = this.renderer;
66845
67191
  this.addEffect(dot);
66846
67192
  }
@@ -66868,7 +67214,7 @@
66868
67214
  numberOfQuarterNotes += numberOfAddedQuarters;
66869
67215
  }
66870
67216
  for (let i = 0; i < numberOfQuarterNotes - 1; i++) {
66871
- const dash = new NumberedDashGlyph(0, sr.getLineY(0), this.container.beat);
67217
+ const dash = new NumberedDashGlyph(0, glyphY, this.container.beat);
66872
67218
  dash.renderer = this.renderer;
66873
67219
  this.addNormal(dash);
66874
67220
  }
@@ -67338,7 +67684,7 @@
67338
67684
  }
67339
67685
  }
67340
67686
  }
67341
- getNoteLine() {
67687
+ getNoteLine(_note) {
67342
67688
  return 0;
67343
67689
  }
67344
67690
  get tupletOffset() {
@@ -69553,87 +69899,128 @@
69553
69899
  * @internal
69554
69900
  */
69555
69901
  class ScoreLegatoGlyph extends TieGlyph {
69556
- constructor(startBeat, endBeat, forEnd = false) {
69557
- super(startBeat, endBeat, forEnd);
69902
+ startBeat;
69903
+ endBeat;
69904
+ startBeatRenderer = null;
69905
+ endBeatRenderer = null;
69906
+ constructor(slurEffectId, startBeat, endBeat, forEnd) {
69907
+ super(slurEffectId, forEnd);
69908
+ this.startBeat = startBeat;
69909
+ this.endBeat = endBeat;
69558
69910
  }
69559
69911
  doLayout() {
69560
69912
  super.doLayout();
69561
69913
  }
69562
- getBeamDirection(beat, noteRenderer) {
69563
- if (beat.isRest) {
69914
+ lookupStartBeatRenderer() {
69915
+ if (!this.startBeatRenderer) {
69916
+ this.startBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
69917
+ }
69918
+ return this.startBeatRenderer;
69919
+ }
69920
+ lookupEndBeatRenderer() {
69921
+ if (!this.endBeatRenderer) {
69922
+ this.endBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
69923
+ }
69924
+ return this.endBeatRenderer;
69925
+ }
69926
+ shouldDrawBendSlur() {
69927
+ return false;
69928
+ }
69929
+ calculateTieDirection() {
69930
+ if (this.startBeat.isRest) {
69564
69931
  return BeamDirection.Up;
69565
69932
  }
69566
69933
  // invert direction (if stems go up, ties go down to not cross them)
69567
- switch (noteRenderer.getBeatDirection(beat)) {
69934
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startBeat)) {
69568
69935
  case BeamDirection.Up:
69569
69936
  return BeamDirection.Down;
69570
69937
  default:
69571
69938
  return BeamDirection.Up;
69572
69939
  }
69573
69940
  }
69574
- getStartY() {
69941
+ calculateStartX() {
69942
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69943
+ return startBeatRenderer.x + startBeatRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69944
+ }
69945
+ calculateStartY() {
69946
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69575
69947
  if (this.startBeat.isRest) {
69576
- // below all lines
69577
- return this.startNoteRenderer.getScoreY(9);
69948
+ switch (this.tieDirection) {
69949
+ case BeamDirection.Up:
69950
+ return (startBeatRenderer.y +
69951
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxTop());
69952
+ default:
69953
+ return (startBeatRenderer.y +
69954
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxBottom());
69955
+ }
69578
69956
  }
69579
69957
  switch (this.tieDirection) {
69580
69958
  case BeamDirection.Up:
69581
69959
  // below lowest note
69582
- return this.startNoteRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69960
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69583
69961
  default:
69584
- return this.startNoteRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69962
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69585
69963
  }
69586
69964
  }
69587
- getEndY() {
69588
- const endNoteScoreRenderer = this.endNoteRenderer;
69965
+ calculateEndX() {
69966
+ const endBeatRenderer = this.lookupEndBeatRenderer();
69967
+ if (!endBeatRenderer) {
69968
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
69969
+ }
69970
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69971
+ return (endBeatRenderer.x +
69972
+ endBeatRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69973
+ ? BeatXPosition.Stem
69974
+ : BeatXPosition.MiddleNotes));
69975
+ }
69976
+ caclculateEndY() {
69977
+ const endBeatRenderer = this.lookupEndBeatRenderer();
69978
+ if (!endBeatRenderer) {
69979
+ return this.calculateStartY();
69980
+ }
69589
69981
  if (this.endBeat.isRest) {
69590
69982
  switch (this.tieDirection) {
69591
69983
  case BeamDirection.Up:
69592
- return endNoteScoreRenderer.getScoreY(9);
69984
+ return (endBeatRenderer.y + endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxTop());
69593
69985
  default:
69594
- return endNoteScoreRenderer.getScoreY(0);
69986
+ return (endBeatRenderer.y +
69987
+ endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxBottom());
69595
69988
  }
69596
69989
  }
69597
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69598
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
69990
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startBeat);
69991
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69599
69992
  if (startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None) {
69600
69993
  if (endBeamDirection === this.tieDirection) {
69601
69994
  switch (this.tieDirection) {
69602
69995
  case BeamDirection.Up:
69603
69996
  // stem upper end
69604
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem);
69997
+ return (endBeatRenderer.y +
69998
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem));
69605
69999
  default:
69606
70000
  // stem lower end
69607
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem);
70001
+ return (endBeatRenderer.y +
70002
+ endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem));
69608
70003
  }
69609
70004
  }
69610
70005
  switch (this.tieDirection) {
69611
70006
  case BeamDirection.Up:
69612
70007
  // stem upper end
69613
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem);
70008
+ return (endBeatRenderer.y +
70009
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem));
69614
70010
  default:
69615
70011
  // stem lower end
69616
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem);
70012
+ return (endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem));
69617
70013
  }
69618
70014
  }
69619
70015
  switch (this.tieDirection) {
69620
70016
  case BeamDirection.Up:
69621
70017
  // below lowest note
69622
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
70018
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
69623
70019
  default:
69624
70020
  // above highest note
69625
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
70021
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
69626
70022
  }
69627
70023
  }
69628
- getStartX() {
69629
- return this.startNoteRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69630
- }
69631
- getEndX() {
69632
- const endBeamDirection = this.endNoteRenderer.getBeatDirection(this.endBeat);
69633
- return this.endNoteRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69634
- ? BeatXPosition.Stem
69635
- : BeatXPosition.MiddleNotes);
69636
- }
69637
70024
  }
69638
70025
 
69639
70026
  /**
@@ -69833,142 +70220,106 @@
69833
70220
  /**
69834
70221
  * @internal
69835
70222
  */
69836
- class ScoreSlurGlyph extends ScoreLegatoGlyph {
69837
- _startNote;
69838
- _endNote;
69839
- constructor(startNote, endNote, forEnd = false) {
69840
- super(startNote.beat, endNote.beat, forEnd);
69841
- this._startNote = startNote;
69842
- this._endNote = endNote;
70223
+ class ScoreTieGlyph extends NoteTieGlyph {
70224
+ shouldDrawBendSlur() {
70225
+ return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70226
+ !!this.startNote.bendOrigin &&
70227
+ this.startNote.isTieOrigin);
70228
+ }
70229
+ calculateStartX() {
70230
+ if (this.isLeftHandTap) {
70231
+ return this.calculateEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70232
+ }
70233
+ return this.renderer.x + this.renderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
69843
70234
  }
70235
+ calculateEndX() {
70236
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70237
+ if (!endNoteRenderer) {
70238
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70239
+ }
70240
+ if (this.isLeftHandTap) {
70241
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70242
+ }
70243
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70244
+ }
70245
+ }
70246
+
70247
+ /**
70248
+ * @internal
70249
+ */
70250
+ class ScoreSlurGlyph extends ScoreTieGlyph {
69844
70251
  getTieHeight(startX, _startY, endX, _endY) {
69845
- return Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
70252
+ return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
69846
70253
  }
69847
- getStartY() {
70254
+ calculateStartX() {
70255
+ return (this.renderer.x +
70256
+ (this._isStartCentered()
70257
+ ? this.renderer.getBeatX(this.startNote.beat, BeatXPosition.MiddleNotes)
70258
+ : this.renderer.getNoteX(this.startNote, NoteXPosition.Right)));
70259
+ }
70260
+ calculateStartY() {
69848
70261
  if (this._isStartCentered()) {
69849
70262
  switch (this.tieDirection) {
69850
70263
  case BeamDirection.Up:
69851
- // below lowest note
69852
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Top);
70264
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Top);
69853
70265
  default:
69854
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Bottom);
70266
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Bottom);
69855
70267
  }
69856
70268
  }
69857
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Center);
70269
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Center);
69858
70270
  }
69859
- getEndY() {
70271
+ calculateEndX() {
70272
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70273
+ if (!endNoteRenderer) {
70274
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70275
+ }
70276
+ if (this._isEndCentered()) {
70277
+ if (this._isEndOnStem()) {
70278
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.Stem);
70279
+ }
70280
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
70281
+ }
70282
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70283
+ }
70284
+ caclculateEndY() {
70285
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70286
+ if (!endNoteRenderer) {
70287
+ return this.calculateStartY();
70288
+ }
69860
70289
  if (this._isEndCentered()) {
69861
70290
  if (this._isEndOnStem()) {
69862
70291
  switch (this.tieDirection) {
69863
70292
  case BeamDirection.Up:
69864
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.TopWithStem);
70293
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.TopWithStem);
69865
70294
  default:
69866
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.BottomWithStem);
70295
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.BottomWithStem);
69867
70296
  }
69868
70297
  }
69869
70298
  switch (this.tieDirection) {
69870
70299
  case BeamDirection.Up:
69871
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Top);
70300
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
69872
70301
  default:
69873
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Bottom);
70302
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
69874
70303
  }
69875
70304
  }
69876
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Center);
70305
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
69877
70306
  }
69878
70307
  _isStartCentered() {
69879
- return ((this._startNote === this._startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
69880
- (this._startNote === this._startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70308
+ return ((this.startNote === this.startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70309
+ (this.startNote === this.startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
69881
70310
  }
69882
70311
  _isEndCentered() {
69883
- return (this._startNote.beat.graceType === GraceType.None &&
69884
- ((this._endNote === this._endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
69885
- (this._endNote === this._endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70312
+ return (this.startNote.beat.graceType === GraceType.None &&
70313
+ ((this.endNote === this.endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70314
+ (this.endNote === this.endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
69886
70315
  }
69887
70316
  _isEndOnStem() {
69888
- const endNoteScoreRenderer = this.endNoteRenderer;
69889
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69890
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
69891
- return startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None;
69892
- }
69893
- getStartX() {
69894
- return this._isStartCentered()
69895
- ? this.startNoteRenderer.getBeatX(this._startNote.beat, BeatXPosition.MiddleNotes)
69896
- : this.startNoteRenderer.getNoteX(this._startNote, NoteXPosition.Right);
69897
- }
69898
- getEndX() {
69899
- if (this._isEndCentered()) {
69900
- if (this._isEndOnStem()) {
69901
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.Stem);
69902
- }
69903
- return this.endNoteRenderer.getNoteX(this._endNote, NoteXPosition.Center);
69904
- }
69905
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.PreNotes);
69906
- }
69907
- }
69908
-
69909
- /**
69910
- * @internal
69911
- */
69912
- class ScoreTieGlyph extends TieGlyph {
69913
- startNote;
69914
- endNote;
69915
- constructor(startNote, endNote, forEnd = false) {
69916
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
69917
- this.startNote = startNote;
69918
- this.endNote = endNote;
69919
- }
69920
- shouldDrawBendSlur() {
69921
- return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
69922
- !!this.startNote.bendOrigin &&
69923
- this.startNote.isTieOrigin);
69924
- }
69925
- doLayout() {
69926
- super.doLayout();
69927
- }
69928
- getBeamDirection(beat, noteRenderer) {
69929
- // invert direction (if stems go up, ties go down to not cross them)
69930
- switch (noteRenderer.getBeatDirection(beat)) {
69931
- case BeamDirection.Up:
69932
- return BeamDirection.Down;
69933
- default:
69934
- return BeamDirection.Up;
69935
- }
69936
- }
69937
- getStartY() {
69938
- if (this.startBeat.isRest) {
69939
- // below all lines
69940
- return this.startNoteRenderer.getScoreY(9);
69941
- }
69942
- switch (this.tieDirection) {
69943
- case BeamDirection.Up:
69944
- // below lowest note
69945
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
69946
- default:
69947
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
69948
- }
69949
- }
69950
- getEndY() {
69951
- const endNoteScoreRenderer = this.endNoteRenderer;
69952
- if (this.endBeat.isRest) {
69953
- switch (this.tieDirection) {
69954
- case BeamDirection.Up:
69955
- return endNoteScoreRenderer.getScoreY(9);
69956
- default:
69957
- return endNoteScoreRenderer.getScoreY(0);
69958
- }
69959
- }
69960
- switch (this.tieDirection) {
69961
- case BeamDirection.Up:
69962
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Top);
69963
- default:
69964
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
69965
- }
69966
- }
69967
- getStartX() {
69968
- return this.startNoteRenderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
69969
- }
69970
- getEndX() {
69971
- return this.endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70317
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat);
70318
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70319
+ const endBeamDirection = endBeatRenderer
70320
+ ? endBeatRenderer.getBeatDirection(this.endNote.beat)
70321
+ : startBeamDirection;
70322
+ return startBeamDirection !== endBeamDirection && this.startNote.beat.graceType === GraceType.None;
69972
70323
  }
69973
70324
  }
69974
70325
 
@@ -70018,12 +70369,11 @@
70018
70369
  n.beat.graceType !== GraceType.BendGrace &&
70019
70370
  n.tieDestination &&
70020
70371
  n.tieDestination.isVisible) {
70021
- // tslint:disable-next-line: no-unnecessary-type-assertion
70022
- const tie = new ScoreTieGlyph(n, n.tieDestination, false);
70372
+ const tie = new ScoreTieGlyph(`score.tie.${n.id}`, n, n.tieDestination, false);
70023
70373
  this.addTie(tie);
70024
70374
  }
70025
70375
  if (n.isTieDestination && !n.tieOrigin.hasBend && !n.beat.hasWhammyBar) {
70026
- const tie = new ScoreTieGlyph(n.tieOrigin, n, true);
70376
+ const tie = new ScoreTieGlyph(`score.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
70027
70377
  this.addTie(tie);
70028
70378
  }
70029
70379
  // TODO: depending on the type we have other positioning
@@ -70033,17 +70383,16 @@
70033
70383
  this.addTie(l);
70034
70384
  }
70035
70385
  if (n.isSlurOrigin && n.slurDestination && n.slurDestination.isVisible) {
70036
- // tslint:disable-next-line: no-unnecessary-type-assertion
70037
- const tie = new ScoreSlurGlyph(n, n.slurDestination, false);
70386
+ const tie = new ScoreSlurGlyph(`score.slur.${n.id}`, n, n.slurDestination, false);
70038
70387
  this.addTie(tie);
70039
70388
  }
70040
70389
  if (n.isSlurDestination) {
70041
- const tie = new ScoreSlurGlyph(n.slurOrigin, n, true);
70390
+ const tie = new ScoreSlurGlyph(`score.slur.${n.slurOrigin.id}`, n.slurOrigin, n, true);
70042
70391
  this.addTie(tie);
70043
70392
  }
70044
70393
  // start effect slur on first beat
70045
70394
  if (!this._effectSlur && n.isEffectSlurOrigin && n.effectSlurDestination) {
70046
- const effectSlur = new ScoreSlurGlyph(n, n.effectSlurDestination, false);
70395
+ const effectSlur = new ScoreSlurGlyph(`score.slur.effect.${n.beat.id}`, n, n.effectSlurDestination, false);
70047
70396
  this._effectSlur = effectSlur;
70048
70397
  this.addTie(effectSlur);
70049
70398
  }
@@ -70052,7 +70401,7 @@
70052
70401
  const direction = this.onNotes.beamingHelper.direction;
70053
70402
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70054
70403
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70055
- const effectEndSlur = new ScoreSlurGlyph(startNote, endNote, true);
70404
+ const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
70056
70405
  this._effectEndSlur = effectEndSlur;
70057
70406
  this.addTie(effectEndSlur);
70058
70407
  }
@@ -70074,7 +70423,7 @@
70074
70423
  while (destination.nextBeat && destination.nextBeat.isLegatoDestination) {
70075
70424
  destination = destination.nextBeat;
70076
70425
  }
70077
- this.addTie(new ScoreLegatoGlyph(this.beat, destination, false));
70426
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${this.beat.id}`, this.beat, destination, false));
70078
70427
  }
70079
70428
  }
70080
70429
  else if (this.beat.isLegatoDestination) {
@@ -70084,7 +70433,7 @@
70084
70433
  while (origin.previousBeat && origin.previousBeat.isLegatoOrigin) {
70085
70434
  origin = origin.previousBeat;
70086
70435
  }
70087
- this.addTie(new ScoreLegatoGlyph(origin, this.beat, true));
70436
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${origin.id}`, origin, this.beat, true));
70088
70437
  }
70089
70438
  }
70090
70439
  }
@@ -70464,6 +70813,9 @@
70464
70813
  this.addBeatGlyph(container);
70465
70814
  }
70466
70815
  }
70816
+ getNoteLine(note) {
70817
+ return this.accidentalHelper.getNoteSteps(note) / 2;
70818
+ }
70467
70819
  getNoteSteps(n) {
70468
70820
  return this.accidentalHelper.getNoteSteps(n);
70469
70821
  }
@@ -70520,43 +70872,15 @@
70520
70872
  /**
70521
70873
  * @internal
70522
70874
  */
70523
- class SlashTieGlyph extends TieGlyph {
70524
- startNote;
70525
- endNote;
70526
- constructor(startNote, endNote, forEnd = false) {
70527
- super(startNote.beat, endNote.beat, forEnd);
70528
- this.startNote = startNote;
70529
- this.endNote = endNote;
70530
- }
70531
- get _isLeftHandTap() {
70532
- return this.startNote === this.endNote;
70533
- }
70534
- getTieHeight(startX, startY, endX, endY) {
70535
- if (this._isLeftHandTap) {
70536
- return this.startNoteRenderer.smuflMetrics.tieHeight;
70537
- }
70538
- return super.getTieHeight(startX, startY, endX, endY);
70539
- }
70540
- getBeamDirection(_beat, _noteRenderer) {
70541
- return BeamDirection.Down;
70542
- }
70543
- static getBeamDirectionForNote(_note) {
70875
+ class SlashTieGlyph extends NoteTieGlyph {
70876
+ calculateTieDirection() {
70544
70877
  return BeamDirection.Down;
70545
70878
  }
70546
- getStartY() {
70547
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
70879
+ getStartNotePosition() {
70880
+ return NoteXPosition.Right;
70548
70881
  }
70549
- getEndY() {
70550
- return this.getStartY();
70551
- }
70552
- getStartX() {
70553
- if (this._isLeftHandTap) {
70554
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70555
- }
70556
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Right);
70557
- }
70558
- getEndX() {
70559
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70882
+ getEndNotePosition() {
70883
+ return NoteXPosition.Left;
70560
70884
  }
70561
70885
  }
70562
70886
 
@@ -70571,12 +70895,12 @@
70571
70895
  return;
70572
70896
  }
70573
70897
  if (!this._tiedNoteTie && n.isTieOrigin && n.tieDestination.isVisible) {
70574
- const tie = new SlashTieGlyph(n, n.tieDestination, false);
70898
+ const tie = new SlashTieGlyph('slash.tie', n, n.tieDestination, false);
70575
70899
  this._tiedNoteTie = tie;
70576
70900
  this.addTie(tie);
70577
70901
  }
70578
70902
  if (!this._tiedNoteTie && n.isTieDestination) {
70579
- const tie = new SlashTieGlyph(n.tieOrigin, n, true);
70903
+ const tie = new SlashTieGlyph('slash.tie', n.tieOrigin, n, true);
70580
70904
  this._tiedNoteTie = tie;
70581
70905
  this.addTie(tie);
70582
70906
  }
@@ -70703,8 +71027,7 @@
70703
71027
  doLayout() {
70704
71028
  // create glyphs
70705
71029
  const sr = this.renderer;
70706
- const line = sr.getNoteLine();
70707
- const glyphY = sr.getLineY(line);
71030
+ const glyphY = sr.getLineY(0);
70708
71031
  if (this.container.beat.deadSlapped) {
70709
71032
  const deadSlapped = new DeadSlappedBeatGlyph();
70710
71033
  deadSlapped.renderer = this.renderer;
@@ -70737,7 +71060,7 @@
70737
71060
  //
70738
71061
  if (this.container.beat.dots > 0) {
70739
71062
  for (let i = 0; i < this.container.beat.dots; i++) {
70740
- this.addEffect(new AugmentationDotGlyph(0, sr.getLineY(sr.getNoteLine()) - sr.getLineHeight(0.5)));
71063
+ this.addEffect(new AugmentationDotGlyph(0, glyphY - sr.getLineHeight(0.5)));
70741
71064
  }
70742
71065
  }
70743
71066
  super.doLayout();
@@ -70819,7 +71142,7 @@
70819
71142
  this.registerOverflowTop(this.tupletSize);
70820
71143
  }
70821
71144
  }
70822
- getNoteLine() {
71145
+ getNoteLine(_note) {
70823
71146
  return 0;
70824
71147
  }
70825
71148
  getFlagTopY(beat, _direction) {
@@ -71156,77 +71479,6 @@
71156
71479
  }
71157
71480
  }
71158
71481
 
71159
- /**
71160
- * @internal
71161
- */
71162
- class TabSlurGlyph extends TabTieGlyph {
71163
- _direction;
71164
- _forSlide;
71165
- constructor(startNote, endNote, forSlide, forEnd = false) {
71166
- super(startNote, endNote, forEnd);
71167
- this._direction = TabTieGlyph.getBeamDirectionForNote(startNote);
71168
- this._forSlide = forSlide;
71169
- }
71170
- getTieHeight(startX, _startY, endX, _endY) {
71171
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
71172
- }
71173
- tryExpand(startNote, endNote, forSlide, forEnd) {
71174
- // same type required
71175
- if (this._forSlide !== forSlide) {
71176
- return false;
71177
- }
71178
- if (this.forEnd !== forEnd) {
71179
- return false;
71180
- }
71181
- // same start and endbeat
71182
- if (this.startNote.beat.id !== startNote.beat.id) {
71183
- return false;
71184
- }
71185
- if (this.endNote.beat.id !== endNote.beat.id) {
71186
- return false;
71187
- }
71188
- // same draw direction
71189
- if (this._direction !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
71190
- return false;
71191
- }
71192
- // if we can expand, expand in correct direction
71193
- switch (this._direction) {
71194
- case BeamDirection.Up:
71195
- if (startNote.realValue > this.startNote.realValue) {
71196
- this.startNote = startNote;
71197
- this.startBeat = startNote.beat;
71198
- }
71199
- if (endNote.realValue > this.endNote.realValue) {
71200
- this.endNote = endNote;
71201
- this.endBeat = endNote.beat;
71202
- }
71203
- break;
71204
- case BeamDirection.Down:
71205
- if (startNote.realValue < this.startNote.realValue) {
71206
- this.startNote = startNote;
71207
- this.startBeat = startNote.beat;
71208
- }
71209
- if (endNote.realValue < this.endNote.realValue) {
71210
- this.endNote = endNote;
71211
- this.endBeat = endNote.beat;
71212
- }
71213
- break;
71214
- }
71215
- return true;
71216
- }
71217
- paint(cx, cy, canvas) {
71218
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
71219
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
71220
- const slurId = `tab.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
71221
- const renderer = this.renderer;
71222
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
71223
- if (!isSlurRendered) {
71224
- renderer.staff.setSharedLayoutData(slurId, true);
71225
- super.paint(cx, cy, canvas);
71226
- }
71227
- }
71228
- }
71229
-
71230
71482
  /**
71231
71483
  * @internal
71232
71484
  */
@@ -71251,15 +71503,15 @@
71251
71503
  }
71252
71504
  const renderer = this.renderer;
71253
71505
  if (n.isTieOrigin && renderer.showTiedNotes && n.tieDestination.isVisible) {
71254
- const tie = new TabTieGlyph(n, n.tieDestination, false);
71506
+ const tie = new TabTieGlyph(`tab.tie.${n.id}`, n, n.tieDestination, false);
71255
71507
  this.addTie(tie);
71256
71508
  }
71257
71509
  if (n.isTieDestination && renderer.showTiedNotes) {
71258
- const tie = new TabTieGlyph(n.tieOrigin, n, true);
71510
+ const tie = new TabTieGlyph(`tab.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
71259
71511
  this.addTie(tie);
71260
71512
  }
71261
71513
  if (n.isLeftHandTapped && !n.isHammerPullDestination) {
71262
- const tapSlur = new TabTieGlyph(n, n, false);
71514
+ const tapSlur = new TabTieGlyph(`tab.tie.leftHandTap.${n.id}`, n, n, false);
71263
71515
  this.addTie(tapSlur);
71264
71516
  }
71265
71517
  // start effect slur on first beat
@@ -71272,7 +71524,7 @@
71272
71524
  }
71273
71525
  }
71274
71526
  if (!expanded) {
71275
- const effectSlur = new TabSlurGlyph(n, n.effectSlurDestination, false, false);
71527
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.id}`, n, n.effectSlurDestination, false, false);
71276
71528
  this._effectSlurs.push(effectSlur);
71277
71529
  this.addTie(effectSlur);
71278
71530
  }
@@ -71287,7 +71539,7 @@
71287
71539
  }
71288
71540
  }
71289
71541
  if (!expanded) {
71290
- const effectSlur = new TabSlurGlyph(n.effectSlurOrigin, n, false, true);
71542
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.effectSlurOrigin.id}`, n.effectSlurOrigin, n, false, true);
71291
71543
  this._effectSlurs.push(effectSlur);
71292
71544
  this.addTie(effectSlur);
71293
71545
  }
@@ -71706,7 +71958,7 @@
71706
71958
  }
71707
71959
  else {
71708
71960
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
71709
- const y = tabRenderer.getTabY(line);
71961
+ const y = tabRenderer.getLineY(line);
71710
71962
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71711
71963
  this.restGlyph = restGlyph;
71712
71964
  restGlyph.beat = this.container.beat;
@@ -71765,8 +72017,8 @@
71765
72017
  _createNoteGlyph(n) {
71766
72018
  const tr = this.renderer;
71767
72019
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
71768
- const l = n.beat.voice.bar.staff.tuning.length - n.string;
71769
- noteNumberGlyph.y = tr.getTabY(l);
72020
+ const l = tr.getNoteLine(n);
72021
+ noteNumberGlyph.y = tr.getLineY(l);
71770
72022
  noteNumberGlyph.renderer = this.renderer;
71771
72023
  noteNumberGlyph.doLayout();
71772
72024
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
@@ -71957,17 +72209,8 @@
71957
72209
  }
71958
72210
  return mode;
71959
72211
  }
71960
- /**
71961
- * Gets the relative y position of the given steps relative to first line.
71962
- * @param line the line of the particular string where 0 is the most top line
71963
- * @param correction
71964
- * @returns
71965
- */
71966
- getTabY(line) {
71967
- return super.getLineY(line);
71968
- }
71969
- getTabHeight(line) {
71970
- return super.getLineHeight(line);
72212
+ getNoteLine(note) {
72213
+ return this.bar.staff.tuning.length - note.string;
71971
72214
  }
71972
72215
  minString = Number.NaN;
71973
72216
  maxString = Number.NaN;
@@ -72056,7 +72299,7 @@
72056
72299
  if (this.isFirstOfLine) {
72057
72300
  const center = (this.bar.staff.tuning.length - 1) / 2;
72058
72301
  this.createStartSpacing();
72059
- this.addPreBeatGlyph(new TabClefGlyph(0, this.getTabY(center)));
72302
+ this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
72060
72303
  }
72061
72304
  // Time Signature
72062
72305
  if (this.showTimeSignature &&
@@ -72077,7 +72320,7 @@
72077
72320
  _createTimeSignatureGlyphs() {
72078
72321
  this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
72079
72322
  const lines = (this.bar.staff.tuning.length + 1) / 2 - 1;
72080
- this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getTabY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72323
+ this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getLineY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72081
72324
  }
72082
72325
  createVoiceGlyphs(v) {
72083
72326
  super.createVoiceGlyphs(v);