@coderline/alphatab 1.8.0-alpha.1637 → 1.8.0-alpha.1640

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * alphaTab v1.8.0-alpha.1637 (develop, build 1637)
2
+ * alphaTab v1.8.0-alpha.1640 (develop, build 1640)
3
3
  *
4
4
  * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.8.0-alpha.1637';
207
- static date = '2025-12-07T02:26:27.382Z';
208
- static commit = 'aa2c8101456d3ddfc777491468bc483789cc6c12';
206
+ static version = '1.8.0-alpha.1640';
207
+ static date = '2025-12-10T02:19:08.776Z';
208
+ static commit = '343f59ee6b39b4f3a41636b3c33db34bccf631f9';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -48527,7 +48527,7 @@ class BeatContainerGlyph extends Glyph {
48527
48527
  onNotes;
48528
48528
  minWidth = 0;
48529
48529
  get onTimeX() {
48530
- return this.onNotes.x + this.onNotes.centerX;
48530
+ return this.onNotes.x + this.onNotes.onTimeX;
48531
48531
  }
48532
48532
  constructor(beat, voiceContainer) {
48533
48533
  super(0, 0);
@@ -48551,8 +48551,8 @@ class BeatContainerGlyph extends Glyph {
48551
48551
  return helper.hasFlag(false, undefined);
48552
48552
  }
48553
48553
  registerLayoutingInfo(layoutings) {
48554
- const preBeatStretch = this.preNotes.computedWidth + this.onNotes.centerX;
48555
- let postBeatStretch = this.onNotes.computedWidth - this.onNotes.centerX;
48554
+ const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
48555
+ let postBeatStretch = this.onNotes.computedWidth - this.onNotes.onTimeX;
48556
48556
  // make space for flag
48557
48557
  const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48558
48558
  if (this.beat.graceType !== GraceType.None) {
@@ -48695,7 +48695,7 @@ class BeatContainerGlyph extends Glyph {
48695
48695
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48696
48696
  beatBoundings.realBounds.w = this.width;
48697
48697
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48698
- beatBoundings.onNotesX = cx + this.x + this.onNotes.centerX;
48698
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48699
48699
  }
48700
48700
  else {
48701
48701
  beatBoundings.visualBounds = new Bounds();
@@ -48733,7 +48733,7 @@ class BeatContainerGlyph extends Glyph {
48733
48733
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48734
48734
  beatBoundings.realBounds.w = this.width;
48735
48735
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48736
- beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.centerX;
48736
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48737
48737
  }
48738
48738
  barBounds.addBeat(beatBoundings);
48739
48739
  if (this.renderer.settings.core.includeNoteBounds) {
@@ -54358,6 +54358,7 @@ class Cursors {
54358
54358
  class ScalableHtmlElementContainer extends HtmlElementContainer {
54359
54359
  _xscale;
54360
54360
  _yscale;
54361
+ centerAtPosition = false;
54361
54362
  constructor(element, xscale, yscale) {
54362
54363
  super(element);
54363
54364
  this._xscale = xscale;
@@ -54399,7 +54400,11 @@ class ScalableHtmlElementContainer extends HtmlElementContainer {
54399
54400
  else {
54400
54401
  h = h / this._yscale;
54401
54402
  }
54402
- this.element.style.transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54403
+ let transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54404
+ if (this.centerAtPosition) {
54405
+ transform += ` translateX(-50%)`;
54406
+ }
54407
+ this.element.style.transform = transform;
54403
54408
  this.element.style.transformOrigin = 'top left';
54404
54409
  this.lastBounds.x = x;
54405
54410
  this.lastBounds.y = y;
@@ -55260,6 +55265,7 @@ class BrowserUiFacade {
55260
55265
  beatCursor.style.willChange = 'transform';
55261
55266
  beatCursorContainer.width = 3;
55262
55267
  beatCursorContainer.height = 1;
55268
+ beatCursorContainer.centerAtPosition = true;
55263
55269
  beatCursorContainer.setBounds(0, 0, 1, 1);
55264
55270
  // add cursors to UI
55265
55271
  element.insertBefore(cursorWrapper, element.firstChild);
@@ -56634,7 +56640,7 @@ var BeatXPosition;
56634
56640
  */
56635
56641
  BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
56636
56642
  /**
56637
- * Gets the middle-notes position which is located after in the middle the note heads.
56643
+ * Gets the middle-notes position which is located after in the exact center of the note heads.
56638
56644
  */
56639
56645
  BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
56640
56646
  /**
@@ -56664,12 +56670,12 @@ class GroupedEffectGlyph extends EffectGlyph {
56664
56670
  this.endPosition = endPosition;
56665
56671
  }
56666
56672
  get isLinkedWithPrevious() {
56667
- 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;
56668
56674
  }
56669
56675
  get isLinkedWithNext() {
56670
56676
  return (!!this.nextGlyph &&
56671
56677
  this.nextGlyph.renderer.isFinalized &&
56672
- this.nextGlyph.renderer.staff.system === this.renderer.staff.system);
56678
+ this.nextGlyph.renderer.staff?.system === this.renderer.staff.system);
56673
56679
  }
56674
56680
  paint(cx, cy, canvas) {
56675
56681
  // if we are linked with the previous, the first glyph of the group will also render this one.
@@ -59057,6 +59063,437 @@ class LeftToRightLayoutingGlyphGroup extends GlyphGroup {
59057
59063
  }
59058
59064
  }
59059
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
+
59060
59497
  /**
59061
59498
  * This glyph acts as container for handling
59062
59499
  * multiple voice rendering
@@ -59527,6 +59964,63 @@ class TuningGlyph extends GlyphGroup {
59527
59964
  }
59528
59965
  }
59529
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
+
59530
60024
  /**
59531
60025
  * A Staff represents a single line within a StaffSystem.
59532
60026
  * It stores BarRenderer instances created from a given factory.
@@ -59648,7 +60142,6 @@ class RenderStaff {
59648
60142
  this._sharedLayoutData = new Map();
59649
60143
  const lastBar = this.barRenderers[this.barRenderers.length - 1];
59650
60144
  this.barRenderers.splice(this.barRenderers.length - 1, 1);
59651
- this.system.layout.unregisterBarRenderer(this.staffId, lastBar);
59652
60145
  this.topOverflow = 0;
59653
60146
  this.bottomOverflow = 0;
59654
60147
  for (const r of this.barRenderers) {
@@ -59741,23 +60234,23 @@ class RenderStaff {
59741
60234
  // changes in the overflows
59742
60235
  let needsSecondPass = false;
59743
60236
  let topOverflow = this.topOverflow;
59744
- for (let i = 0; i < this.barRenderers.length; i++) {
59745
- this.barRenderers[i].y = this.topPadding + topOverflow;
59746
- 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()) {
59747
60240
  needsSecondPass = true;
59748
60241
  }
59749
- this.height = Math.max(this.height, this.barRenderers[i].height);
60242
+ this.height = Math.max(this.height, renderer.height);
59750
60243
  }
59751
60244
  // 2nd pass: move renderers to correct position respecting the new overflows
59752
60245
  if (needsSecondPass) {
59753
60246
  topOverflow = this.topOverflow;
59754
60247
  // shift all the renderers to the new position to match required spacing
59755
- for (let i = 0; i < this.barRenderers.length; i++) {
59756
- this.barRenderers[i].y = this.topPadding + topOverflow;
60248
+ for (const renderer of this.barRenderers) {
60249
+ renderer.y = this.topPadding + topOverflow;
59757
60250
  }
59758
60251
  // finalize again (to align ties)
59759
- for (let i = 0; i < this.barRenderers.length; i++) {
59760
- this.barRenderers[i].finalizeRenderer();
60252
+ for (const renderer of this.barRenderers) {
60253
+ renderer.finalizeRenderer();
59761
60254
  }
59762
60255
  }
59763
60256
  if (this.height > 0) {
@@ -60363,6 +60856,7 @@ class StaffSystem {
60363
60856
  if (newBarDisplayScale > barDisplayScale) {
60364
60857
  barDisplayScale = newBarDisplayScale;
60365
60858
  }
60859
+ lastBar.afterReverted();
60366
60860
  }
60367
60861
  this.width -= width;
60368
60862
  this.computedWidth -= width;
@@ -60882,12 +61376,16 @@ class ScoreLayout {
60882
61376
  constructor(renderer) {
60883
61377
  this.renderer = renderer;
60884
61378
  }
61379
+ slurRegistry = new SlurRegistry();
60885
61380
  resize() {
60886
61381
  this._lazyPartials.clear();
61382
+ this.slurRegistry.clear();
60887
61383
  this.doResize();
60888
61384
  }
60889
61385
  layoutAndRender() {
60890
61386
  this._lazyPartials.clear();
61387
+ this.slurRegistry.clear();
61388
+ this._barRendererLookup.clear();
60891
61389
  this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
60892
61390
  const score = this.renderer.score;
60893
61391
  this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
@@ -61156,17 +61654,6 @@ class ScoreLayout {
61156
61654
  }
61157
61655
  }
61158
61656
  }
61159
- unregisterBarRenderer(key, renderer) {
61160
- if (this._barRendererLookup.has(key)) {
61161
- const lookup = this._barRendererLookup.get(key);
61162
- lookup.delete(renderer.bar.id);
61163
- if (renderer.additionalMultiRestBars) {
61164
- for (const b of renderer.additionalMultiRestBars) {
61165
- lookup.delete(b.id);
61166
- }
61167
- }
61168
- }
61169
- }
61170
61657
  getRendererForBar(key, bar) {
61171
61658
  const barRendererId = bar.id;
61172
61659
  if (this._barRendererLookup.has(key) && this._barRendererLookup.get(key).has(barRendererId)) {
@@ -61332,7 +61819,8 @@ class BeatGlyphBase extends GlyphGroup {
61332
61819
  */
61333
61820
  class BeatOnNoteGlyphBase extends BeatGlyphBase {
61334
61821
  beamingHelper;
61335
- centerX = 0;
61822
+ onTimeX = 0;
61823
+ middleX = 0;
61336
61824
  updateBeamingHelper() {
61337
61825
  }
61338
61826
  buildBoundingsLookup(_beatBounds, _cx, _cy) {
@@ -62155,6 +62643,7 @@ class BarRendererBase {
62155
62643
  _voiceContainers = new Map();
62156
62644
  _postBeatGlyphs = new LeftToRightLayoutingGlyphGroup();
62157
62645
  _ties = [];
62646
+ _multiSystemSlurs;
62158
62647
  topEffects;
62159
62648
  bottomEffects;
62160
62649
  get nextRenderer() {
@@ -62307,6 +62796,11 @@ class BarRendererBase {
62307
62796
  }
62308
62797
  }
62309
62798
  _appliedLayoutingInfo = 0;
62799
+ afterReverted() {
62800
+ this.staff = undefined;
62801
+ this.registerMultiSystemSlurs(undefined);
62802
+ this.isFinalized = false;
62803
+ }
62310
62804
  afterStaffBarReverted() {
62311
62805
  this.topEffects.afterStaffBarReverted();
62312
62806
  this.bottomEffects.afterStaffBarReverted();
@@ -62352,13 +62846,26 @@ class BarRendererBase {
62352
62846
  return true;
62353
62847
  }
62354
62848
  isFinalized = false;
62355
- finalizeRenderer() {
62356
- this.isFinalized = true;
62849
+ registerMultiSystemSlurs(startedTies) {
62850
+ if (!startedTies) {
62851
+ this._multiSystemSlurs = undefined;
62852
+ return;
62853
+ }
62854
+ let ties = undefined;
62855
+ for (const g of startedTies) {
62856
+ const continuation = new ContinuationTieGlyph(g);
62857
+ continuation.renderer = this;
62858
+ continuation.tieDirection = g.tieDirection;
62859
+ if (!ties) {
62860
+ ties = [];
62861
+ }
62862
+ ties.push(continuation);
62863
+ }
62864
+ this._multiSystemSlurs = ties;
62865
+ }
62866
+ _finalizeTies(ties, barTop, barBottom) {
62357
62867
  let didChangeOverflows = false;
62358
- // allow spacing to be used for tie overflows
62359
- const barTop = this.y;
62360
- const barBottom = this.y + this.height;
62361
- for (const t of this._ties) {
62868
+ for (const t of ties) {
62362
62869
  const tie = t;
62363
62870
  tie.doLayout();
62364
62871
  if (t.checkForOverflow) {
@@ -62379,6 +62886,21 @@ class BarRendererBase {
62379
62886
  }
62380
62887
  }
62381
62888
  }
62889
+ return didChangeOverflows;
62890
+ }
62891
+ finalizeRenderer() {
62892
+ this.isFinalized = true;
62893
+ let didChangeOverflows = false;
62894
+ // allow spacing to be used for tie overflows
62895
+ const barTop = this.y;
62896
+ const barBottom = this.y + this.height;
62897
+ if (this._finalizeTies(this._ties, barTop, barBottom)) {
62898
+ didChangeOverflows = true;
62899
+ }
62900
+ const multiSystemSlurs = this._multiSystemSlurs;
62901
+ if (multiSystemSlurs && this._finalizeTies(multiSystemSlurs, barTop, barBottom)) {
62902
+ didChangeOverflows = true;
62903
+ }
62382
62904
  const topHeightChanged = this.topEffects.finalizeEffects();
62383
62905
  const bottomHeightChanged = this.bottomEffects.finalizeEffects();
62384
62906
  if (topHeightChanged || bottomHeightChanged) {
@@ -62559,6 +63081,16 @@ class BarRendererBase {
62559
63081
  }
62560
63082
  canvas.color = this.resources.mainGlyphColor;
62561
63083
  this._postBeatGlyphs.paint(cx + this.x, cy + this.y, canvas);
63084
+ this._paintMultiSystemSlurs(cx, cy, canvas);
63085
+ }
63086
+ _paintMultiSystemSlurs(cx, cy, canvas) {
63087
+ const multiSystemSlurs = this._multiSystemSlurs;
63088
+ if (!multiSystemSlurs) {
63089
+ return;
63090
+ }
63091
+ for (const slur of multiSystemSlurs) {
63092
+ slur.paint(cx, cy, canvas);
63093
+ }
62562
63094
  }
62563
63095
  paintBackground(cx, cy, canvas) {
62564
63096
  this.layoutingInfo.paint(cx + this.x + this._preBeatGlyphs.x + this._preBeatGlyphs.width, cy + this.y + this.height, canvas);
@@ -62624,7 +63156,7 @@ class BarRendererBase {
62624
63156
  case BeatXPosition.OnNotes:
62625
63157
  return container.voiceContainer.x + container.x + container.onNotes.x;
62626
63158
  case BeatXPosition.MiddleNotes:
62627
- return container.voiceContainer.x + container.x + container.onTimeX;
63159
+ return container.voiceContainer.x + container.x + container.onNotes.x + container.onNotes.middleX;
62628
63160
  case BeatXPosition.Stem:
62629
63161
  const offset = container.onNotes.beamingHelper
62630
63162
  ? container.onNotes.beamingHelper.getBeatLineX(beat)
@@ -64637,6 +65169,13 @@ class PageViewLayout extends ScoreLayout {
64637
65169
  }
64638
65170
  }
64639
65171
  else {
65172
+ // clear out staves during re-layout, this info is outdated during
65173
+ // re-layout of the bars
65174
+ for (const r of this._allMasterBarRenderers) {
65175
+ for (const b of r.renderers) {
65176
+ b.afterReverted();
65177
+ }
65178
+ }
64640
65179
  this._systems = [];
64641
65180
  let currentIndex = 0;
64642
65181
  const maxWidth = this._maxWidth;
@@ -65098,7 +65637,7 @@ class BarLineGlyph extends LeftToRightLayoutingGlyphGroup {
65098
65637
  // as during layout things are still moving
65099
65638
  let actualLineHeight = this.height;
65100
65639
  const thisStaff = renderer.staff;
65101
- const allStaves = renderer.staff.system.allStaves;
65640
+ const allStaves = thisStaff.system.allStaves;
65102
65641
  let isExtended = false;
65103
65642
  if (this._extendToNextStaff && thisStaff.index < allStaves.length - 1) {
65104
65643
  const nextStaff = allStaves[thisStaff.index + 1];
@@ -65280,7 +65819,7 @@ class LineBarRenderer extends BarRendererBase {
65280
65819
  }
65281
65820
  // during system fitting it can happen that we have fraction widths
65282
65821
  // but to have lines until the full end-pixel we round up.
65283
- // this way we avoid holes,
65822
+ // this way we avoid holes,
65284
65823
  const lineWidth = this.width;
65285
65824
  // we want the lines to be exactly virtually aligned with the respective Y-position
65286
65825
  // for note heads to align correctly
@@ -66046,375 +66585,23 @@ class LineBarRenderer extends BarRendererBase {
66046
66585
  /**
66047
66586
  * @internal
66048
66587
  */
66049
- class TieGlyph extends Glyph {
66050
- startBeat;
66051
- endBeat;
66052
- yOffset = 0;
66053
- forEnd;
66054
- startNoteRenderer = null;
66055
- endNoteRenderer = null;
66056
- tieDirection = BeamDirection.Up;
66057
- constructor(startBeat, endBeat, forEnd) {
66058
- super(0, 0);
66059
- this.startBeat = startBeat;
66060
- this.endBeat = endBeat;
66061
- this.forEnd = forEnd;
66062
- }
66063
- _startX = 0;
66064
- _startY = 0;
66065
- _endX = 0;
66066
- _endY = 0;
66067
- _tieHeight = 0;
66068
- _shouldDraw = false;
66069
- _boundingBox;
66070
- get checkForOverflow() {
66071
- return this._boundingBox !== undefined;
66072
- }
66073
- getBoundingBoxTop() {
66074
- if (this._boundingBox) {
66075
- return this._boundingBox.y;
66076
- }
66077
- return this._startY;
66078
- }
66079
- getBoundingBoxBottom() {
66080
- if (this._boundingBox) {
66081
- return this._boundingBox.y + this._boundingBox.h;
66082
- }
66083
- return this._startY;
66084
- }
66085
- doLayout() {
66086
- this.width = 0;
66087
- // TODO fix nullability of start/end beat,
66088
- if (!this.endBeat) {
66089
- this._shouldDraw = false;
66090
- return;
66091
- }
66092
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66093
- this.startNoteRenderer = startNoteRenderer;
66094
- const endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
66095
- this.endNoteRenderer = endNoteRenderer;
66096
- this._startX = 0;
66097
- this._endX = 0;
66098
- this._startY = 0;
66099
- this._endY = 0;
66100
- this.height = 0;
66101
- this._shouldDraw = false;
66102
- // if we are on the tie start, we check if we
66103
- // either can draw till the end note, or we just can draw till the bar end
66104
- this.tieDirection = !startNoteRenderer
66105
- ? this.getBeamDirection(this.endBeat, endNoteRenderer)
66106
- : this.getBeamDirection(this.startBeat, startNoteRenderer);
66107
- if (!this.forEnd && startNoteRenderer) {
66108
- // line break or bar break
66109
- if (startNoteRenderer !== endNoteRenderer) {
66110
- this._startX = startNoteRenderer.x + this.getStartX();
66111
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
66112
- // line break: to bar end
66113
- if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
66114
- this._endX = startNoteRenderer.x + startNoteRenderer.width;
66115
- this._endY = this._startY;
66116
- }
66117
- else {
66118
- this._endX = endNoteRenderer.x + this.getEndX();
66119
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66120
- }
66121
- }
66122
- else {
66123
- this._startX = startNoteRenderer.x + this.getStartX();
66124
- this._endX = endNoteRenderer.x + this.getEndX();
66125
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
66126
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66127
- }
66128
- this._shouldDraw = true;
66129
- }
66130
- else if (!startNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
66131
- this._startX = endNoteRenderer.x;
66132
- this._endX = endNoteRenderer.x + this.getEndX();
66133
- this._startY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66134
- this._endY = this._startY;
66135
- this._shouldDraw = true;
66136
- }
66137
- this._boundingBox = undefined;
66138
- if (this._shouldDraw) {
66139
- this.y = Math.min(this._startY, this._endY);
66140
- if (this.shouldDrawBendSlur()) {
66141
- this._tieHeight = 0; // TODO: Bend slur height to be considered?
66142
- }
66143
- else {
66144
- this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
66145
- const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
66146
- this._boundingBox = tieBoundingBox;
66147
- this.height = tieBoundingBox.h;
66148
- if (this.tieDirection === BeamDirection.Up) {
66149
- // the tie might go above `this.y` due to its shape
66150
- // here we calculate how much this is so we can consider the
66151
- // respective overflow
66152
- const overlap = this.y - tieBoundingBox.y;
66153
- if (overlap > 0) {
66154
- this.y -= overlap;
66155
- }
66156
- }
66157
- }
66158
- }
66159
- }
66160
- paint(cx, cy, canvas) {
66161
- if (this._shouldDraw) {
66162
- if (this.shouldDrawBendSlur()) {
66163
- TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
66164
- }
66165
- else {
66166
- 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);
66167
- }
66168
- }
66169
- }
66170
- shouldDrawBendSlur() {
66171
- return false;
66172
- }
66173
- getTieHeight(_startX, _startY, _endX, _endY) {
66174
- return this.renderer.smuflMetrics.tieHeight;
66175
- }
66176
- getBeamDirection(_beat, _noteRenderer) {
66177
- return BeamDirection.Down;
66178
- }
66179
- getStartY() {
66180
- return 0;
66181
- }
66182
- getEndY() {
66183
- return 0;
66184
- }
66185
- getStartX() {
66186
- return 0;
66187
- }
66188
- getEndX() {
66189
- return 0;
66190
- }
66191
- static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
66192
- const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66193
- // For a musical tie/slur, the extrema occur predictably near the midpoint
66194
- // Evaluate at midpoint (t=0.5) and check endpoints
66195
- const p0x = cp[0];
66196
- const p0y = cp[1];
66197
- const c1x = cp[2];
66198
- const c1y = cp[3];
66199
- const c2x = cp[4];
66200
- const c2y = cp[5];
66201
- const p1x = cp[6];
66202
- const p1y = cp[7];
66203
- // Evaluate at t=0.5 for midpoint
66204
- const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
66205
- const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
66206
- // Bounds are simply min/max of start, end, and midpoint
66207
- const xMin = Math.min(p0x, p1x, midX);
66208
- const xMax = Math.max(p0x, p1x, midX);
66209
- let yMin = Math.min(p0y, p1y, midY);
66210
- let yMax = Math.max(p0y, p1y, midY);
66211
- // Account for thickness of the tie/slur
66212
- if (down) {
66213
- yMax += size;
66214
- }
66215
- else {
66216
- yMin -= size;
66217
- }
66218
- const b = new Bounds();
66219
- b.x = xMin;
66220
- b.y = yMin;
66221
- b.w = xMax - xMin;
66222
- b.h = yMax - yMin;
66223
- return b;
66224
- }
66225
- static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
66226
- if (x1 === x2 && y1 === y2) {
66227
- return [];
66228
- }
66229
- // ensure endX > startX
66230
- if (x2 < x1) {
66231
- let t = x1;
66232
- x1 = x2;
66233
- x2 = t;
66234
- t = y1;
66235
- y1 = y2;
66236
- y2 = t;
66237
- }
66238
- //
66239
- // calculate control points
66240
- //
66241
- offset *= scale;
66242
- size *= scale;
66243
- if (down) {
66244
- offset *= -1;
66245
- size *= -1;
66246
- }
66247
- if (scale >= 1) {
66248
- size *= 1.2;
66249
- }
66250
- // calculate control points on horizontal axis then rotate:
66251
- /*
66252
- cp1x/cpy1 cp2x/cpy2
66253
- *----------------*
66254
- / \
66255
- / \
66256
- x1/y1 * * x2/y2
66257
-
66258
- cp3 and cp4 are simply with lower height
66259
- */
66260
- const dY = y2 - y1;
66261
- const dX = x2 - x1;
66262
- const length = Math.sqrt(dX * dX + dY * dY);
66263
- let cp1x = x1 + length * 0.25;
66264
- let cp1y = y1 - offset;
66265
- let cp2x = x1 + length * 0.75;
66266
- let cp2y = y1 - offset;
66267
- let cp3x = x1 + length * 0.75;
66268
- let cp3y = y1 - offset - size;
66269
- let cp4x = x1 + length * 0.25;
66270
- let cp4y = y1 - offset - size;
66271
- const angle = Math.atan2(dY, dX);
66272
- [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
66273
- [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
66274
- [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
66275
- [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
66276
- return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
66277
- }
66278
- static _rotate(x, y, rotateX, rotateY, angle) {
66279
- const dx = x - rotateX;
66280
- const dy = y - rotateY;
66281
- const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
66282
- const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
66283
- return [rotateX + rx, rotateY + ry];
66284
- }
66285
- static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
66286
- const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66287
- canvas.beginPath();
66288
- canvas.moveTo(cps[0], cps[1]);
66289
- canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
66290
- canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
66291
- canvas.closePath();
66292
- canvas.fill();
66293
- }
66294
- static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
66295
- let normalVectorX = y2 - y1;
66296
- let normalVectorY = x2 - x1;
66297
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66298
- if (down) {
66299
- normalVectorX *= -1;
66300
- }
66301
- else {
66302
- normalVectorY *= -1;
66303
- }
66304
- // make to unit vector
66305
- normalVectorX /= length;
66306
- normalVectorY /= length;
66307
- let offset = bendSlurHeight * scale;
66308
- if (x2 - x1 < 20) {
66309
- offset /= 2;
66310
- }
66311
- const centerY = (y2 + y1) / 2;
66312
- const cp1Y = centerY + offset * normalVectorY;
66313
- return cp1Y;
66314
- }
66315
- static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
66316
- let normalVectorX = y2 - y1;
66317
- let normalVectorY = x2 - x1;
66318
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66319
- if (down) {
66320
- normalVectorX *= -1;
66321
- }
66322
- else {
66323
- normalVectorY *= -1;
66324
- }
66325
- // make to unit vector
66326
- normalVectorX /= length;
66327
- normalVectorY /= length;
66328
- // center of connection
66329
- // TODO: should be 1/3
66330
- const centerX = (x2 + x1) / 2;
66331
- const centerY = (y2 + y1) / 2;
66332
- let offset = bendSlurHeight * scale;
66333
- if (x2 - x1 < 20) {
66334
- offset /= 2;
66335
- }
66336
- const cp1X = centerX + offset * normalVectorX;
66337
- const cp1Y = centerY + offset * normalVectorY;
66338
- canvas.beginPath();
66339
- canvas.moveTo(x1, y1);
66340
- canvas.lineTo(cp1X, cp1Y);
66341
- canvas.lineTo(x2, y2);
66342
- canvas.stroke();
66343
- if (slurText) {
66344
- const w = canvas.measureText(slurText).width;
66345
- const textOffset = down ? 0 : -canvas.font.size;
66346
- canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
66347
- }
66348
- }
66349
- }
66350
-
66351
- /**
66352
- * @internal
66353
- */
66354
- class NumberedTieGlyph extends TieGlyph {
66355
- startNote;
66356
- endNote;
66357
- constructor(startNote, endNote, forEnd = false) {
66358
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
66359
- this.startNote = startNote;
66360
- this.endNote = endNote;
66361
- }
66362
- get _isLeftHandTap() {
66363
- return this.startNote === this.endNote;
66364
- }
66588
+ class NumberedTieGlyph extends NoteTieGlyph {
66365
66589
  shouldDrawBendSlur() {
66366
66590
  return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66367
66591
  !!this.startNote.bendOrigin &&
66368
66592
  this.startNote.isTieOrigin);
66369
66593
  }
66370
- doLayout() {
66371
- super.doLayout();
66372
- }
66373
- getBeamDirection(_beat, _noteRenderer) {
66594
+ calculateTieDirection() {
66374
66595
  return BeamDirection.Up;
66375
66596
  }
66376
- getStartY() {
66377
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66378
- }
66379
- getEndY() {
66380
- return this.getStartY();
66381
- }
66382
- getStartX() {
66383
- if (this._isLeftHandTap) {
66384
- return this.getEndX() - this.startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
66385
- }
66386
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66387
- }
66388
- getEndX() {
66389
- if (this._isLeftHandTap) {
66390
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66391
- }
66392
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66393
- }
66394
66597
  }
66395
66598
 
66396
66599
  /**
66397
66600
  * @internal
66398
66601
  */
66399
- class TabTieGlyph extends TieGlyph {
66400
- startNote;
66401
- endNote;
66402
- constructor(startNote, endNote, forEnd = false) {
66403
- super(startNote.beat, endNote.beat, forEnd);
66404
- this.startNote = startNote;
66405
- this.endNote = endNote;
66406
- }
66407
- get _isLeftHandTap() {
66408
- return this.startNote === this.endNote;
66409
- }
66410
- getTieHeight(startX, startY, endX, endY) {
66411
- if (this._isLeftHandTap) {
66412
- return this.startNoteRenderer.smuflMetrics.tieHeight;
66413
- }
66414
- return super.getTieHeight(startX, startY, endX, endY);
66415
- }
66416
- getBeamDirection(_beat, _noteRenderer) {
66417
- if (this._isLeftHandTap) {
66602
+ class TabTieGlyph extends NoteTieGlyph {
66603
+ calculateTieDirection() {
66604
+ if (this.isLeftHandTap) {
66418
66605
  return BeamDirection.Up;
66419
66606
  }
66420
66607
  return TabTieGlyph.getBeamDirectionForNote(this.startNote);
@@ -66422,54 +66609,25 @@ class TabTieGlyph extends TieGlyph {
66422
66609
  static getBeamDirectionForNote(note) {
66423
66610
  return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
66424
66611
  }
66425
- getStartY() {
66426
- if (this._isLeftHandTap) {
66427
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
66428
- }
66429
- if (this.tieDirection === BeamDirection.Up) {
66430
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66431
- }
66432
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
66433
- }
66434
- getEndY() {
66435
- return this.getStartY();
66436
- }
66437
- getStartX() {
66438
- if (this._isLeftHandTap) {
66439
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
66440
- }
66441
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66442
- }
66443
- getEndX() {
66444
- if (this._isLeftHandTap) {
66445
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66446
- }
66447
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66448
- }
66449
66612
  }
66450
66613
 
66451
66614
  /**
66452
66615
  * @internal
66453
66616
  */
66454
- class NumberedSlurGlyph extends TabTieGlyph {
66455
- _direction;
66617
+ class TabSlurGlyph extends TabTieGlyph {
66456
66618
  _forSlide;
66457
- constructor(startNote, endNote, forSlide, forEnd = false) {
66458
- super(startNote, endNote, forEnd);
66459
- this._direction = BeamDirection.Up;
66619
+ constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
66620
+ super(slurEffectId, startNote, endNote, forEnd);
66460
66621
  this._forSlide = forSlide;
66461
66622
  }
66462
66623
  getTieHeight(startX, _startY, endX, _endY) {
66463
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
66624
+ return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
66464
66625
  }
66465
66626
  tryExpand(startNote, endNote, forSlide, forEnd) {
66466
66627
  // same type required
66467
66628
  if (this._forSlide !== forSlide) {
66468
66629
  return false;
66469
66630
  }
66470
- if (this.forEnd !== forEnd) {
66471
- return false;
66472
- }
66473
66631
  // same start and endbeat
66474
66632
  if (this.startNote.beat.id !== startNote.beat.id) {
66475
66633
  return false;
@@ -66477,41 +66635,43 @@ class NumberedSlurGlyph extends TabTieGlyph {
66477
66635
  if (this.endNote.beat.id !== endNote.beat.id) {
66478
66636
  return false;
66479
66637
  }
66638
+ const isForEnd = this.renderer === this.lookupEndBeatRenderer();
66639
+ if (isForEnd !== forEnd) {
66640
+ return false;
66641
+ }
66642
+ // same draw direction
66643
+ if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
66644
+ return false;
66645
+ }
66480
66646
  // if we can expand, expand in correct direction
66481
- switch (this._direction) {
66647
+ switch (this.tieDirection) {
66482
66648
  case BeamDirection.Up:
66483
66649
  if (startNote.realValue > this.startNote.realValue) {
66484
66650
  this.startNote = startNote;
66485
- this.startBeat = startNote.beat;
66486
66651
  }
66487
66652
  if (endNote.realValue > this.endNote.realValue) {
66488
66653
  this.endNote = endNote;
66489
- this.endBeat = endNote.beat;
66490
66654
  }
66491
66655
  break;
66492
66656
  case BeamDirection.Down:
66493
66657
  if (startNote.realValue < this.startNote.realValue) {
66494
66658
  this.startNote = startNote;
66495
- this.startBeat = startNote.beat;
66496
66659
  }
66497
66660
  if (endNote.realValue < this.endNote.realValue) {
66498
66661
  this.endNote = endNote;
66499
- this.endBeat = endNote.beat;
66500
66662
  }
66501
66663
  break;
66502
66664
  }
66503
66665
  return true;
66504
66666
  }
66505
- paint(cx, cy, canvas) {
66506
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66507
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
66508
- const slurId = `numbered.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
66509
- const renderer = this.renderer;
66510
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
66511
- if (!isSlurRendered) {
66512
- renderer.staff.setSharedLayoutData(slurId, true);
66513
- super.paint(cx, cy, canvas);
66514
- }
66667
+ }
66668
+
66669
+ /**
66670
+ * @internal
66671
+ */
66672
+ class NumberedSlurGlyph extends TabSlurGlyph {
66673
+ calculateTieDirection() {
66674
+ return BeamDirection.Up;
66515
66675
  }
66516
66676
  }
66517
66677
 
@@ -66519,23 +66679,31 @@ class NumberedSlurGlyph extends TabTieGlyph {
66519
66679
  * @internal
66520
66680
  */
66521
66681
  class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66682
+ _slurs = new Map();
66522
66683
  _effectSlurs = [];
66684
+ doLayout() {
66685
+ this._slurs.clear();
66686
+ this._effectSlurs = [];
66687
+ super.doLayout();
66688
+ }
66523
66689
  createTies(n) {
66524
66690
  // create a tie if any effect requires it
66525
66691
  if (!n.isVisible) {
66526
66692
  return;
66527
66693
  }
66528
- if (n.isTieOrigin && n.tieDestination.isVisible) {
66529
- const tie = new NumberedTieGlyph(n, n.tieDestination, false);
66694
+ if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
66695
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
66530
66696
  this.addTie(tie);
66697
+ this._slurs.set(tie.slurEffectId, tie);
66531
66698
  }
66532
66699
  if (n.isTieDestination) {
66533
- const tie = new NumberedTieGlyph(n.tieOrigin, n, true);
66700
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
66534
66701
  this.addTie(tie);
66535
66702
  }
66536
- if (n.isLeftHandTapped && !n.isHammerPullDestination) {
66537
- const tapSlur = new NumberedTieGlyph(n, n, false);
66703
+ if (n.isLeftHandTapped && !n.isHammerPullDestination && !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
66704
+ const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
66538
66705
  this.addTie(tapSlur);
66706
+ this._slurs.set(tapSlur.slurEffectId, tapSlur);
66539
66707
  }
66540
66708
  // start effect slur on first beat
66541
66709
  if (n.isEffectSlurOrigin && n.effectSlurDestination) {
@@ -66547,9 +66715,11 @@ class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66547
66715
  }
66548
66716
  }
66549
66717
  if (!expanded) {
66550
- const effectSlur = new NumberedSlurGlyph(n, n.effectSlurDestination, false, false);
66718
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
66551
66719
  this._effectSlurs.push(effectSlur);
66552
66720
  this.addTie(effectSlur);
66721
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66722
+ this._slurs.set('numbered.slur.effect', effectSlur);
66553
66723
  }
66554
66724
  }
66555
66725
  // end effect slur on last beat
@@ -66562,9 +66732,11 @@ class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66562
66732
  }
66563
66733
  }
66564
66734
  if (!expanded) {
66565
- const effectSlur = new NumberedSlurGlyph(n.effectSlurOrigin, n, false, true);
66735
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
66566
66736
  this._effectSlurs.push(effectSlur);
66567
66737
  this.addTie(effectSlur);
66738
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66739
+ this._slurs.set('numbered.slur.effect', effectSlur);
66568
66740
  }
66569
66741
  }
66570
66742
  }
@@ -66957,10 +67129,11 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66957
67129
  if (sr.shortestDuration < this.container.beat.duration) {
66958
67130
  sr.shortestDuration = this.container.beat.duration;
66959
67131
  }
66960
- const glyphY = sr.getLineY(sr.getNoteLine());
66961
67132
  if (!this.container.beat.isEmpty) {
67133
+ const glyphY = sr.getLineY(0);
66962
67134
  let numberWithinOctave = '0';
66963
67135
  if (this.container.beat.notes.length > 0) {
67136
+ const note = this.container.beat.notes[0];
66964
67137
  const kst = this.renderer.bar.keySignatureType;
66965
67138
  const ks = this.renderer.bar.keySignature;
66966
67139
  const ksi = ks + 7;
@@ -66968,7 +67141,6 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66968
67141
  ? NumberedBeatGlyph.minorKeySignatureOneValues
66969
67142
  : NumberedBeatGlyph.majorKeySignatureOneValues;
66970
67143
  const oneNoteValue = oneNoteValues[ksi];
66971
- const note = this.container.beat.notes[0];
66972
67144
  if (note.isDead) {
66973
67145
  numberWithinOctave = 'X';
66974
67146
  }
@@ -67015,7 +67187,7 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67015
67187
  // Note dots
67016
67188
  if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
67017
67189
  for (let i = 0; i < this.container.beat.dots; i++) {
67018
- const dot = new AugmentationDotGlyph(0, sr.getLineY(0));
67190
+ const dot = new AugmentationDotGlyph(0, glyphY);
67019
67191
  dot.renderer = this.renderer;
67020
67192
  this.addEffect(dot);
67021
67193
  }
@@ -67043,21 +67215,22 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67043
67215
  numberOfQuarterNotes += numberOfAddedQuarters;
67044
67216
  }
67045
67217
  for (let i = 0; i < numberOfQuarterNotes - 1; i++) {
67046
- const dash = new NumberedDashGlyph(0, sr.getLineY(0), this.container.beat);
67218
+ const dash = new NumberedDashGlyph(0, glyphY, this.container.beat);
67047
67219
  dash.renderer = this.renderer;
67048
67220
  this.addNormal(dash);
67049
67221
  }
67050
67222
  }
67051
67223
  super.doLayout();
67052
67224
  if (this.container.beat.isEmpty) {
67053
- this.centerX = this.width / 2;
67225
+ this.onTimeX = this.width / 2;
67054
67226
  }
67055
67227
  else if (this.noteHeads) {
67056
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
67228
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
67057
67229
  }
67058
67230
  else if (this.deadSlapped) {
67059
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
67231
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67060
67232
  }
67233
+ this.middleX = this.onTimeX;
67061
67234
  }
67062
67235
  }
67063
67236
 
@@ -67513,7 +67686,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67513
67686
  }
67514
67687
  }
67515
67688
  }
67516
- getNoteLine() {
67689
+ getNoteLine(_note) {
67517
67690
  return 0;
67518
67691
  }
67519
67692
  get tupletOffset() {
@@ -67923,6 +68096,7 @@ class ScoreNoteChordGlyphBase extends Glyph {
67923
68096
  upLineX = 0;
67924
68097
  downLineX = 0;
67925
68098
  noteStartX = 0;
68099
+ onTimeX = 0;
67926
68100
  constructor() {
67927
68101
  super(0, 0);
67928
68102
  }
@@ -68009,6 +68183,8 @@ class ScoreNoteChordGlyphBase extends Glyph {
68009
68183
  // align all notes so that they align with the stem positions
68010
68184
  const stemPosition = anyDisplaced || direction === BeamDirection.Up ? stemUpX : stemDownX;
68011
68185
  let w = 0;
68186
+ let displacedWidth = 0;
68187
+ let nonDisplacedWidth = 0;
68012
68188
  for (let i = 0, j = this._infos.length; i < j; i++) {
68013
68189
  const g = this._infos[i].glyph;
68014
68190
  const alignDisplaced = displaced.get(i);
@@ -68024,7 +68200,14 @@ class ScoreNoteChordGlyphBase extends Glyph {
68024
68200
  }
68025
68201
  }
68026
68202
  g.x += this.noteStartX;
68027
- w = Math.max(w, g.x + g.width);
68203
+ const gw = g.x + g.width;
68204
+ w = Math.max(w, gw);
68205
+ if (alignDisplaced) {
68206
+ displacedWidth = Math.max(displacedWidth, gw);
68207
+ }
68208
+ else {
68209
+ nonDisplacedWidth = Math.max(nonDisplacedWidth, gw);
68210
+ }
68028
68211
  // after size calculation, re-align glyph to stem if needed
68029
68212
  if (g instanceof NoteHeadGlyph && g.centerOnStem) {
68030
68213
  g.x = stemPosition;
@@ -68038,6 +68221,23 @@ class ScoreNoteChordGlyphBase extends Glyph {
68038
68221
  this.upLineX = stemUpX;
68039
68222
  this.downLineX = stemDownX;
68040
68223
  }
68224
+ // the center of score notes, (used for aligning the beat to the right on-time position)
68225
+ // is always the center of the "correct note" position.
68226
+ // * If the stem is upwards, the center is the middle of the left hand side note head
68227
+ // * If the stem is downards, the center is the middle of the right-hand-side note head
68228
+ if (anyDisplaced) {
68229
+ if (direction === BeamDirection.Up) {
68230
+ this.onTimeX = nonDisplacedWidth / 2;
68231
+ }
68232
+ else {
68233
+ const displacedRawWith = displacedWidth - stemPosition;
68234
+ this.onTimeX = stemPosition + (displacedRawWith / 2);
68235
+ }
68236
+ }
68237
+ else {
68238
+ // for no displaced notes it is simply the center
68239
+ this.onTimeX = w / 2;
68240
+ }
68041
68241
  this.width = w;
68042
68242
  }
68043
68243
  paint(cx, cy, canvas) {
@@ -68214,6 +68414,7 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68214
68414
  this._deadSlapped.renderer = this.renderer;
68215
68415
  this._deadSlapped.doLayout();
68216
68416
  this.width = this._deadSlapped.width;
68417
+ this.onTimeX = this.width / 2;
68217
68418
  }
68218
68419
  let aboveBeatEffectsY = 0;
68219
68420
  let belowBeatEffectsY = 0;
@@ -69112,13 +69313,16 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69112
69313
  }
69113
69314
  super.doLayout();
69114
69315
  if (this.container.beat.isEmpty) {
69115
- this.centerX = this.width / 2;
69316
+ this.onTimeX = this.width / 2;
69317
+ this.middleX = this.onTimeX;
69116
69318
  }
69117
69319
  else if (this.restGlyph) {
69118
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
69320
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
69321
+ this.middleX = this.onTimeX;
69119
69322
  }
69120
69323
  else if (this.noteHeads) {
69121
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
69324
+ this.onTimeX = this.noteHeads.x + this.noteHeads.onTimeX;
69325
+ this.middleX = this.noteHeads.x + this.noteHeads.width / 2;
69122
69326
  }
69123
69327
  }
69124
69328
  _createBeatDot(line, group) {
@@ -69728,87 +69932,128 @@ class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
69728
69932
  * @internal
69729
69933
  */
69730
69934
  class ScoreLegatoGlyph extends TieGlyph {
69731
- constructor(startBeat, endBeat, forEnd = false) {
69732
- super(startBeat, endBeat, forEnd);
69935
+ startBeat;
69936
+ endBeat;
69937
+ startBeatRenderer = null;
69938
+ endBeatRenderer = null;
69939
+ constructor(slurEffectId, startBeat, endBeat, forEnd) {
69940
+ super(slurEffectId, forEnd);
69941
+ this.startBeat = startBeat;
69942
+ this.endBeat = endBeat;
69733
69943
  }
69734
69944
  doLayout() {
69735
69945
  super.doLayout();
69736
69946
  }
69737
- getBeamDirection(beat, noteRenderer) {
69738
- if (beat.isRest) {
69947
+ lookupStartBeatRenderer() {
69948
+ if (!this.startBeatRenderer) {
69949
+ this.startBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
69950
+ }
69951
+ return this.startBeatRenderer;
69952
+ }
69953
+ lookupEndBeatRenderer() {
69954
+ if (!this.endBeatRenderer) {
69955
+ this.endBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
69956
+ }
69957
+ return this.endBeatRenderer;
69958
+ }
69959
+ shouldDrawBendSlur() {
69960
+ return false;
69961
+ }
69962
+ calculateTieDirection() {
69963
+ if (this.startBeat.isRest) {
69739
69964
  return BeamDirection.Up;
69740
69965
  }
69741
69966
  // invert direction (if stems go up, ties go down to not cross them)
69742
- switch (noteRenderer.getBeatDirection(beat)) {
69967
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startBeat)) {
69743
69968
  case BeamDirection.Up:
69744
69969
  return BeamDirection.Down;
69745
69970
  default:
69746
69971
  return BeamDirection.Up;
69747
69972
  }
69748
69973
  }
69749
- getStartY() {
69974
+ calculateStartX() {
69975
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69976
+ return startBeatRenderer.x + startBeatRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69977
+ }
69978
+ calculateStartY() {
69979
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69750
69980
  if (this.startBeat.isRest) {
69751
- // below all lines
69752
- return this.startNoteRenderer.getScoreY(9);
69981
+ switch (this.tieDirection) {
69982
+ case BeamDirection.Up:
69983
+ return (startBeatRenderer.y +
69984
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxTop());
69985
+ default:
69986
+ return (startBeatRenderer.y +
69987
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxBottom());
69988
+ }
69753
69989
  }
69754
69990
  switch (this.tieDirection) {
69755
69991
  case BeamDirection.Up:
69756
69992
  // below lowest note
69757
- return this.startNoteRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69993
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69758
69994
  default:
69759
- return this.startNoteRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69995
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
69996
+ }
69997
+ }
69998
+ calculateEndX() {
69999
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70000
+ if (!endBeatRenderer) {
70001
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
69760
70002
  }
70003
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
70004
+ return (endBeatRenderer.x +
70005
+ endBeatRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
70006
+ ? BeatXPosition.Stem
70007
+ : BeatXPosition.MiddleNotes));
69761
70008
  }
69762
- getEndY() {
69763
- const endNoteScoreRenderer = this.endNoteRenderer;
70009
+ caclculateEndY() {
70010
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70011
+ if (!endBeatRenderer) {
70012
+ return this.calculateStartY();
70013
+ }
69764
70014
  if (this.endBeat.isRest) {
69765
70015
  switch (this.tieDirection) {
69766
70016
  case BeamDirection.Up:
69767
- return endNoteScoreRenderer.getScoreY(9);
70017
+ return (endBeatRenderer.y + endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxTop());
69768
70018
  default:
69769
- return endNoteScoreRenderer.getScoreY(0);
70019
+ return (endBeatRenderer.y +
70020
+ endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxBottom());
69770
70021
  }
69771
70022
  }
69772
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69773
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
70023
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startBeat);
70024
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69774
70025
  if (startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None) {
69775
70026
  if (endBeamDirection === this.tieDirection) {
69776
70027
  switch (this.tieDirection) {
69777
70028
  case BeamDirection.Up:
69778
70029
  // stem upper end
69779
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem);
70030
+ return (endBeatRenderer.y +
70031
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem));
69780
70032
  default:
69781
70033
  // stem lower end
69782
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem);
70034
+ return (endBeatRenderer.y +
70035
+ endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem));
69783
70036
  }
69784
70037
  }
69785
70038
  switch (this.tieDirection) {
69786
70039
  case BeamDirection.Up:
69787
70040
  // stem upper end
69788
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem);
70041
+ return (endBeatRenderer.y +
70042
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem));
69789
70043
  default:
69790
70044
  // stem lower end
69791
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem);
70045
+ return (endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem));
69792
70046
  }
69793
70047
  }
69794
70048
  switch (this.tieDirection) {
69795
70049
  case BeamDirection.Up:
69796
70050
  // below lowest note
69797
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
70051
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
69798
70052
  default:
69799
70053
  // above highest note
69800
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
70054
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
69801
70055
  }
69802
70056
  }
69803
- getStartX() {
69804
- return this.startNoteRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69805
- }
69806
- getEndX() {
69807
- const endBeamDirection = this.endNoteRenderer.getBeatDirection(this.endBeat);
69808
- return this.endNoteRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69809
- ? BeatXPosition.Stem
69810
- : BeatXPosition.MiddleNotes);
69811
- }
69812
70057
  }
69813
70058
 
69814
70059
  /**
@@ -70008,142 +70253,106 @@ class ScoreSlideLineGlyph extends Glyph {
70008
70253
  /**
70009
70254
  * @internal
70010
70255
  */
70011
- class ScoreSlurGlyph extends ScoreLegatoGlyph {
70012
- _startNote;
70013
- _endNote;
70014
- constructor(startNote, endNote, forEnd = false) {
70015
- super(startNote.beat, endNote.beat, forEnd);
70016
- this._startNote = startNote;
70017
- this._endNote = endNote;
70256
+ class ScoreTieGlyph extends NoteTieGlyph {
70257
+ shouldDrawBendSlur() {
70258
+ return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70259
+ !!this.startNote.bendOrigin &&
70260
+ this.startNote.isTieOrigin);
70261
+ }
70262
+ calculateStartX() {
70263
+ if (this.isLeftHandTap) {
70264
+ return this.calculateEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70265
+ }
70266
+ return this.renderer.x + this.renderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
70018
70267
  }
70268
+ calculateEndX() {
70269
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70270
+ if (!endNoteRenderer) {
70271
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70272
+ }
70273
+ if (this.isLeftHandTap) {
70274
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70275
+ }
70276
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70277
+ }
70278
+ }
70279
+
70280
+ /**
70281
+ * @internal
70282
+ */
70283
+ class ScoreSlurGlyph extends ScoreTieGlyph {
70019
70284
  getTieHeight(startX, _startY, endX, _endY) {
70020
- return Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
70285
+ return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
70286
+ }
70287
+ calculateStartX() {
70288
+ return (this.renderer.x +
70289
+ (this._isStartCentered()
70290
+ ? this.renderer.getBeatX(this.startNote.beat, BeatXPosition.MiddleNotes)
70291
+ : this.renderer.getNoteX(this.startNote, NoteXPosition.Right)));
70021
70292
  }
70022
- getStartY() {
70293
+ calculateStartY() {
70023
70294
  if (this._isStartCentered()) {
70024
70295
  switch (this.tieDirection) {
70025
70296
  case BeamDirection.Up:
70026
- // below lowest note
70027
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Top);
70297
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Top);
70028
70298
  default:
70029
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Bottom);
70299
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Bottom);
70030
70300
  }
70031
70301
  }
70032
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Center);
70302
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Center);
70033
70303
  }
70034
- getEndY() {
70304
+ calculateEndX() {
70305
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70306
+ if (!endNoteRenderer) {
70307
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70308
+ }
70309
+ if (this._isEndCentered()) {
70310
+ if (this._isEndOnStem()) {
70311
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.Stem);
70312
+ }
70313
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
70314
+ }
70315
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70316
+ }
70317
+ caclculateEndY() {
70318
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70319
+ if (!endNoteRenderer) {
70320
+ return this.calculateStartY();
70321
+ }
70035
70322
  if (this._isEndCentered()) {
70036
70323
  if (this._isEndOnStem()) {
70037
70324
  switch (this.tieDirection) {
70038
70325
  case BeamDirection.Up:
70039
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.TopWithStem);
70326
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.TopWithStem);
70040
70327
  default:
70041
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.BottomWithStem);
70328
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.BottomWithStem);
70042
70329
  }
70043
70330
  }
70044
70331
  switch (this.tieDirection) {
70045
70332
  case BeamDirection.Up:
70046
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Top);
70333
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
70047
70334
  default:
70048
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Bottom);
70335
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
70049
70336
  }
70050
70337
  }
70051
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Center);
70338
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
70052
70339
  }
70053
70340
  _isStartCentered() {
70054
- return ((this._startNote === this._startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70055
- (this._startNote === this._startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70341
+ return ((this.startNote === this.startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70342
+ (this.startNote === this.startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70056
70343
  }
70057
70344
  _isEndCentered() {
70058
- return (this._startNote.beat.graceType === GraceType.None &&
70059
- ((this._endNote === this._endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70060
- (this._endNote === this._endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70345
+ return (this.startNote.beat.graceType === GraceType.None &&
70346
+ ((this.endNote === this.endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70347
+ (this.endNote === this.endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70061
70348
  }
70062
70349
  _isEndOnStem() {
70063
- const endNoteScoreRenderer = this.endNoteRenderer;
70064
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
70065
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
70066
- return startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None;
70067
- }
70068
- getStartX() {
70069
- return this._isStartCentered()
70070
- ? this.startNoteRenderer.getBeatX(this._startNote.beat, BeatXPosition.MiddleNotes)
70071
- : this.startNoteRenderer.getNoteX(this._startNote, NoteXPosition.Right);
70072
- }
70073
- getEndX() {
70074
- if (this._isEndCentered()) {
70075
- if (this._isEndOnStem()) {
70076
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.Stem);
70077
- }
70078
- return this.endNoteRenderer.getNoteX(this._endNote, NoteXPosition.Center);
70079
- }
70080
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.PreNotes);
70081
- }
70082
- }
70083
-
70084
- /**
70085
- * @internal
70086
- */
70087
- class ScoreTieGlyph extends TieGlyph {
70088
- startNote;
70089
- endNote;
70090
- constructor(startNote, endNote, forEnd = false) {
70091
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
70092
- this.startNote = startNote;
70093
- this.endNote = endNote;
70094
- }
70095
- shouldDrawBendSlur() {
70096
- return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70097
- !!this.startNote.bendOrigin &&
70098
- this.startNote.isTieOrigin);
70099
- }
70100
- doLayout() {
70101
- super.doLayout();
70102
- }
70103
- getBeamDirection(beat, noteRenderer) {
70104
- // invert direction (if stems go up, ties go down to not cross them)
70105
- switch (noteRenderer.getBeatDirection(beat)) {
70106
- case BeamDirection.Up:
70107
- return BeamDirection.Down;
70108
- default:
70109
- return BeamDirection.Up;
70110
- }
70111
- }
70112
- getStartY() {
70113
- if (this.startBeat.isRest) {
70114
- // below all lines
70115
- return this.startNoteRenderer.getScoreY(9);
70116
- }
70117
- switch (this.tieDirection) {
70118
- case BeamDirection.Up:
70119
- // below lowest note
70120
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
70121
- default:
70122
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
70123
- }
70124
- }
70125
- getEndY() {
70126
- const endNoteScoreRenderer = this.endNoteRenderer;
70127
- if (this.endBeat.isRest) {
70128
- switch (this.tieDirection) {
70129
- case BeamDirection.Up:
70130
- return endNoteScoreRenderer.getScoreY(9);
70131
- default:
70132
- return endNoteScoreRenderer.getScoreY(0);
70133
- }
70134
- }
70135
- switch (this.tieDirection) {
70136
- case BeamDirection.Up:
70137
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Top);
70138
- default:
70139
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
70140
- }
70141
- }
70142
- getStartX() {
70143
- return this.startNoteRenderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
70144
- }
70145
- getEndX() {
70146
- return this.endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70350
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat);
70351
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70352
+ const endBeamDirection = endBeatRenderer
70353
+ ? endBeatRenderer.getBeatDirection(this.endNote.beat)
70354
+ : startBeamDirection;
70355
+ return startBeamDirection !== endBeamDirection && this.startNote.beat.graceType === GraceType.None;
70147
70356
  }
70148
70357
  }
70149
70358
 
@@ -70193,12 +70402,11 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70193
70402
  n.beat.graceType !== GraceType.BendGrace &&
70194
70403
  n.tieDestination &&
70195
70404
  n.tieDestination.isVisible) {
70196
- // tslint:disable-next-line: no-unnecessary-type-assertion
70197
- const tie = new ScoreTieGlyph(n, n.tieDestination, false);
70405
+ const tie = new ScoreTieGlyph(`score.tie.${n.id}`, n, n.tieDestination, false);
70198
70406
  this.addTie(tie);
70199
70407
  }
70200
70408
  if (n.isTieDestination && !n.tieOrigin.hasBend && !n.beat.hasWhammyBar) {
70201
- const tie = new ScoreTieGlyph(n.tieOrigin, n, true);
70409
+ const tie = new ScoreTieGlyph(`score.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
70202
70410
  this.addTie(tie);
70203
70411
  }
70204
70412
  // TODO: depending on the type we have other positioning
@@ -70208,17 +70416,16 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70208
70416
  this.addTie(l);
70209
70417
  }
70210
70418
  if (n.isSlurOrigin && n.slurDestination && n.slurDestination.isVisible) {
70211
- // tslint:disable-next-line: no-unnecessary-type-assertion
70212
- const tie = new ScoreSlurGlyph(n, n.slurDestination, false);
70419
+ const tie = new ScoreSlurGlyph(`score.slur.${n.id}`, n, n.slurDestination, false);
70213
70420
  this.addTie(tie);
70214
70421
  }
70215
70422
  if (n.isSlurDestination) {
70216
- const tie = new ScoreSlurGlyph(n.slurOrigin, n, true);
70423
+ const tie = new ScoreSlurGlyph(`score.slur.${n.slurOrigin.id}`, n.slurOrigin, n, true);
70217
70424
  this.addTie(tie);
70218
70425
  }
70219
70426
  // start effect slur on first beat
70220
70427
  if (!this._effectSlur && n.isEffectSlurOrigin && n.effectSlurDestination) {
70221
- const effectSlur = new ScoreSlurGlyph(n, n.effectSlurDestination, false);
70428
+ const effectSlur = new ScoreSlurGlyph(`score.slur.effect.${n.beat.id}`, n, n.effectSlurDestination, false);
70222
70429
  this._effectSlur = effectSlur;
70223
70430
  this.addTie(effectSlur);
70224
70431
  }
@@ -70227,7 +70434,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70227
70434
  const direction = this.onNotes.beamingHelper.direction;
70228
70435
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70229
70436
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70230
- const effectEndSlur = new ScoreSlurGlyph(startNote, endNote, true);
70437
+ const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
70231
70438
  this._effectEndSlur = effectEndSlur;
70232
70439
  this.addTie(effectEndSlur);
70233
70440
  }
@@ -70249,7 +70456,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70249
70456
  while (destination.nextBeat && destination.nextBeat.isLegatoDestination) {
70250
70457
  destination = destination.nextBeat;
70251
70458
  }
70252
- this.addTie(new ScoreLegatoGlyph(this.beat, destination, false));
70459
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${this.beat.id}`, this.beat, destination, false));
70253
70460
  }
70254
70461
  }
70255
70462
  else if (this.beat.isLegatoDestination) {
@@ -70259,7 +70466,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70259
70466
  while (origin.previousBeat && origin.previousBeat.isLegatoOrigin) {
70260
70467
  origin = origin.previousBeat;
70261
70468
  }
70262
- this.addTie(new ScoreLegatoGlyph(origin, this.beat, true));
70469
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${origin.id}`, origin, this.beat, true));
70263
70470
  }
70264
70471
  }
70265
70472
  }
@@ -70639,6 +70846,9 @@ class ScoreBarRenderer extends LineBarRenderer {
70639
70846
  this.addBeatGlyph(container);
70640
70847
  }
70641
70848
  }
70849
+ getNoteLine(note) {
70850
+ return this.accidentalHelper.getNoteSteps(note) / 2;
70851
+ }
70642
70852
  getNoteSteps(n) {
70643
70853
  return this.accidentalHelper.getNoteSteps(n);
70644
70854
  }
@@ -70695,43 +70905,15 @@ class ScoreBarRendererFactory extends BarRendererFactory {
70695
70905
  /**
70696
70906
  * @internal
70697
70907
  */
70698
- class SlashTieGlyph extends TieGlyph {
70699
- startNote;
70700
- endNote;
70701
- constructor(startNote, endNote, forEnd = false) {
70702
- super(startNote.beat, endNote.beat, forEnd);
70703
- this.startNote = startNote;
70704
- this.endNote = endNote;
70705
- }
70706
- get _isLeftHandTap() {
70707
- return this.startNote === this.endNote;
70708
- }
70709
- getTieHeight(startX, startY, endX, endY) {
70710
- if (this._isLeftHandTap) {
70711
- return this.startNoteRenderer.smuflMetrics.tieHeight;
70712
- }
70713
- return super.getTieHeight(startX, startY, endX, endY);
70714
- }
70715
- getBeamDirection(_beat, _noteRenderer) {
70908
+ class SlashTieGlyph extends NoteTieGlyph {
70909
+ calculateTieDirection() {
70716
70910
  return BeamDirection.Down;
70717
70911
  }
70718
- static getBeamDirectionForNote(_note) {
70719
- return BeamDirection.Down;
70720
- }
70721
- getStartY() {
70722
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
70723
- }
70724
- getEndY() {
70725
- return this.getStartY();
70726
- }
70727
- getStartX() {
70728
- if (this._isLeftHandTap) {
70729
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70730
- }
70731
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Right);
70912
+ getStartNotePosition() {
70913
+ return NoteXPosition.Right;
70732
70914
  }
70733
- getEndX() {
70734
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70915
+ getEndNotePosition() {
70916
+ return NoteXPosition.Left;
70735
70917
  }
70736
70918
  }
70737
70919
 
@@ -70746,12 +70928,12 @@ class SlashBeatContainerGlyph extends BeatContainerGlyph {
70746
70928
  return;
70747
70929
  }
70748
70930
  if (!this._tiedNoteTie && n.isTieOrigin && n.tieDestination.isVisible) {
70749
- const tie = new SlashTieGlyph(n, n.tieDestination, false);
70931
+ const tie = new SlashTieGlyph('slash.tie', n, n.tieDestination, false);
70750
70932
  this._tiedNoteTie = tie;
70751
70933
  this.addTie(tie);
70752
70934
  }
70753
70935
  if (!this._tiedNoteTie && n.isTieDestination) {
70754
- const tie = new SlashTieGlyph(n.tieOrigin, n, true);
70936
+ const tie = new SlashTieGlyph('slash.tie', n.tieOrigin, n, true);
70755
70937
  this._tiedNoteTie = tie;
70756
70938
  this.addTie(tie);
70757
70939
  }
@@ -70878,8 +71060,7 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
70878
71060
  doLayout() {
70879
71061
  // create glyphs
70880
71062
  const sr = this.renderer;
70881
- const line = sr.getNoteLine();
70882
- const glyphY = sr.getLineY(line);
71063
+ const glyphY = sr.getLineY(0);
70883
71064
  if (this.container.beat.deadSlapped) {
70884
71065
  const deadSlapped = new DeadSlappedBeatGlyph();
70885
71066
  deadSlapped.renderer = this.renderer;
@@ -70912,22 +71093,23 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
70912
71093
  //
70913
71094
  if (this.container.beat.dots > 0) {
70914
71095
  for (let i = 0; i < this.container.beat.dots; i++) {
70915
- this.addEffect(new AugmentationDotGlyph(0, sr.getLineY(sr.getNoteLine()) - sr.getLineHeight(0.5)));
71096
+ this.addEffect(new AugmentationDotGlyph(0, glyphY - sr.getLineHeight(0.5)));
70916
71097
  }
70917
71098
  }
70918
71099
  super.doLayout();
70919
71100
  if (this.container.beat.isEmpty) {
70920
- this.centerX = this.width / 2;
71101
+ this.onTimeX = this.width / 2;
70921
71102
  }
70922
71103
  else if (this.restGlyph) {
70923
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
71104
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
70924
71105
  }
70925
71106
  else if (this.noteHeads) {
70926
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
71107
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
70927
71108
  }
70928
71109
  else if (this.deadSlapped) {
70929
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
71110
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
70930
71111
  }
71112
+ this.middleX = this.onTimeX;
70931
71113
  }
70932
71114
  }
70933
71115
 
@@ -70994,7 +71176,7 @@ class SlashBarRenderer extends LineBarRenderer {
70994
71176
  this.registerOverflowTop(this.tupletSize);
70995
71177
  }
70996
71178
  }
70997
- getNoteLine() {
71179
+ getNoteLine(_note) {
70998
71180
  return 0;
70999
71181
  }
71000
71182
  getFlagTopY(beat, _direction) {
@@ -71331,77 +71513,6 @@ class TabSlideLineGlyph extends Glyph {
71331
71513
  }
71332
71514
  }
71333
71515
 
71334
- /**
71335
- * @internal
71336
- */
71337
- class TabSlurGlyph extends TabTieGlyph {
71338
- _direction;
71339
- _forSlide;
71340
- constructor(startNote, endNote, forSlide, forEnd = false) {
71341
- super(startNote, endNote, forEnd);
71342
- this._direction = TabTieGlyph.getBeamDirectionForNote(startNote);
71343
- this._forSlide = forSlide;
71344
- }
71345
- getTieHeight(startX, _startY, endX, _endY) {
71346
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
71347
- }
71348
- tryExpand(startNote, endNote, forSlide, forEnd) {
71349
- // same type required
71350
- if (this._forSlide !== forSlide) {
71351
- return false;
71352
- }
71353
- if (this.forEnd !== forEnd) {
71354
- return false;
71355
- }
71356
- // same start and endbeat
71357
- if (this.startNote.beat.id !== startNote.beat.id) {
71358
- return false;
71359
- }
71360
- if (this.endNote.beat.id !== endNote.beat.id) {
71361
- return false;
71362
- }
71363
- // same draw direction
71364
- if (this._direction !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
71365
- return false;
71366
- }
71367
- // if we can expand, expand in correct direction
71368
- switch (this._direction) {
71369
- case BeamDirection.Up:
71370
- if (startNote.realValue > this.startNote.realValue) {
71371
- this.startNote = startNote;
71372
- this.startBeat = startNote.beat;
71373
- }
71374
- if (endNote.realValue > this.endNote.realValue) {
71375
- this.endNote = endNote;
71376
- this.endBeat = endNote.beat;
71377
- }
71378
- break;
71379
- case BeamDirection.Down:
71380
- if (startNote.realValue < this.startNote.realValue) {
71381
- this.startNote = startNote;
71382
- this.startBeat = startNote.beat;
71383
- }
71384
- if (endNote.realValue < this.endNote.realValue) {
71385
- this.endNote = endNote;
71386
- this.endBeat = endNote.beat;
71387
- }
71388
- break;
71389
- }
71390
- return true;
71391
- }
71392
- paint(cx, cy, canvas) {
71393
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
71394
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
71395
- const slurId = `tab.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
71396
- const renderer = this.renderer;
71397
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
71398
- if (!isSlurRendered) {
71399
- renderer.staff.setSharedLayoutData(slurId, true);
71400
- super.paint(cx, cy, canvas);
71401
- }
71402
- }
71403
- }
71404
-
71405
71516
  /**
71406
71517
  * @internal
71407
71518
  */
@@ -71426,15 +71537,15 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71426
71537
  }
71427
71538
  const renderer = this.renderer;
71428
71539
  if (n.isTieOrigin && renderer.showTiedNotes && n.tieDestination.isVisible) {
71429
- const tie = new TabTieGlyph(n, n.tieDestination, false);
71540
+ const tie = new TabTieGlyph(`tab.tie.${n.id}`, n, n.tieDestination, false);
71430
71541
  this.addTie(tie);
71431
71542
  }
71432
71543
  if (n.isTieDestination && renderer.showTiedNotes) {
71433
- const tie = new TabTieGlyph(n.tieOrigin, n, true);
71544
+ const tie = new TabTieGlyph(`tab.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
71434
71545
  this.addTie(tie);
71435
71546
  }
71436
71547
  if (n.isLeftHandTapped && !n.isHammerPullDestination) {
71437
- const tapSlur = new TabTieGlyph(n, n, false);
71548
+ const tapSlur = new TabTieGlyph(`tab.tie.leftHandTap.${n.id}`, n, n, false);
71438
71549
  this.addTie(tapSlur);
71439
71550
  }
71440
71551
  // start effect slur on first beat
@@ -71447,7 +71558,7 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71447
71558
  }
71448
71559
  }
71449
71560
  if (!expanded) {
71450
- const effectSlur = new TabSlurGlyph(n, n.effectSlurDestination, false, false);
71561
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.id}`, n, n.effectSlurDestination, false, false);
71451
71562
  this._effectSlurs.push(effectSlur);
71452
71563
  this.addTie(effectSlur);
71453
71564
  }
@@ -71462,7 +71573,7 @@ class TabBeatContainerGlyph extends BeatContainerGlyph {
71462
71573
  }
71463
71574
  }
71464
71575
  if (!expanded) {
71465
- const effectSlur = new TabSlurGlyph(n.effectSlurOrigin, n, false, true);
71576
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.effectSlurOrigin.id}`, n.effectSlurOrigin, n, false, true);
71466
71577
  this._effectSlurs.push(effectSlur);
71467
71578
  this.addTie(effectSlur);
71468
71579
  }
@@ -71881,7 +71992,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71881
71992
  }
71882
71993
  else {
71883
71994
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
71884
- const y = tabRenderer.getTabY(line);
71995
+ const y = tabRenderer.getLineY(line);
71885
71996
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71886
71997
  this.restGlyph = restGlyph;
71887
71998
  restGlyph.beat = this.container.beat;
@@ -71911,19 +72022,20 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71911
72022
  this.width = w;
71912
72023
  this.computedWidth = w;
71913
72024
  if (this.container.beat.isEmpty) {
71914
- this.centerX = this.width / 2;
72025
+ this.onTimeX = this.width / 2;
71915
72026
  }
71916
72027
  else if (this.restGlyph) {
71917
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
72028
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71918
72029
  }
71919
72030
  else if (this.noteNumbers) {
71920
- this.centerX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
72031
+ this.onTimeX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
71921
72032
  }
71922
72033
  else if (this.slash) {
71923
- this.centerX = this.slash.x + this.slash.width / 2;
72034
+ this.onTimeX = this.slash.x + this.slash.width / 2;
71924
72035
  }
72036
+ this.middleX = this.onTimeX;
71925
72037
  for (const g of centeredEffectGlyphs) {
71926
- g.x = this.centerX;
72038
+ g.x = this.onTimeX;
71927
72039
  }
71928
72040
  }
71929
72041
  updateBeamingHelper() {
@@ -71940,8 +72052,8 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71940
72052
  _createNoteGlyph(n) {
71941
72053
  const tr = this.renderer;
71942
72054
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
71943
- const l = n.beat.voice.bar.staff.tuning.length - n.string;
71944
- noteNumberGlyph.y = tr.getTabY(l);
72055
+ const l = tr.getNoteLine(n);
72056
+ noteNumberGlyph.y = tr.getLineY(l);
71945
72057
  noteNumberGlyph.renderer = this.renderer;
71946
72058
  noteNumberGlyph.doLayout();
71947
72059
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
@@ -72132,17 +72244,8 @@ class TabBarRenderer extends LineBarRenderer {
72132
72244
  }
72133
72245
  return mode;
72134
72246
  }
72135
- /**
72136
- * Gets the relative y position of the given steps relative to first line.
72137
- * @param line the line of the particular string where 0 is the most top line
72138
- * @param correction
72139
- * @returns
72140
- */
72141
- getTabY(line) {
72142
- return super.getLineY(line);
72143
- }
72144
- getTabHeight(line) {
72145
- return super.getLineHeight(line);
72247
+ getNoteLine(note) {
72248
+ return this.bar.staff.tuning.length - note.string;
72146
72249
  }
72147
72250
  minString = Number.NaN;
72148
72251
  maxString = Number.NaN;
@@ -72231,7 +72334,7 @@ class TabBarRenderer extends LineBarRenderer {
72231
72334
  if (this.isFirstOfLine) {
72232
72335
  const center = (this.bar.staff.tuning.length - 1) / 2;
72233
72336
  this.createStartSpacing();
72234
- this.addPreBeatGlyph(new TabClefGlyph(0, this.getTabY(center)));
72337
+ this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
72235
72338
  }
72236
72339
  // Time Signature
72237
72340
  if (this.showTimeSignature &&
@@ -72252,7 +72355,7 @@ class TabBarRenderer extends LineBarRenderer {
72252
72355
  _createTimeSignatureGlyphs() {
72253
72356
  this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
72254
72357
  const lines = (this.bar.staff.tuning.length + 1) / 2 - 1;
72255
- this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getTabY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72358
+ this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getLineY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72256
72359
  }
72257
72360
  createVoiceGlyphs(v) {
72258
72361
  super.createVoiceGlyphs(v);