@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.
package/dist/alphaTab.js CHANGED
@@ -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
  *
@@ -209,9 +209,9 @@
209
209
  * @internal
210
210
  */
211
211
  class VersionInfo {
212
- static version = '1.8.0-alpha.1637';
213
- static date = '2025-12-07T02:26:27.382Z';
214
- static commit = 'aa2c8101456d3ddfc777491468bc483789cc6c12';
212
+ static version = '1.8.0-alpha.1640';
213
+ static date = '2025-12-10T02:19:08.776Z';
214
+ static commit = '343f59ee6b39b4f3a41636b3c33db34bccf631f9';
215
215
  static print(print) {
216
216
  print(`alphaTab ${VersionInfo.version}`);
217
217
  print(`commit: ${VersionInfo.commit}`);
@@ -48533,7 +48533,7 @@
48533
48533
  onNotes;
48534
48534
  minWidth = 0;
48535
48535
  get onTimeX() {
48536
- return this.onNotes.x + this.onNotes.centerX;
48536
+ return this.onNotes.x + this.onNotes.onTimeX;
48537
48537
  }
48538
48538
  constructor(beat, voiceContainer) {
48539
48539
  super(0, 0);
@@ -48557,8 +48557,8 @@
48557
48557
  return helper.hasFlag(false, undefined);
48558
48558
  }
48559
48559
  registerLayoutingInfo(layoutings) {
48560
- const preBeatStretch = this.preNotes.computedWidth + this.onNotes.centerX;
48561
- let postBeatStretch = this.onNotes.computedWidth - this.onNotes.centerX;
48560
+ const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
48561
+ let postBeatStretch = this.onNotes.computedWidth - this.onNotes.onTimeX;
48562
48562
  // make space for flag
48563
48563
  const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48564
48564
  if (this.beat.graceType !== GraceType.None) {
@@ -48701,7 +48701,7 @@
48701
48701
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48702
48702
  beatBoundings.realBounds.w = this.width;
48703
48703
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48704
- beatBoundings.onNotesX = cx + this.x + this.onNotes.centerX;
48704
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48705
48705
  }
48706
48706
  else {
48707
48707
  beatBoundings.visualBounds = new Bounds();
@@ -48739,7 +48739,7 @@
48739
48739
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48740
48740
  beatBoundings.realBounds.w = this.width;
48741
48741
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48742
- beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.centerX;
48742
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48743
48743
  }
48744
48744
  barBounds.addBeat(beatBoundings);
48745
48745
  if (this.renderer.settings.core.includeNoteBounds) {
@@ -54364,6 +54364,7 @@
54364
54364
  class ScalableHtmlElementContainer extends HtmlElementContainer {
54365
54365
  _xscale;
54366
54366
  _yscale;
54367
+ centerAtPosition = false;
54367
54368
  constructor(element, xscale, yscale) {
54368
54369
  super(element);
54369
54370
  this._xscale = xscale;
@@ -54405,7 +54406,11 @@
54405
54406
  else {
54406
54407
  h = h / this._yscale;
54407
54408
  }
54408
- this.element.style.transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54409
+ let transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54410
+ if (this.centerAtPosition) {
54411
+ transform += ` translateX(-50%)`;
54412
+ }
54413
+ this.element.style.transform = transform;
54409
54414
  this.element.style.transformOrigin = 'top left';
54410
54415
  this.lastBounds.x = x;
54411
54416
  this.lastBounds.y = y;
@@ -55266,6 +55271,7 @@
55266
55271
  beatCursor.style.willChange = 'transform';
55267
55272
  beatCursorContainer.width = 3;
55268
55273
  beatCursorContainer.height = 1;
55274
+ beatCursorContainer.centerAtPosition = true;
55269
55275
  beatCursorContainer.setBounds(0, 0, 1, 1);
55270
55276
  // add cursors to UI
55271
55277
  element.insertBefore(cursorWrapper, element.firstChild);
@@ -56640,7 +56646,7 @@
56640
56646
  */
56641
56647
  BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
56642
56648
  /**
56643
- * Gets the middle-notes position which is located after in the middle the note heads.
56649
+ * Gets the middle-notes position which is located after in the exact center of the note heads.
56644
56650
  */
56645
56651
  BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
56646
56652
  /**
@@ -56670,12 +56676,12 @@
56670
56676
  this.endPosition = endPosition;
56671
56677
  }
56672
56678
  get isLinkedWithPrevious() {
56673
- return !!this.previousGlyph && this.previousGlyph.renderer.staff.system === this.renderer.staff.system;
56679
+ return !!this.previousGlyph && this.previousGlyph.renderer.staff?.system === this.renderer.staff.system;
56674
56680
  }
56675
56681
  get isLinkedWithNext() {
56676
56682
  return (!!this.nextGlyph &&
56677
56683
  this.nextGlyph.renderer.isFinalized &&
56678
- this.nextGlyph.renderer.staff.system === this.renderer.staff.system);
56684
+ this.nextGlyph.renderer.staff?.system === this.renderer.staff.system);
56679
56685
  }
56680
56686
  paint(cx, cy, canvas) {
56681
56687
  // if we are linked with the previous, the first glyph of the group will also render this one.
@@ -59063,6 +59069,437 @@
59063
59069
  }
59064
59070
  }
59065
59071
 
59072
+ /**
59073
+ * @internal
59074
+ */
59075
+ class TieGlyph extends Glyph {
59076
+ tieDirection = BeamDirection.Up;
59077
+ slurEffectId;
59078
+ isForEnd;
59079
+ constructor(slurEffectId, forEnd) {
59080
+ super(0, 0);
59081
+ this.slurEffectId = slurEffectId;
59082
+ this.isForEnd = forEnd;
59083
+ }
59084
+ _startX = 0;
59085
+ _startY = 0;
59086
+ _endX = 0;
59087
+ _endY = 0;
59088
+ _tieHeight = 0;
59089
+ _boundingBox;
59090
+ _shouldPaint = false;
59091
+ get checkForOverflow() {
59092
+ return this._shouldPaint && this._boundingBox !== undefined;
59093
+ }
59094
+ getBoundingBoxTop() {
59095
+ if (this._boundingBox) {
59096
+ return this._boundingBox.y;
59097
+ }
59098
+ return this._startY;
59099
+ }
59100
+ getBoundingBoxBottom() {
59101
+ if (this._boundingBox) {
59102
+ return this._boundingBox.y + this._boundingBox.h;
59103
+ }
59104
+ return this._startY;
59105
+ }
59106
+ doLayout() {
59107
+ this.width = 0;
59108
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59109
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59110
+ this._startX = 0;
59111
+ this._endX = 0;
59112
+ this._startY = 0;
59113
+ this._endY = 0;
59114
+ this.height = 0;
59115
+ // if we are on the tie start, we check if we
59116
+ // either can draw till the end note, or we just can draw till the bar end
59117
+ this.tieDirection = this.calculateTieDirection();
59118
+ const forEnd = this.isForEnd;
59119
+ this._shouldPaint = false;
59120
+ if (!forEnd) {
59121
+ if (startNoteRenderer !== endNoteRenderer) {
59122
+ this._startX = this.calculateStartX();
59123
+ this._startY = this.calculateStartY();
59124
+ if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
59125
+ const lastRendererInStaff = startNoteRenderer.staff.barRenderers[startNoteRenderer.staff.barRenderers.length - 1];
59126
+ this._endX = lastRendererInStaff.x + lastRendererInStaff.width;
59127
+ this._endY = this._startY;
59128
+ startNoteRenderer.scoreRenderer.layout.slurRegistry.startMultiSystemSlur(this);
59129
+ }
59130
+ else {
59131
+ this._endX = this.calculateEndX();
59132
+ this._endY = this.caclculateEndY();
59133
+ }
59134
+ }
59135
+ else {
59136
+ this._shouldPaint = true;
59137
+ this._startX = this.calculateStartX();
59138
+ this._endX = this.calculateEndX();
59139
+ this._startY = this.calculateStartY();
59140
+ this._endY = this.caclculateEndY();
59141
+ }
59142
+ this._shouldPaint = true;
59143
+ }
59144
+ else if (startNoteRenderer.staff !== endNoteRenderer.staff) {
59145
+ const firstRendererInStaff = startNoteRenderer.staff.barRenderers[0];
59146
+ this._startX = firstRendererInStaff.x;
59147
+ this._endX = this.calculateEndX();
59148
+ const startGlyph = startNoteRenderer.scoreRenderer.layout.slurRegistry.completeMultiSystemSlur(this);
59149
+ if (startGlyph) {
59150
+ this._startY = startGlyph.calculateMultiSystemSlurY(endNoteRenderer);
59151
+ }
59152
+ else {
59153
+ this._startY = this.caclculateEndY();
59154
+ }
59155
+ this._endY = this.caclculateEndY();
59156
+ this._shouldPaint = startNoteRenderer.staff !== endNoteRenderer.staff;
59157
+ }
59158
+ this._boundingBox = undefined;
59159
+ this.y = Math.min(this._startY, this._endY);
59160
+ if (this.shouldDrawBendSlur()) {
59161
+ this._tieHeight = 0; // TODO: Bend slur height to be considered?
59162
+ }
59163
+ else {
59164
+ this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
59165
+ const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
59166
+ this._boundingBox = tieBoundingBox;
59167
+ this.height = tieBoundingBox.h;
59168
+ if (this.tieDirection === BeamDirection.Up) {
59169
+ // the tie might go above `this.y` due to its shape
59170
+ // here we calculate how much this is so we can consider the
59171
+ // respective overflow
59172
+ const overlap = this.y - tieBoundingBox.y;
59173
+ if (overlap > 0) {
59174
+ this.y -= overlap;
59175
+ }
59176
+ }
59177
+ }
59178
+ }
59179
+ paint(cx, cy, canvas) {
59180
+ if (!this._shouldPaint) {
59181
+ return;
59182
+ }
59183
+ if (this.shouldDrawBendSlur()) {
59184
+ TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
59185
+ }
59186
+ else {
59187
+ 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);
59188
+ }
59189
+ }
59190
+ getTieHeight(_startX, _startY, _endX, _endY) {
59191
+ return this.renderer.smuflMetrics.tieHeight;
59192
+ }
59193
+ calculateMultiSystemSlurY(renderer) {
59194
+ const startRenderer = this.lookupStartBeatRenderer();
59195
+ const startY = this.calculateStartY();
59196
+ const relY = startY - startRenderer.y;
59197
+ return renderer.y + relY;
59198
+ }
59199
+ shouldCreateMultiSystemSlur(renderer) {
59200
+ const endStaff = this.lookupEndBeatRenderer()?.staff;
59201
+ if (!endStaff) {
59202
+ return true;
59203
+ }
59204
+ return renderer.staff.system.index < endStaff.system.index;
59205
+ }
59206
+ static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
59207
+ const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59208
+ if (cp.length === 0) {
59209
+ return new Bounds(x1, y1, x2 - x1, y2 - y1);
59210
+ }
59211
+ // For a musical tie/slur, the extrema occur predictably near the midpoint
59212
+ // Evaluate at midpoint (t=0.5) and check endpoints
59213
+ const p0x = cp[0];
59214
+ const p0y = cp[1];
59215
+ const c1x = cp[2];
59216
+ const c1y = cp[3];
59217
+ const c2x = cp[4];
59218
+ const c2y = cp[5];
59219
+ const p1x = cp[6];
59220
+ const p1y = cp[7];
59221
+ // Evaluate at t=0.5 for midpoint
59222
+ const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
59223
+ const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
59224
+ // Bounds are simply min/max of start, end, and midpoint
59225
+ const xMin = Math.min(p0x, p1x, midX);
59226
+ const xMax = Math.max(p0x, p1x, midX);
59227
+ let yMin = Math.min(p0y, p1y, midY);
59228
+ let yMax = Math.max(p0y, p1y, midY);
59229
+ // Account for thickness of the tie/slur
59230
+ if (down) {
59231
+ yMax += size;
59232
+ }
59233
+ else {
59234
+ yMin -= size;
59235
+ }
59236
+ const b = new Bounds();
59237
+ b.x = xMin;
59238
+ b.y = yMin;
59239
+ b.w = xMax - xMin;
59240
+ b.h = yMax - yMin;
59241
+ return b;
59242
+ }
59243
+ static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
59244
+ if (x1 === x2 && y1 === y2) {
59245
+ return [];
59246
+ }
59247
+ // ensure endX > startX
59248
+ if (x2 < x1) {
59249
+ let t = x1;
59250
+ x1 = x2;
59251
+ x2 = t;
59252
+ t = y1;
59253
+ y1 = y2;
59254
+ y2 = t;
59255
+ }
59256
+ //
59257
+ // calculate control points
59258
+ //
59259
+ offset *= scale;
59260
+ size *= scale;
59261
+ if (down) {
59262
+ offset *= -1;
59263
+ size *= -1;
59264
+ }
59265
+ if (scale >= 1) {
59266
+ size *= 1.2;
59267
+ }
59268
+ // calculate control points on horizontal axis then rotate:
59269
+ /*
59270
+ cp1x/cpy1 cp2x/cpy2
59271
+ *----------------*
59272
+ / \
59273
+ / \
59274
+ x1/y1 * * x2/y2
59275
+
59276
+ cp3 and cp4 are simply with lower height
59277
+ */
59278
+ const dY = y2 - y1;
59279
+ const dX = x2 - x1;
59280
+ const length = Math.sqrt(dX * dX + dY * dY);
59281
+ let cp1x = x1 + length * 0.25;
59282
+ let cp1y = y1 - offset;
59283
+ let cp2x = x1 + length * 0.75;
59284
+ let cp2y = y1 - offset;
59285
+ let cp3x = x1 + length * 0.75;
59286
+ let cp3y = y1 - offset - size;
59287
+ let cp4x = x1 + length * 0.25;
59288
+ let cp4y = y1 - offset - size;
59289
+ const angle = Math.atan2(dY, dX);
59290
+ [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
59291
+ [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
59292
+ [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
59293
+ [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
59294
+ return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
59295
+ }
59296
+ static _rotate(x, y, rotateX, rotateY, angle) {
59297
+ const dx = x - rotateX;
59298
+ const dy = y - rotateY;
59299
+ const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
59300
+ const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
59301
+ return [rotateX + rx, rotateY + ry];
59302
+ }
59303
+ static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
59304
+ const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
59305
+ canvas.beginPath();
59306
+ canvas.moveTo(cps[0], cps[1]);
59307
+ canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
59308
+ canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
59309
+ canvas.closePath();
59310
+ canvas.fill();
59311
+ }
59312
+ static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
59313
+ let normalVectorX = y2 - y1;
59314
+ let normalVectorY = x2 - x1;
59315
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59316
+ if (down) {
59317
+ normalVectorX *= -1;
59318
+ }
59319
+ else {
59320
+ normalVectorY *= -1;
59321
+ }
59322
+ // make to unit vector
59323
+ normalVectorX /= length;
59324
+ normalVectorY /= length;
59325
+ let offset = bendSlurHeight * scale;
59326
+ if (x2 - x1 < 20) {
59327
+ offset /= 2;
59328
+ }
59329
+ const centerY = (y2 + y1) / 2;
59330
+ const cp1Y = centerY + offset * normalVectorY;
59331
+ return cp1Y;
59332
+ }
59333
+ static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
59334
+ let normalVectorX = y2 - y1;
59335
+ let normalVectorY = x2 - x1;
59336
+ const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
59337
+ if (down) {
59338
+ normalVectorX *= -1;
59339
+ }
59340
+ else {
59341
+ normalVectorY *= -1;
59342
+ }
59343
+ // make to unit vector
59344
+ normalVectorX /= length;
59345
+ normalVectorY /= length;
59346
+ // center of connection
59347
+ // TODO: should be 1/3
59348
+ const centerX = (x2 + x1) / 2;
59349
+ const centerY = (y2 + y1) / 2;
59350
+ let offset = bendSlurHeight * scale;
59351
+ if (x2 - x1 < 20) {
59352
+ offset /= 2;
59353
+ }
59354
+ const cp1X = centerX + offset * normalVectorX;
59355
+ const cp1Y = centerY + offset * normalVectorY;
59356
+ canvas.beginPath();
59357
+ canvas.moveTo(x1, y1);
59358
+ canvas.lineTo(cp1X, cp1Y);
59359
+ canvas.lineTo(x2, y2);
59360
+ canvas.stroke();
59361
+ if (slurText) {
59362
+ const w = canvas.measureText(slurText).width;
59363
+ const textOffset = down ? 0 : -canvas.font.size;
59364
+ canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
59365
+ }
59366
+ }
59367
+ }
59368
+ /**
59369
+ * A common tie implementation using note details for positioning
59370
+ * @internal
59371
+ */
59372
+ class NoteTieGlyph extends TieGlyph {
59373
+ startNote;
59374
+ endNote;
59375
+ startNoteRenderer = null;
59376
+ endNoteRenderer = null;
59377
+ constructor(slurEffectId, startNote, endNote, forEnd) {
59378
+ super(slurEffectId, forEnd);
59379
+ this.startNote = startNote;
59380
+ this.endNote = endNote;
59381
+ }
59382
+ get isLeftHandTap() {
59383
+ return this.startNote === this.endNote;
59384
+ }
59385
+ getTieHeight(startX, startY, endX, endY) {
59386
+ if (this.isLeftHandTap) {
59387
+ return this.renderer.smuflMetrics.tieHeight;
59388
+ }
59389
+ return super.getTieHeight(startX, startY, endX, endY);
59390
+ }
59391
+ calculateTieDirection() {
59392
+ // invert direction (if stems go up, ties go down to not cross them)
59393
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat)) {
59394
+ case BeamDirection.Up:
59395
+ return BeamDirection.Down;
59396
+ default:
59397
+ return BeamDirection.Up;
59398
+ }
59399
+ }
59400
+ calculateStartX() {
59401
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59402
+ if (this.isLeftHandTap) {
59403
+ return this.calculateEndX() - startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
59404
+ }
59405
+ return startNoteRenderer.x + startNoteRenderer.getNoteX(this.startNote, this.getStartNotePosition());
59406
+ }
59407
+ getStartNotePosition() {
59408
+ return NoteXPosition.Center;
59409
+ }
59410
+ calculateStartY() {
59411
+ const startNoteRenderer = this.lookupStartBeatRenderer();
59412
+ if (this.isLeftHandTap) {
59413
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
59414
+ }
59415
+ switch (this.tieDirection) {
59416
+ case BeamDirection.Up:
59417
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
59418
+ default:
59419
+ return startNoteRenderer.y + startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
59420
+ }
59421
+ }
59422
+ calculateEndX() {
59423
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59424
+ if (!endNoteRenderer) {
59425
+ return this.calculateStartY() + this.renderer.smuflMetrics.leftHandTabTieWidth;
59426
+ }
59427
+ if (this.isLeftHandTap) {
59428
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
59429
+ }
59430
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
59431
+ }
59432
+ getEndNotePosition() {
59433
+ return NoteXPosition.Center;
59434
+ }
59435
+ caclculateEndY() {
59436
+ const endNoteRenderer = this.lookupEndBeatRenderer();
59437
+ if (!endNoteRenderer) {
59438
+ return this.calculateStartY();
59439
+ }
59440
+ if (this.isLeftHandTap) {
59441
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
59442
+ }
59443
+ switch (this.tieDirection) {
59444
+ case BeamDirection.Up:
59445
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
59446
+ default:
59447
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
59448
+ }
59449
+ }
59450
+ lookupEndBeatRenderer() {
59451
+ if (!this.endNoteRenderer) {
59452
+ this.endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endNote.beat.voice.bar);
59453
+ }
59454
+ return this.endNoteRenderer;
59455
+ }
59456
+ lookupStartBeatRenderer() {
59457
+ if (!this.startNoteRenderer) {
59458
+ this.startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startNote.beat.voice.bar);
59459
+ }
59460
+ return this.startNoteRenderer;
59461
+ }
59462
+ shouldDrawBendSlur() {
59463
+ return false;
59464
+ }
59465
+ }
59466
+ /**
59467
+ * A tie glyph for continued multi-system ties/slurs
59468
+ * @internal
59469
+ */
59470
+ class ContinuationTieGlyph extends TieGlyph {
59471
+ _startTie;
59472
+ constructor(startTie) {
59473
+ super(startTie.slurEffectId, false);
59474
+ this._startTie = startTie;
59475
+ }
59476
+ lookupStartBeatRenderer() {
59477
+ return this.renderer;
59478
+ }
59479
+ lookupEndBeatRenderer() {
59480
+ return this.renderer;
59481
+ }
59482
+ shouldDrawBendSlur() {
59483
+ return false;
59484
+ }
59485
+ calculateTieDirection() {
59486
+ return this._startTie.tieDirection;
59487
+ }
59488
+ calculateStartY() {
59489
+ return this._startTie.calculateMultiSystemSlurY(this.renderer);
59490
+ }
59491
+ caclculateEndY() {
59492
+ return this.calculateStartY();
59493
+ }
59494
+ calculateStartX() {
59495
+ return this.renderer.staff.barRenderers[0].x;
59496
+ }
59497
+ calculateEndX() {
59498
+ const last = this.renderer.staff.barRenderers[this.renderer.staff.barRenderers.length - 1];
59499
+ return last.x + last.width;
59500
+ }
59501
+ }
59502
+
59066
59503
  /**
59067
59504
  * This glyph acts as container for handling
59068
59505
  * multiple voice rendering
@@ -59533,6 +59970,63 @@
59533
59970
  }
59534
59971
  }
59535
59972
 
59973
+ /**
59974
+ * This registry keeps track of which slurs and ties were started and needs completion.
59975
+ * Slurs might span multiple systems, and in such cases we need to create additional
59976
+ * slur/ties in the intermediate and end system.
59977
+ *
59978
+ * @internal
59979
+ *
59980
+ */
59981
+ class SlurRegistry {
59982
+ _staffLookup = new Map();
59983
+ clear() {
59984
+ this._staffLookup.clear();
59985
+ }
59986
+ startMultiSystemSlur(startGlyph) {
59987
+ const staffId = SlurRegistry._staffId(startGlyph.renderer.staff);
59988
+ let container;
59989
+ if (!this._staffLookup.has(staffId)) {
59990
+ container = {
59991
+ startedSlurs: new Map()
59992
+ };
59993
+ this._staffLookup.set(staffId, container);
59994
+ }
59995
+ else {
59996
+ container = this._staffLookup.get(staffId);
59997
+ }
59998
+ container.startedSlurs.set(startGlyph.slurEffectId, { startGlyph });
59999
+ }
60000
+ static _staffId(staff) {
60001
+ return `${staff.modelStaff.index}.${staff.modelStaff.track.index}.${staff.staffId}`;
60002
+ }
60003
+ completeMultiSystemSlur(endGlyph) {
60004
+ const staffId = SlurRegistry._staffId(endGlyph.renderer.staff);
60005
+ if (!this._staffLookup.has(staffId)) {
60006
+ return undefined;
60007
+ }
60008
+ const container = this._staffLookup.get(staffId);
60009
+ if (container.startedSlurs.has(endGlyph.slurEffectId)) {
60010
+ const info = container.startedSlurs.get(endGlyph.slurEffectId);
60011
+ info.endGlyph = endGlyph;
60012
+ return info.startGlyph;
60013
+ }
60014
+ return undefined;
60015
+ }
60016
+ *getAllContinuations(renderer) {
60017
+ const staffId = SlurRegistry._staffId(renderer.staff);
60018
+ if (!this._staffLookup.has(staffId) || renderer.index > 0) {
60019
+ return;
60020
+ }
60021
+ const container = this._staffLookup.get(staffId);
60022
+ for (const g of container.startedSlurs.values()) {
60023
+ if (g.startGlyph.shouldCreateMultiSystemSlur(renderer)) {
60024
+ yield g.startGlyph;
60025
+ }
60026
+ }
60027
+ }
60028
+ }
60029
+
59536
60030
  /**
59537
60031
  * A Staff represents a single line within a StaffSystem.
59538
60032
  * It stores BarRenderer instances created from a given factory.
@@ -59654,7 +60148,6 @@
59654
60148
  this._sharedLayoutData = new Map();
59655
60149
  const lastBar = this.barRenderers[this.barRenderers.length - 1];
59656
60150
  this.barRenderers.splice(this.barRenderers.length - 1, 1);
59657
- this.system.layout.unregisterBarRenderer(this.staffId, lastBar);
59658
60151
  this.topOverflow = 0;
59659
60152
  this.bottomOverflow = 0;
59660
60153
  for (const r of this.barRenderers) {
@@ -59747,23 +60240,23 @@
59747
60240
  // changes in the overflows
59748
60241
  let needsSecondPass = false;
59749
60242
  let topOverflow = this.topOverflow;
59750
- for (let i = 0; i < this.barRenderers.length; i++) {
59751
- this.barRenderers[i].y = this.topPadding + topOverflow;
59752
- if (this.barRenderers[i].finalizeRenderer()) {
60243
+ for (const renderer of this.barRenderers) {
60244
+ renderer.registerMultiSystemSlurs(this.system.layout.slurRegistry.getAllContinuations(renderer));
60245
+ if (renderer.finalizeRenderer()) {
59753
60246
  needsSecondPass = true;
59754
60247
  }
59755
- this.height = Math.max(this.height, this.barRenderers[i].height);
60248
+ this.height = Math.max(this.height, renderer.height);
59756
60249
  }
59757
60250
  // 2nd pass: move renderers to correct position respecting the new overflows
59758
60251
  if (needsSecondPass) {
59759
60252
  topOverflow = this.topOverflow;
59760
60253
  // shift all the renderers to the new position to match required spacing
59761
- for (let i = 0; i < this.barRenderers.length; i++) {
59762
- this.barRenderers[i].y = this.topPadding + topOverflow;
60254
+ for (const renderer of this.barRenderers) {
60255
+ renderer.y = this.topPadding + topOverflow;
59763
60256
  }
59764
60257
  // finalize again (to align ties)
59765
- for (let i = 0; i < this.barRenderers.length; i++) {
59766
- this.barRenderers[i].finalizeRenderer();
60258
+ for (const renderer of this.barRenderers) {
60259
+ renderer.finalizeRenderer();
59767
60260
  }
59768
60261
  }
59769
60262
  if (this.height > 0) {
@@ -60369,6 +60862,7 @@
60369
60862
  if (newBarDisplayScale > barDisplayScale) {
60370
60863
  barDisplayScale = newBarDisplayScale;
60371
60864
  }
60865
+ lastBar.afterReverted();
60372
60866
  }
60373
60867
  this.width -= width;
60374
60868
  this.computedWidth -= width;
@@ -60888,12 +61382,16 @@
60888
61382
  constructor(renderer) {
60889
61383
  this.renderer = renderer;
60890
61384
  }
61385
+ slurRegistry = new SlurRegistry();
60891
61386
  resize() {
60892
61387
  this._lazyPartials.clear();
61388
+ this.slurRegistry.clear();
60893
61389
  this.doResize();
60894
61390
  }
60895
61391
  layoutAndRender() {
60896
61392
  this._lazyPartials.clear();
61393
+ this.slurRegistry.clear();
61394
+ this._barRendererLookup.clear();
60897
61395
  this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
60898
61396
  const score = this.renderer.score;
60899
61397
  this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
@@ -61162,17 +61660,6 @@
61162
61660
  }
61163
61661
  }
61164
61662
  }
61165
- unregisterBarRenderer(key, renderer) {
61166
- if (this._barRendererLookup.has(key)) {
61167
- const lookup = this._barRendererLookup.get(key);
61168
- lookup.delete(renderer.bar.id);
61169
- if (renderer.additionalMultiRestBars) {
61170
- for (const b of renderer.additionalMultiRestBars) {
61171
- lookup.delete(b.id);
61172
- }
61173
- }
61174
- }
61175
- }
61176
61663
  getRendererForBar(key, bar) {
61177
61664
  const barRendererId = bar.id;
61178
61665
  if (this._barRendererLookup.has(key) && this._barRendererLookup.get(key).has(barRendererId)) {
@@ -61338,7 +61825,8 @@
61338
61825
  */
61339
61826
  class BeatOnNoteGlyphBase extends BeatGlyphBase {
61340
61827
  beamingHelper;
61341
- centerX = 0;
61828
+ onTimeX = 0;
61829
+ middleX = 0;
61342
61830
  updateBeamingHelper() {
61343
61831
  }
61344
61832
  buildBoundingsLookup(_beatBounds, _cx, _cy) {
@@ -62161,6 +62649,7 @@
62161
62649
  _voiceContainers = new Map();
62162
62650
  _postBeatGlyphs = new LeftToRightLayoutingGlyphGroup();
62163
62651
  _ties = [];
62652
+ _multiSystemSlurs;
62164
62653
  topEffects;
62165
62654
  bottomEffects;
62166
62655
  get nextRenderer() {
@@ -62313,6 +62802,11 @@
62313
62802
  }
62314
62803
  }
62315
62804
  _appliedLayoutingInfo = 0;
62805
+ afterReverted() {
62806
+ this.staff = undefined;
62807
+ this.registerMultiSystemSlurs(undefined);
62808
+ this.isFinalized = false;
62809
+ }
62316
62810
  afterStaffBarReverted() {
62317
62811
  this.topEffects.afterStaffBarReverted();
62318
62812
  this.bottomEffects.afterStaffBarReverted();
@@ -62358,13 +62852,26 @@
62358
62852
  return true;
62359
62853
  }
62360
62854
  isFinalized = false;
62361
- finalizeRenderer() {
62362
- this.isFinalized = true;
62855
+ registerMultiSystemSlurs(startedTies) {
62856
+ if (!startedTies) {
62857
+ this._multiSystemSlurs = undefined;
62858
+ return;
62859
+ }
62860
+ let ties = undefined;
62861
+ for (const g of startedTies) {
62862
+ const continuation = new ContinuationTieGlyph(g);
62863
+ continuation.renderer = this;
62864
+ continuation.tieDirection = g.tieDirection;
62865
+ if (!ties) {
62866
+ ties = [];
62867
+ }
62868
+ ties.push(continuation);
62869
+ }
62870
+ this._multiSystemSlurs = ties;
62871
+ }
62872
+ _finalizeTies(ties, barTop, barBottom) {
62363
62873
  let didChangeOverflows = false;
62364
- // allow spacing to be used for tie overflows
62365
- const barTop = this.y;
62366
- const barBottom = this.y + this.height;
62367
- for (const t of this._ties) {
62874
+ for (const t of ties) {
62368
62875
  const tie = t;
62369
62876
  tie.doLayout();
62370
62877
  if (t.checkForOverflow) {
@@ -62385,6 +62892,21 @@
62385
62892
  }
62386
62893
  }
62387
62894
  }
62895
+ return didChangeOverflows;
62896
+ }
62897
+ finalizeRenderer() {
62898
+ this.isFinalized = true;
62899
+ let didChangeOverflows = false;
62900
+ // allow spacing to be used for tie overflows
62901
+ const barTop = this.y;
62902
+ const barBottom = this.y + this.height;
62903
+ if (this._finalizeTies(this._ties, barTop, barBottom)) {
62904
+ didChangeOverflows = true;
62905
+ }
62906
+ const multiSystemSlurs = this._multiSystemSlurs;
62907
+ if (multiSystemSlurs && this._finalizeTies(multiSystemSlurs, barTop, barBottom)) {
62908
+ didChangeOverflows = true;
62909
+ }
62388
62910
  const topHeightChanged = this.topEffects.finalizeEffects();
62389
62911
  const bottomHeightChanged = this.bottomEffects.finalizeEffects();
62390
62912
  if (topHeightChanged || bottomHeightChanged) {
@@ -62565,6 +63087,16 @@
62565
63087
  }
62566
63088
  canvas.color = this.resources.mainGlyphColor;
62567
63089
  this._postBeatGlyphs.paint(cx + this.x, cy + this.y, canvas);
63090
+ this._paintMultiSystemSlurs(cx, cy, canvas);
63091
+ }
63092
+ _paintMultiSystemSlurs(cx, cy, canvas) {
63093
+ const multiSystemSlurs = this._multiSystemSlurs;
63094
+ if (!multiSystemSlurs) {
63095
+ return;
63096
+ }
63097
+ for (const slur of multiSystemSlurs) {
63098
+ slur.paint(cx, cy, canvas);
63099
+ }
62568
63100
  }
62569
63101
  paintBackground(cx, cy, canvas) {
62570
63102
  this.layoutingInfo.paint(cx + this.x + this._preBeatGlyphs.x + this._preBeatGlyphs.width, cy + this.y + this.height, canvas);
@@ -62630,7 +63162,7 @@
62630
63162
  case BeatXPosition.OnNotes:
62631
63163
  return container.voiceContainer.x + container.x + container.onNotes.x;
62632
63164
  case BeatXPosition.MiddleNotes:
62633
- return container.voiceContainer.x + container.x + container.onTimeX;
63165
+ return container.voiceContainer.x + container.x + container.onNotes.x + container.onNotes.middleX;
62634
63166
  case BeatXPosition.Stem:
62635
63167
  const offset = container.onNotes.beamingHelper
62636
63168
  ? container.onNotes.beamingHelper.getBeatLineX(beat)
@@ -64643,6 +65175,13 @@
64643
65175
  }
64644
65176
  }
64645
65177
  else {
65178
+ // clear out staves during re-layout, this info is outdated during
65179
+ // re-layout of the bars
65180
+ for (const r of this._allMasterBarRenderers) {
65181
+ for (const b of r.renderers) {
65182
+ b.afterReverted();
65183
+ }
65184
+ }
64646
65185
  this._systems = [];
64647
65186
  let currentIndex = 0;
64648
65187
  const maxWidth = this._maxWidth;
@@ -65104,7 +65643,7 @@
65104
65643
  // as during layout things are still moving
65105
65644
  let actualLineHeight = this.height;
65106
65645
  const thisStaff = renderer.staff;
65107
- const allStaves = renderer.staff.system.allStaves;
65646
+ const allStaves = thisStaff.system.allStaves;
65108
65647
  let isExtended = false;
65109
65648
  if (this._extendToNextStaff && thisStaff.index < allStaves.length - 1) {
65110
65649
  const nextStaff = allStaves[thisStaff.index + 1];
@@ -65286,7 +65825,7 @@
65286
65825
  }
65287
65826
  // during system fitting it can happen that we have fraction widths
65288
65827
  // but to have lines until the full end-pixel we round up.
65289
- // this way we avoid holes,
65828
+ // this way we avoid holes,
65290
65829
  const lineWidth = this.width;
65291
65830
  // we want the lines to be exactly virtually aligned with the respective Y-position
65292
65831
  // for note heads to align correctly
@@ -66052,375 +66591,23 @@
66052
66591
  /**
66053
66592
  * @internal
66054
66593
  */
66055
- class TieGlyph extends Glyph {
66056
- startBeat;
66057
- endBeat;
66058
- yOffset = 0;
66059
- forEnd;
66060
- startNoteRenderer = null;
66061
- endNoteRenderer = null;
66062
- tieDirection = BeamDirection.Up;
66063
- constructor(startBeat, endBeat, forEnd) {
66064
- super(0, 0);
66065
- this.startBeat = startBeat;
66066
- this.endBeat = endBeat;
66067
- this.forEnd = forEnd;
66068
- }
66069
- _startX = 0;
66070
- _startY = 0;
66071
- _endX = 0;
66072
- _endY = 0;
66073
- _tieHeight = 0;
66074
- _shouldDraw = false;
66075
- _boundingBox;
66076
- get checkForOverflow() {
66077
- return this._boundingBox !== undefined;
66078
- }
66079
- getBoundingBoxTop() {
66080
- if (this._boundingBox) {
66081
- return this._boundingBox.y;
66082
- }
66083
- return this._startY;
66084
- }
66085
- getBoundingBoxBottom() {
66086
- if (this._boundingBox) {
66087
- return this._boundingBox.y + this._boundingBox.h;
66088
- }
66089
- return this._startY;
66090
- }
66091
- doLayout() {
66092
- this.width = 0;
66093
- // TODO fix nullability of start/end beat,
66094
- if (!this.endBeat) {
66095
- this._shouldDraw = false;
66096
- return;
66097
- }
66098
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66099
- this.startNoteRenderer = startNoteRenderer;
66100
- const endNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
66101
- this.endNoteRenderer = endNoteRenderer;
66102
- this._startX = 0;
66103
- this._endX = 0;
66104
- this._startY = 0;
66105
- this._endY = 0;
66106
- this.height = 0;
66107
- this._shouldDraw = false;
66108
- // if we are on the tie start, we check if we
66109
- // either can draw till the end note, or we just can draw till the bar end
66110
- this.tieDirection = !startNoteRenderer
66111
- ? this.getBeamDirection(this.endBeat, endNoteRenderer)
66112
- : this.getBeamDirection(this.startBeat, startNoteRenderer);
66113
- if (!this.forEnd && startNoteRenderer) {
66114
- // line break or bar break
66115
- if (startNoteRenderer !== endNoteRenderer) {
66116
- this._startX = startNoteRenderer.x + this.getStartX();
66117
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
66118
- // line break: to bar end
66119
- if (!endNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
66120
- this._endX = startNoteRenderer.x + startNoteRenderer.width;
66121
- this._endY = this._startY;
66122
- }
66123
- else {
66124
- this._endX = endNoteRenderer.x + this.getEndX();
66125
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66126
- }
66127
- }
66128
- else {
66129
- this._startX = startNoteRenderer.x + this.getStartX();
66130
- this._endX = endNoteRenderer.x + this.getEndX();
66131
- this._startY = startNoteRenderer.y + this.getStartY() + this.yOffset;
66132
- this._endY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66133
- }
66134
- this._shouldDraw = true;
66135
- }
66136
- else if (!startNoteRenderer || startNoteRenderer.staff !== endNoteRenderer.staff) {
66137
- this._startX = endNoteRenderer.x;
66138
- this._endX = endNoteRenderer.x + this.getEndX();
66139
- this._startY = endNoteRenderer.y + this.getEndY() + this.yOffset;
66140
- this._endY = this._startY;
66141
- this._shouldDraw = true;
66142
- }
66143
- this._boundingBox = undefined;
66144
- if (this._shouldDraw) {
66145
- this.y = Math.min(this._startY, this._endY);
66146
- if (this.shouldDrawBendSlur()) {
66147
- this._tieHeight = 0; // TODO: Bend slur height to be considered?
66148
- }
66149
- else {
66150
- this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
66151
- const tieBoundingBox = TieGlyph.calculateActualTieHeight(1, this._startX, this._startY, this._endX, this._endY, this.tieDirection === BeamDirection.Down, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
66152
- this._boundingBox = tieBoundingBox;
66153
- this.height = tieBoundingBox.h;
66154
- if (this.tieDirection === BeamDirection.Up) {
66155
- // the tie might go above `this.y` due to its shape
66156
- // here we calculate how much this is so we can consider the
66157
- // respective overflow
66158
- const overlap = this.y - tieBoundingBox.y;
66159
- if (overlap > 0) {
66160
- this.y -= overlap;
66161
- }
66162
- }
66163
- }
66164
- }
66165
- }
66166
- paint(cx, cy, canvas) {
66167
- if (this._shouldDraw) {
66168
- if (this.shouldDrawBendSlur()) {
66169
- TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, this.tieDirection === BeamDirection.Down, 1, this.renderer.smuflMetrics.tieHeight);
66170
- }
66171
- else {
66172
- 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);
66173
- }
66174
- }
66175
- }
66176
- shouldDrawBendSlur() {
66177
- return false;
66178
- }
66179
- getTieHeight(_startX, _startY, _endX, _endY) {
66180
- return this.renderer.smuflMetrics.tieHeight;
66181
- }
66182
- getBeamDirection(_beat, _noteRenderer) {
66183
- return BeamDirection.Down;
66184
- }
66185
- getStartY() {
66186
- return 0;
66187
- }
66188
- getEndY() {
66189
- return 0;
66190
- }
66191
- getStartX() {
66192
- return 0;
66193
- }
66194
- getEndX() {
66195
- return 0;
66196
- }
66197
- static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
66198
- const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66199
- // For a musical tie/slur, the extrema occur predictably near the midpoint
66200
- // Evaluate at midpoint (t=0.5) and check endpoints
66201
- const p0x = cp[0];
66202
- const p0y = cp[1];
66203
- const c1x = cp[2];
66204
- const c1y = cp[3];
66205
- const c2x = cp[4];
66206
- const c2y = cp[5];
66207
- const p1x = cp[6];
66208
- const p1y = cp[7];
66209
- // Evaluate at t=0.5 for midpoint
66210
- const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
66211
- const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
66212
- // Bounds are simply min/max of start, end, and midpoint
66213
- const xMin = Math.min(p0x, p1x, midX);
66214
- const xMax = Math.max(p0x, p1x, midX);
66215
- let yMin = Math.min(p0y, p1y, midY);
66216
- let yMax = Math.max(p0y, p1y, midY);
66217
- // Account for thickness of the tie/slur
66218
- if (down) {
66219
- yMax += size;
66220
- }
66221
- else {
66222
- yMin -= size;
66223
- }
66224
- const b = new Bounds();
66225
- b.x = xMin;
66226
- b.y = yMin;
66227
- b.w = xMax - xMin;
66228
- b.h = yMax - yMin;
66229
- return b;
66230
- }
66231
- static _computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size) {
66232
- if (x1 === x2 && y1 === y2) {
66233
- return [];
66234
- }
66235
- // ensure endX > startX
66236
- if (x2 < x1) {
66237
- let t = x1;
66238
- x1 = x2;
66239
- x2 = t;
66240
- t = y1;
66241
- y1 = y2;
66242
- y2 = t;
66243
- }
66244
- //
66245
- // calculate control points
66246
- //
66247
- offset *= scale;
66248
- size *= scale;
66249
- if (down) {
66250
- offset *= -1;
66251
- size *= -1;
66252
- }
66253
- if (scale >= 1) {
66254
- size *= 1.2;
66255
- }
66256
- // calculate control points on horizontal axis then rotate:
66257
- /*
66258
- cp1x/cpy1 cp2x/cpy2
66259
- *----------------*
66260
- / \
66261
- / \
66262
- x1/y1 * * x2/y2
66263
-
66264
- cp3 and cp4 are simply with lower height
66265
- */
66266
- const dY = y2 - y1;
66267
- const dX = x2 - x1;
66268
- const length = Math.sqrt(dX * dX + dY * dY);
66269
- let cp1x = x1 + length * 0.25;
66270
- let cp1y = y1 - offset;
66271
- let cp2x = x1 + length * 0.75;
66272
- let cp2y = y1 - offset;
66273
- let cp3x = x1 + length * 0.75;
66274
- let cp3y = y1 - offset - size;
66275
- let cp4x = x1 + length * 0.25;
66276
- let cp4y = y1 - offset - size;
66277
- const angle = Math.atan2(dY, dX);
66278
- [cp1x, cp1y] = TieGlyph._rotate(cp1x, cp1y, x1, y1, angle);
66279
- [cp2x, cp2y] = TieGlyph._rotate(cp2x, cp2y, x1, y1, angle);
66280
- [cp3x, cp3y] = TieGlyph._rotate(cp3x, cp3y, x1, y1, angle);
66281
- [cp4x, cp4y] = TieGlyph._rotate(cp4x, cp4y, x1, y1, angle);
66282
- return [x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, cp3x, cp3y, cp4x, cp4y, x1, y1];
66283
- }
66284
- static _rotate(x, y, rotateX, rotateY, angle) {
66285
- const dx = x - rotateX;
66286
- const dy = y - rotateY;
66287
- const rx = dx * Math.cos(angle) - dy * Math.sin(angle);
66288
- const ry = dx * Math.sin(angle) + dy * Math.cos(angle);
66289
- return [rotateX + rx, rotateY + ry];
66290
- }
66291
- static paintTie(canvas, scale, x1, y1, x2, y2, down /*= false*/, offset /*= 22*/, size /*= 4*/) {
66292
- const cps = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
66293
- canvas.beginPath();
66294
- canvas.moveTo(cps[0], cps[1]);
66295
- canvas.bezierCurveTo(cps[2], cps[3], cps[4], cps[5], cps[6], cps[7]);
66296
- canvas.bezierCurveTo(cps[8], cps[9], cps[10], cps[11], cps[12], cps[13]);
66297
- canvas.closePath();
66298
- canvas.fill();
66299
- }
66300
- static calculateBendSlurTopY(x1, y1, x2, y2, down, scale, bendSlurHeight) {
66301
- let normalVectorX = y2 - y1;
66302
- let normalVectorY = x2 - x1;
66303
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66304
- if (down) {
66305
- normalVectorX *= -1;
66306
- }
66307
- else {
66308
- normalVectorY *= -1;
66309
- }
66310
- // make to unit vector
66311
- normalVectorX /= length;
66312
- normalVectorY /= length;
66313
- let offset = bendSlurHeight * scale;
66314
- if (x2 - x1 < 20) {
66315
- offset /= 2;
66316
- }
66317
- const centerY = (y2 + y1) / 2;
66318
- const cp1Y = centerY + offset * normalVectorY;
66319
- return cp1Y;
66320
- }
66321
- static drawBendSlur(canvas, x1, y1, x2, y2, down, scale, bendSlurHeight, slurText) {
66322
- let normalVectorX = y2 - y1;
66323
- let normalVectorY = x2 - x1;
66324
- const length = Math.sqrt(normalVectorX * normalVectorX + normalVectorY * normalVectorY);
66325
- if (down) {
66326
- normalVectorX *= -1;
66327
- }
66328
- else {
66329
- normalVectorY *= -1;
66330
- }
66331
- // make to unit vector
66332
- normalVectorX /= length;
66333
- normalVectorY /= length;
66334
- // center of connection
66335
- // TODO: should be 1/3
66336
- const centerX = (x2 + x1) / 2;
66337
- const centerY = (y2 + y1) / 2;
66338
- let offset = bendSlurHeight * scale;
66339
- if (x2 - x1 < 20) {
66340
- offset /= 2;
66341
- }
66342
- const cp1X = centerX + offset * normalVectorX;
66343
- const cp1Y = centerY + offset * normalVectorY;
66344
- canvas.beginPath();
66345
- canvas.moveTo(x1, y1);
66346
- canvas.lineTo(cp1X, cp1Y);
66347
- canvas.lineTo(x2, y2);
66348
- canvas.stroke();
66349
- if (slurText) {
66350
- const w = canvas.measureText(slurText).width;
66351
- const textOffset = down ? 0 : -canvas.font.size;
66352
- canvas.fillText(slurText, cp1X - w / 2, cp1Y + textOffset);
66353
- }
66354
- }
66355
- }
66356
-
66357
- /**
66358
- * @internal
66359
- */
66360
- class NumberedTieGlyph extends TieGlyph {
66361
- startNote;
66362
- endNote;
66363
- constructor(startNote, endNote, forEnd = false) {
66364
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
66365
- this.startNote = startNote;
66366
- this.endNote = endNote;
66367
- }
66368
- get _isLeftHandTap() {
66369
- return this.startNote === this.endNote;
66370
- }
66594
+ class NumberedTieGlyph extends NoteTieGlyph {
66371
66595
  shouldDrawBendSlur() {
66372
66596
  return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66373
66597
  !!this.startNote.bendOrigin &&
66374
66598
  this.startNote.isTieOrigin);
66375
66599
  }
66376
- doLayout() {
66377
- super.doLayout();
66378
- }
66379
- getBeamDirection(_beat, _noteRenderer) {
66600
+ calculateTieDirection() {
66380
66601
  return BeamDirection.Up;
66381
66602
  }
66382
- getStartY() {
66383
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66384
- }
66385
- getEndY() {
66386
- return this.getStartY();
66387
- }
66388
- getStartX() {
66389
- if (this._isLeftHandTap) {
66390
- return this.getEndX() - this.startNoteRenderer.smuflMetrics.leftHandTabTieWidth;
66391
- }
66392
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66393
- }
66394
- getEndX() {
66395
- if (this._isLeftHandTap) {
66396
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66397
- }
66398
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66399
- }
66400
66603
  }
66401
66604
 
66402
66605
  /**
66403
66606
  * @internal
66404
66607
  */
66405
- class TabTieGlyph extends TieGlyph {
66406
- startNote;
66407
- endNote;
66408
- constructor(startNote, endNote, forEnd = false) {
66409
- super(startNote.beat, endNote.beat, forEnd);
66410
- this.startNote = startNote;
66411
- this.endNote = endNote;
66412
- }
66413
- get _isLeftHandTap() {
66414
- return this.startNote === this.endNote;
66415
- }
66416
- getTieHeight(startX, startY, endX, endY) {
66417
- if (this._isLeftHandTap) {
66418
- return this.startNoteRenderer.smuflMetrics.tieHeight;
66419
- }
66420
- return super.getTieHeight(startX, startY, endX, endY);
66421
- }
66422
- getBeamDirection(_beat, _noteRenderer) {
66423
- if (this._isLeftHandTap) {
66608
+ class TabTieGlyph extends NoteTieGlyph {
66609
+ calculateTieDirection() {
66610
+ if (this.isLeftHandTap) {
66424
66611
  return BeamDirection.Up;
66425
66612
  }
66426
66613
  return TabTieGlyph.getBeamDirectionForNote(this.startNote);
@@ -66428,54 +66615,25 @@
66428
66615
  static getBeamDirectionForNote(note) {
66429
66616
  return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
66430
66617
  }
66431
- getStartY() {
66432
- if (this._isLeftHandTap) {
66433
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
66434
- }
66435
- if (this.tieDirection === BeamDirection.Up) {
66436
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
66437
- }
66438
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
66439
- }
66440
- getEndY() {
66441
- return this.getStartY();
66442
- }
66443
- getStartX() {
66444
- if (this._isLeftHandTap) {
66445
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
66446
- }
66447
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Center);
66448
- }
66449
- getEndX() {
66450
- if (this._isLeftHandTap) {
66451
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
66452
- }
66453
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
66454
- }
66455
66618
  }
66456
66619
 
66457
66620
  /**
66458
66621
  * @internal
66459
66622
  */
66460
- class NumberedSlurGlyph extends TabTieGlyph {
66461
- _direction;
66623
+ class TabSlurGlyph extends TabTieGlyph {
66462
66624
  _forSlide;
66463
- constructor(startNote, endNote, forSlide, forEnd = false) {
66464
- super(startNote, endNote, forEnd);
66465
- this._direction = BeamDirection.Up;
66625
+ constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
66626
+ super(slurEffectId, startNote, endNote, forEnd);
66466
66627
  this._forSlide = forSlide;
66467
66628
  }
66468
66629
  getTieHeight(startX, _startY, endX, _endY) {
66469
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
66630
+ return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
66470
66631
  }
66471
66632
  tryExpand(startNote, endNote, forSlide, forEnd) {
66472
66633
  // same type required
66473
66634
  if (this._forSlide !== forSlide) {
66474
66635
  return false;
66475
66636
  }
66476
- if (this.forEnd !== forEnd) {
66477
- return false;
66478
- }
66479
66637
  // same start and endbeat
66480
66638
  if (this.startNote.beat.id !== startNote.beat.id) {
66481
66639
  return false;
@@ -66483,41 +66641,43 @@
66483
66641
  if (this.endNote.beat.id !== endNote.beat.id) {
66484
66642
  return false;
66485
66643
  }
66644
+ const isForEnd = this.renderer === this.lookupEndBeatRenderer();
66645
+ if (isForEnd !== forEnd) {
66646
+ return false;
66647
+ }
66648
+ // same draw direction
66649
+ if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
66650
+ return false;
66651
+ }
66486
66652
  // if we can expand, expand in correct direction
66487
- switch (this._direction) {
66653
+ switch (this.tieDirection) {
66488
66654
  case BeamDirection.Up:
66489
66655
  if (startNote.realValue > this.startNote.realValue) {
66490
66656
  this.startNote = startNote;
66491
- this.startBeat = startNote.beat;
66492
66657
  }
66493
66658
  if (endNote.realValue > this.endNote.realValue) {
66494
66659
  this.endNote = endNote;
66495
- this.endBeat = endNote.beat;
66496
66660
  }
66497
66661
  break;
66498
66662
  case BeamDirection.Down:
66499
66663
  if (startNote.realValue < this.startNote.realValue) {
66500
66664
  this.startNote = startNote;
66501
- this.startBeat = startNote.beat;
66502
66665
  }
66503
66666
  if (endNote.realValue < this.endNote.realValue) {
66504
66667
  this.endNote = endNote;
66505
- this.endBeat = endNote.beat;
66506
66668
  }
66507
66669
  break;
66508
66670
  }
66509
66671
  return true;
66510
66672
  }
66511
- paint(cx, cy, canvas) {
66512
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
66513
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
66514
- const slurId = `numbered.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
66515
- const renderer = this.renderer;
66516
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
66517
- if (!isSlurRendered) {
66518
- renderer.staff.setSharedLayoutData(slurId, true);
66519
- super.paint(cx, cy, canvas);
66520
- }
66673
+ }
66674
+
66675
+ /**
66676
+ * @internal
66677
+ */
66678
+ class NumberedSlurGlyph extends TabSlurGlyph {
66679
+ calculateTieDirection() {
66680
+ return BeamDirection.Up;
66521
66681
  }
66522
66682
  }
66523
66683
 
@@ -66525,23 +66685,31 @@
66525
66685
  * @internal
66526
66686
  */
66527
66687
  class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66688
+ _slurs = new Map();
66528
66689
  _effectSlurs = [];
66690
+ doLayout() {
66691
+ this._slurs.clear();
66692
+ this._effectSlurs = [];
66693
+ super.doLayout();
66694
+ }
66529
66695
  createTies(n) {
66530
66696
  // create a tie if any effect requires it
66531
66697
  if (!n.isVisible) {
66532
66698
  return;
66533
66699
  }
66534
- if (n.isTieOrigin && n.tieDestination.isVisible) {
66535
- const tie = new NumberedTieGlyph(n, n.tieDestination, false);
66700
+ if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
66701
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
66536
66702
  this.addTie(tie);
66703
+ this._slurs.set(tie.slurEffectId, tie);
66537
66704
  }
66538
66705
  if (n.isTieDestination) {
66539
- const tie = new NumberedTieGlyph(n.tieOrigin, n, true);
66706
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
66540
66707
  this.addTie(tie);
66541
66708
  }
66542
- if (n.isLeftHandTapped && !n.isHammerPullDestination) {
66543
- const tapSlur = new NumberedTieGlyph(n, n, false);
66709
+ if (n.isLeftHandTapped && !n.isHammerPullDestination && !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
66710
+ const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
66544
66711
  this.addTie(tapSlur);
66712
+ this._slurs.set(tapSlur.slurEffectId, tapSlur);
66545
66713
  }
66546
66714
  // start effect slur on first beat
66547
66715
  if (n.isEffectSlurOrigin && n.effectSlurDestination) {
@@ -66553,9 +66721,11 @@
66553
66721
  }
66554
66722
  }
66555
66723
  if (!expanded) {
66556
- const effectSlur = new NumberedSlurGlyph(n, n.effectSlurDestination, false, false);
66724
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
66557
66725
  this._effectSlurs.push(effectSlur);
66558
66726
  this.addTie(effectSlur);
66727
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66728
+ this._slurs.set('numbered.slur.effect', effectSlur);
66559
66729
  }
66560
66730
  }
66561
66731
  // end effect slur on last beat
@@ -66568,9 +66738,11 @@
66568
66738
  }
66569
66739
  }
66570
66740
  if (!expanded) {
66571
- const effectSlur = new NumberedSlurGlyph(n.effectSlurOrigin, n, false, true);
66741
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
66572
66742
  this._effectSlurs.push(effectSlur);
66573
66743
  this.addTie(effectSlur);
66744
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66745
+ this._slurs.set('numbered.slur.effect', effectSlur);
66574
66746
  }
66575
66747
  }
66576
66748
  }
@@ -66963,10 +67135,11 @@
66963
67135
  if (sr.shortestDuration < this.container.beat.duration) {
66964
67136
  sr.shortestDuration = this.container.beat.duration;
66965
67137
  }
66966
- const glyphY = sr.getLineY(sr.getNoteLine());
66967
67138
  if (!this.container.beat.isEmpty) {
67139
+ const glyphY = sr.getLineY(0);
66968
67140
  let numberWithinOctave = '0';
66969
67141
  if (this.container.beat.notes.length > 0) {
67142
+ const note = this.container.beat.notes[0];
66970
67143
  const kst = this.renderer.bar.keySignatureType;
66971
67144
  const ks = this.renderer.bar.keySignature;
66972
67145
  const ksi = ks + 7;
@@ -66974,7 +67147,6 @@
66974
67147
  ? NumberedBeatGlyph.minorKeySignatureOneValues
66975
67148
  : NumberedBeatGlyph.majorKeySignatureOneValues;
66976
67149
  const oneNoteValue = oneNoteValues[ksi];
66977
- const note = this.container.beat.notes[0];
66978
67150
  if (note.isDead) {
66979
67151
  numberWithinOctave = 'X';
66980
67152
  }
@@ -67021,7 +67193,7 @@
67021
67193
  // Note dots
67022
67194
  if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
67023
67195
  for (let i = 0; i < this.container.beat.dots; i++) {
67024
- const dot = new AugmentationDotGlyph(0, sr.getLineY(0));
67196
+ const dot = new AugmentationDotGlyph(0, glyphY);
67025
67197
  dot.renderer = this.renderer;
67026
67198
  this.addEffect(dot);
67027
67199
  }
@@ -67049,21 +67221,22 @@
67049
67221
  numberOfQuarterNotes += numberOfAddedQuarters;
67050
67222
  }
67051
67223
  for (let i = 0; i < numberOfQuarterNotes - 1; i++) {
67052
- const dash = new NumberedDashGlyph(0, sr.getLineY(0), this.container.beat);
67224
+ const dash = new NumberedDashGlyph(0, glyphY, this.container.beat);
67053
67225
  dash.renderer = this.renderer;
67054
67226
  this.addNormal(dash);
67055
67227
  }
67056
67228
  }
67057
67229
  super.doLayout();
67058
67230
  if (this.container.beat.isEmpty) {
67059
- this.centerX = this.width / 2;
67231
+ this.onTimeX = this.width / 2;
67060
67232
  }
67061
67233
  else if (this.noteHeads) {
67062
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
67234
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
67063
67235
  }
67064
67236
  else if (this.deadSlapped) {
67065
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
67237
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67066
67238
  }
67239
+ this.middleX = this.onTimeX;
67067
67240
  }
67068
67241
  }
67069
67242
 
@@ -67519,7 +67692,7 @@
67519
67692
  }
67520
67693
  }
67521
67694
  }
67522
- getNoteLine() {
67695
+ getNoteLine(_note) {
67523
67696
  return 0;
67524
67697
  }
67525
67698
  get tupletOffset() {
@@ -67929,6 +68102,7 @@
67929
68102
  upLineX = 0;
67930
68103
  downLineX = 0;
67931
68104
  noteStartX = 0;
68105
+ onTimeX = 0;
67932
68106
  constructor() {
67933
68107
  super(0, 0);
67934
68108
  }
@@ -68015,6 +68189,8 @@
68015
68189
  // align all notes so that they align with the stem positions
68016
68190
  const stemPosition = anyDisplaced || direction === BeamDirection.Up ? stemUpX : stemDownX;
68017
68191
  let w = 0;
68192
+ let displacedWidth = 0;
68193
+ let nonDisplacedWidth = 0;
68018
68194
  for (let i = 0, j = this._infos.length; i < j; i++) {
68019
68195
  const g = this._infos[i].glyph;
68020
68196
  const alignDisplaced = displaced.get(i);
@@ -68030,7 +68206,14 @@
68030
68206
  }
68031
68207
  }
68032
68208
  g.x += this.noteStartX;
68033
- w = Math.max(w, g.x + g.width);
68209
+ const gw = g.x + g.width;
68210
+ w = Math.max(w, gw);
68211
+ if (alignDisplaced) {
68212
+ displacedWidth = Math.max(displacedWidth, gw);
68213
+ }
68214
+ else {
68215
+ nonDisplacedWidth = Math.max(nonDisplacedWidth, gw);
68216
+ }
68034
68217
  // after size calculation, re-align glyph to stem if needed
68035
68218
  if (g instanceof NoteHeadGlyph && g.centerOnStem) {
68036
68219
  g.x = stemPosition;
@@ -68044,6 +68227,23 @@
68044
68227
  this.upLineX = stemUpX;
68045
68228
  this.downLineX = stemDownX;
68046
68229
  }
68230
+ // the center of score notes, (used for aligning the beat to the right on-time position)
68231
+ // is always the center of the "correct note" position.
68232
+ // * If the stem is upwards, the center is the middle of the left hand side note head
68233
+ // * If the stem is downards, the center is the middle of the right-hand-side note head
68234
+ if (anyDisplaced) {
68235
+ if (direction === BeamDirection.Up) {
68236
+ this.onTimeX = nonDisplacedWidth / 2;
68237
+ }
68238
+ else {
68239
+ const displacedRawWith = displacedWidth - stemPosition;
68240
+ this.onTimeX = stemPosition + (displacedRawWith / 2);
68241
+ }
68242
+ }
68243
+ else {
68244
+ // for no displaced notes it is simply the center
68245
+ this.onTimeX = w / 2;
68246
+ }
68047
68247
  this.width = w;
68048
68248
  }
68049
68249
  paint(cx, cy, canvas) {
@@ -68220,6 +68420,7 @@
68220
68420
  this._deadSlapped.renderer = this.renderer;
68221
68421
  this._deadSlapped.doLayout();
68222
68422
  this.width = this._deadSlapped.width;
68423
+ this.onTimeX = this.width / 2;
68223
68424
  }
68224
68425
  let aboveBeatEffectsY = 0;
68225
68426
  let belowBeatEffectsY = 0;
@@ -69118,13 +69319,16 @@
69118
69319
  }
69119
69320
  super.doLayout();
69120
69321
  if (this.container.beat.isEmpty) {
69121
- this.centerX = this.width / 2;
69322
+ this.onTimeX = this.width / 2;
69323
+ this.middleX = this.onTimeX;
69122
69324
  }
69123
69325
  else if (this.restGlyph) {
69124
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
69326
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
69327
+ this.middleX = this.onTimeX;
69125
69328
  }
69126
69329
  else if (this.noteHeads) {
69127
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
69330
+ this.onTimeX = this.noteHeads.x + this.noteHeads.onTimeX;
69331
+ this.middleX = this.noteHeads.x + this.noteHeads.width / 2;
69128
69332
  }
69129
69333
  }
69130
69334
  _createBeatDot(line, group) {
@@ -69734,87 +69938,128 @@
69734
69938
  * @internal
69735
69939
  */
69736
69940
  class ScoreLegatoGlyph extends TieGlyph {
69737
- constructor(startBeat, endBeat, forEnd = false) {
69738
- super(startBeat, endBeat, forEnd);
69941
+ startBeat;
69942
+ endBeat;
69943
+ startBeatRenderer = null;
69944
+ endBeatRenderer = null;
69945
+ constructor(slurEffectId, startBeat, endBeat, forEnd) {
69946
+ super(slurEffectId, forEnd);
69947
+ this.startBeat = startBeat;
69948
+ this.endBeat = endBeat;
69739
69949
  }
69740
69950
  doLayout() {
69741
69951
  super.doLayout();
69742
69952
  }
69743
- getBeamDirection(beat, noteRenderer) {
69744
- if (beat.isRest) {
69953
+ lookupStartBeatRenderer() {
69954
+ if (!this.startBeatRenderer) {
69955
+ this.startBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
69956
+ }
69957
+ return this.startBeatRenderer;
69958
+ }
69959
+ lookupEndBeatRenderer() {
69960
+ if (!this.endBeatRenderer) {
69961
+ this.endBeatRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.endBeat.voice.bar);
69962
+ }
69963
+ return this.endBeatRenderer;
69964
+ }
69965
+ shouldDrawBendSlur() {
69966
+ return false;
69967
+ }
69968
+ calculateTieDirection() {
69969
+ if (this.startBeat.isRest) {
69745
69970
  return BeamDirection.Up;
69746
69971
  }
69747
69972
  // invert direction (if stems go up, ties go down to not cross them)
69748
- switch (noteRenderer.getBeatDirection(beat)) {
69973
+ switch (this.lookupStartBeatRenderer().getBeatDirection(this.startBeat)) {
69749
69974
  case BeamDirection.Up:
69750
69975
  return BeamDirection.Down;
69751
69976
  default:
69752
69977
  return BeamDirection.Up;
69753
69978
  }
69754
69979
  }
69755
- getStartY() {
69980
+ calculateStartX() {
69981
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69982
+ return startBeatRenderer.x + startBeatRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69983
+ }
69984
+ calculateStartY() {
69985
+ const startBeatRenderer = this.lookupStartBeatRenderer();
69756
69986
  if (this.startBeat.isRest) {
69757
- // below all lines
69758
- return this.startNoteRenderer.getScoreY(9);
69987
+ switch (this.tieDirection) {
69988
+ case BeamDirection.Up:
69989
+ return (startBeatRenderer.y +
69990
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxTop());
69991
+ default:
69992
+ return (startBeatRenderer.y +
69993
+ startBeatRenderer.getBeatContainer(this.startBeat).onNotes.getBoundingBoxBottom());
69994
+ }
69759
69995
  }
69760
69996
  switch (this.tieDirection) {
69761
69997
  case BeamDirection.Up:
69762
69998
  // below lowest note
69763
- return this.startNoteRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69999
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.maxNote, NoteYPosition.Top);
69764
70000
  default:
69765
- return this.startNoteRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
70001
+ return startBeatRenderer.y + startBeatRenderer.getNoteY(this.startBeat.minNote, NoteYPosition.Bottom);
70002
+ }
70003
+ }
70004
+ calculateEndX() {
70005
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70006
+ if (!endBeatRenderer) {
70007
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
69766
70008
  }
70009
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
70010
+ return (endBeatRenderer.x +
70011
+ endBeatRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
70012
+ ? BeatXPosition.Stem
70013
+ : BeatXPosition.MiddleNotes));
69767
70014
  }
69768
- getEndY() {
69769
- const endNoteScoreRenderer = this.endNoteRenderer;
70015
+ caclculateEndY() {
70016
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70017
+ if (!endBeatRenderer) {
70018
+ return this.calculateStartY();
70019
+ }
69770
70020
  if (this.endBeat.isRest) {
69771
70021
  switch (this.tieDirection) {
69772
70022
  case BeamDirection.Up:
69773
- return endNoteScoreRenderer.getScoreY(9);
70023
+ return (endBeatRenderer.y + endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxTop());
69774
70024
  default:
69775
- return endNoteScoreRenderer.getScoreY(0);
70025
+ return (endBeatRenderer.y +
70026
+ endBeatRenderer.getBeatContainer(this.endBeat).onNotes.getBoundingBoxBottom());
69776
70027
  }
69777
70028
  }
69778
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
69779
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
70029
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startBeat);
70030
+ const endBeamDirection = endBeatRenderer.getBeatDirection(this.endBeat);
69780
70031
  if (startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None) {
69781
70032
  if (endBeamDirection === this.tieDirection) {
69782
70033
  switch (this.tieDirection) {
69783
70034
  case BeamDirection.Up:
69784
70035
  // stem upper end
69785
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem);
70036
+ return (endBeatRenderer.y +
70037
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.TopWithStem));
69786
70038
  default:
69787
70039
  // stem lower end
69788
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem);
70040
+ return (endBeatRenderer.y +
70041
+ endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.BottomWithStem));
69789
70042
  }
69790
70043
  }
69791
70044
  switch (this.tieDirection) {
69792
70045
  case BeamDirection.Up:
69793
70046
  // stem upper end
69794
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem);
70047
+ return (endBeatRenderer.y +
70048
+ endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.BottomWithStem));
69795
70049
  default:
69796
70050
  // stem lower end
69797
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem);
70051
+ return (endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.TopWithStem));
69798
70052
  }
69799
70053
  }
69800
70054
  switch (this.tieDirection) {
69801
70055
  case BeamDirection.Up:
69802
70056
  // below lowest note
69803
- return endNoteScoreRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
70057
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.maxNote, NoteYPosition.Top);
69804
70058
  default:
69805
70059
  // above highest note
69806
- return endNoteScoreRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
70060
+ return endBeatRenderer.y + endBeatRenderer.getNoteY(this.endBeat.minNote, NoteYPosition.Bottom);
69807
70061
  }
69808
70062
  }
69809
- getStartX() {
69810
- return this.startNoteRenderer.getBeatX(this.startBeat, BeatXPosition.MiddleNotes);
69811
- }
69812
- getEndX() {
69813
- const endBeamDirection = this.endNoteRenderer.getBeatDirection(this.endBeat);
69814
- return this.endNoteRenderer.getBeatX(this.endBeat, this.endBeat.duration > Duration.Whole && endBeamDirection === this.tieDirection
69815
- ? BeatXPosition.Stem
69816
- : BeatXPosition.MiddleNotes);
69817
- }
69818
70063
  }
69819
70064
 
69820
70065
  /**
@@ -70014,142 +70259,106 @@
70014
70259
  /**
70015
70260
  * @internal
70016
70261
  */
70017
- class ScoreSlurGlyph extends ScoreLegatoGlyph {
70018
- _startNote;
70019
- _endNote;
70020
- constructor(startNote, endNote, forEnd = false) {
70021
- super(startNote.beat, endNote.beat, forEnd);
70022
- this._startNote = startNote;
70023
- this._endNote = endNote;
70262
+ class ScoreTieGlyph extends NoteTieGlyph {
70263
+ shouldDrawBendSlur() {
70264
+ return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70265
+ !!this.startNote.bendOrigin &&
70266
+ this.startNote.isTieOrigin);
70267
+ }
70268
+ calculateStartX() {
70269
+ if (this.isLeftHandTap) {
70270
+ return this.calculateEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70271
+ }
70272
+ return this.renderer.x + this.renderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
70024
70273
  }
70274
+ calculateEndX() {
70275
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70276
+ if (!endNoteRenderer) {
70277
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70278
+ }
70279
+ if (this.isLeftHandTap) {
70280
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70281
+ }
70282
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70283
+ }
70284
+ }
70285
+
70286
+ /**
70287
+ * @internal
70288
+ */
70289
+ class ScoreSlurGlyph extends ScoreTieGlyph {
70025
70290
  getTieHeight(startX, _startY, endX, _endY) {
70026
- return Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
70291
+ return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
70292
+ }
70293
+ calculateStartX() {
70294
+ return (this.renderer.x +
70295
+ (this._isStartCentered()
70296
+ ? this.renderer.getBeatX(this.startNote.beat, BeatXPosition.MiddleNotes)
70297
+ : this.renderer.getNoteX(this.startNote, NoteXPosition.Right)));
70027
70298
  }
70028
- getStartY() {
70299
+ calculateStartY() {
70029
70300
  if (this._isStartCentered()) {
70030
70301
  switch (this.tieDirection) {
70031
70302
  case BeamDirection.Up:
70032
- // below lowest note
70033
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Top);
70303
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Top);
70034
70304
  default:
70035
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Bottom);
70305
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Bottom);
70036
70306
  }
70037
70307
  }
70038
- return this.startNoteRenderer.getNoteY(this._startNote, NoteYPosition.Center);
70308
+ return this.renderer.y + this.renderer.getNoteY(this.startNote, NoteYPosition.Center);
70039
70309
  }
70040
- getEndY() {
70310
+ calculateEndX() {
70311
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70312
+ if (!endNoteRenderer) {
70313
+ return this.calculateStartX() + this.renderer.smuflMetrics.leftHandTabTieWidth;
70314
+ }
70315
+ if (this._isEndCentered()) {
70316
+ if (this._isEndOnStem()) {
70317
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.Stem);
70318
+ }
70319
+ return endNoteRenderer.x + endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Center);
70320
+ }
70321
+ return endNoteRenderer.x + endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70322
+ }
70323
+ caclculateEndY() {
70324
+ const endNoteRenderer = this.lookupEndBeatRenderer();
70325
+ if (!endNoteRenderer) {
70326
+ return this.calculateStartY();
70327
+ }
70041
70328
  if (this._isEndCentered()) {
70042
70329
  if (this._isEndOnStem()) {
70043
70330
  switch (this.tieDirection) {
70044
70331
  case BeamDirection.Up:
70045
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.TopWithStem);
70332
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.TopWithStem);
70046
70333
  default:
70047
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.BottomWithStem);
70334
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.BottomWithStem);
70048
70335
  }
70049
70336
  }
70050
70337
  switch (this.tieDirection) {
70051
70338
  case BeamDirection.Up:
70052
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Top);
70339
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Top);
70053
70340
  default:
70054
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Bottom);
70341
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
70055
70342
  }
70056
70343
  }
70057
- return this.endNoteRenderer.getNoteY(this._endNote, NoteYPosition.Center);
70344
+ return endNoteRenderer.y + endNoteRenderer.getNoteY(this.endNote, NoteYPosition.Center);
70058
70345
  }
70059
70346
  _isStartCentered() {
70060
- return ((this._startNote === this._startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70061
- (this._startNote === this._startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70347
+ return ((this.startNote === this.startNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70348
+ (this.startNote === this.startNote.beat.minNote && this.tieDirection === BeamDirection.Down));
70062
70349
  }
70063
70350
  _isEndCentered() {
70064
- return (this._startNote.beat.graceType === GraceType.None &&
70065
- ((this._endNote === this._endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70066
- (this._endNote === this._endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70351
+ return (this.startNote.beat.graceType === GraceType.None &&
70352
+ ((this.endNote === this.endNote.beat.maxNote && this.tieDirection === BeamDirection.Up) ||
70353
+ (this.endNote === this.endNote.beat.minNote && this.tieDirection === BeamDirection.Down)));
70067
70354
  }
70068
70355
  _isEndOnStem() {
70069
- const endNoteScoreRenderer = this.endNoteRenderer;
70070
- const startBeamDirection = this.startNoteRenderer.getBeatDirection(this.startBeat);
70071
- const endBeamDirection = endNoteScoreRenderer.getBeatDirection(this.endBeat);
70072
- return startBeamDirection !== endBeamDirection && this.startBeat.graceType === GraceType.None;
70073
- }
70074
- getStartX() {
70075
- return this._isStartCentered()
70076
- ? this.startNoteRenderer.getBeatX(this._startNote.beat, BeatXPosition.MiddleNotes)
70077
- : this.startNoteRenderer.getNoteX(this._startNote, NoteXPosition.Right);
70078
- }
70079
- getEndX() {
70080
- if (this._isEndCentered()) {
70081
- if (this._isEndOnStem()) {
70082
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.Stem);
70083
- }
70084
- return this.endNoteRenderer.getNoteX(this._endNote, NoteXPosition.Center);
70085
- }
70086
- return this.endNoteRenderer.getBeatX(this._endNote.beat, BeatXPosition.PreNotes);
70087
- }
70088
- }
70089
-
70090
- /**
70091
- * @internal
70092
- */
70093
- class ScoreTieGlyph extends TieGlyph {
70094
- startNote;
70095
- endNote;
70096
- constructor(startNote, endNote, forEnd = false) {
70097
- super(!startNote ? null : startNote.beat, !endNote ? null : endNote.beat, forEnd);
70098
- this.startNote = startNote;
70099
- this.endNote = endNote;
70100
- }
70101
- shouldDrawBendSlur() {
70102
- return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
70103
- !!this.startNote.bendOrigin &&
70104
- this.startNote.isTieOrigin);
70105
- }
70106
- doLayout() {
70107
- super.doLayout();
70108
- }
70109
- getBeamDirection(beat, noteRenderer) {
70110
- // invert direction (if stems go up, ties go down to not cross them)
70111
- switch (noteRenderer.getBeatDirection(beat)) {
70112
- case BeamDirection.Up:
70113
- return BeamDirection.Down;
70114
- default:
70115
- return BeamDirection.Up;
70116
- }
70117
- }
70118
- getStartY() {
70119
- if (this.startBeat.isRest) {
70120
- // below all lines
70121
- return this.startNoteRenderer.getScoreY(9);
70122
- }
70123
- switch (this.tieDirection) {
70124
- case BeamDirection.Up:
70125
- // below lowest note
70126
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Top);
70127
- default:
70128
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Bottom);
70129
- }
70130
- }
70131
- getEndY() {
70132
- const endNoteScoreRenderer = this.endNoteRenderer;
70133
- if (this.endBeat.isRest) {
70134
- switch (this.tieDirection) {
70135
- case BeamDirection.Up:
70136
- return endNoteScoreRenderer.getScoreY(9);
70137
- default:
70138
- return endNoteScoreRenderer.getScoreY(0);
70139
- }
70140
- }
70141
- switch (this.tieDirection) {
70142
- case BeamDirection.Up:
70143
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Top);
70144
- default:
70145
- return endNoteScoreRenderer.getNoteY(this.endNote, NoteYPosition.Bottom);
70146
- }
70147
- }
70148
- getStartX() {
70149
- return this.startNoteRenderer.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
70150
- }
70151
- getEndX() {
70152
- return this.endNoteRenderer.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
70356
+ const startBeamDirection = this.lookupStartBeatRenderer().getBeatDirection(this.startNote.beat);
70357
+ const endBeatRenderer = this.lookupEndBeatRenderer();
70358
+ const endBeamDirection = endBeatRenderer
70359
+ ? endBeatRenderer.getBeatDirection(this.endNote.beat)
70360
+ : startBeamDirection;
70361
+ return startBeamDirection !== endBeamDirection && this.startNote.beat.graceType === GraceType.None;
70153
70362
  }
70154
70363
  }
70155
70364
 
@@ -70199,12 +70408,11 @@
70199
70408
  n.beat.graceType !== GraceType.BendGrace &&
70200
70409
  n.tieDestination &&
70201
70410
  n.tieDestination.isVisible) {
70202
- // tslint:disable-next-line: no-unnecessary-type-assertion
70203
- const tie = new ScoreTieGlyph(n, n.tieDestination, false);
70411
+ const tie = new ScoreTieGlyph(`score.tie.${n.id}`, n, n.tieDestination, false);
70204
70412
  this.addTie(tie);
70205
70413
  }
70206
70414
  if (n.isTieDestination && !n.tieOrigin.hasBend && !n.beat.hasWhammyBar) {
70207
- const tie = new ScoreTieGlyph(n.tieOrigin, n, true);
70415
+ const tie = new ScoreTieGlyph(`score.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
70208
70416
  this.addTie(tie);
70209
70417
  }
70210
70418
  // TODO: depending on the type we have other positioning
@@ -70214,17 +70422,16 @@
70214
70422
  this.addTie(l);
70215
70423
  }
70216
70424
  if (n.isSlurOrigin && n.slurDestination && n.slurDestination.isVisible) {
70217
- // tslint:disable-next-line: no-unnecessary-type-assertion
70218
- const tie = new ScoreSlurGlyph(n, n.slurDestination, false);
70425
+ const tie = new ScoreSlurGlyph(`score.slur.${n.id}`, n, n.slurDestination, false);
70219
70426
  this.addTie(tie);
70220
70427
  }
70221
70428
  if (n.isSlurDestination) {
70222
- const tie = new ScoreSlurGlyph(n.slurOrigin, n, true);
70429
+ const tie = new ScoreSlurGlyph(`score.slur.${n.slurOrigin.id}`, n.slurOrigin, n, true);
70223
70430
  this.addTie(tie);
70224
70431
  }
70225
70432
  // start effect slur on first beat
70226
70433
  if (!this._effectSlur && n.isEffectSlurOrigin && n.effectSlurDestination) {
70227
- const effectSlur = new ScoreSlurGlyph(n, n.effectSlurDestination, false);
70434
+ const effectSlur = new ScoreSlurGlyph(`score.slur.effect.${n.beat.id}`, n, n.effectSlurDestination, false);
70228
70435
  this._effectSlur = effectSlur;
70229
70436
  this.addTie(effectSlur);
70230
70437
  }
@@ -70233,7 +70440,7 @@
70233
70440
  const direction = this.onNotes.beamingHelper.direction;
70234
70441
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70235
70442
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70236
- const effectEndSlur = new ScoreSlurGlyph(startNote, endNote, true);
70443
+ const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
70237
70444
  this._effectEndSlur = effectEndSlur;
70238
70445
  this.addTie(effectEndSlur);
70239
70446
  }
@@ -70255,7 +70462,7 @@
70255
70462
  while (destination.nextBeat && destination.nextBeat.isLegatoDestination) {
70256
70463
  destination = destination.nextBeat;
70257
70464
  }
70258
- this.addTie(new ScoreLegatoGlyph(this.beat, destination, false));
70465
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${this.beat.id}`, this.beat, destination, false));
70259
70466
  }
70260
70467
  }
70261
70468
  else if (this.beat.isLegatoDestination) {
@@ -70265,7 +70472,7 @@
70265
70472
  while (origin.previousBeat && origin.previousBeat.isLegatoOrigin) {
70266
70473
  origin = origin.previousBeat;
70267
70474
  }
70268
- this.addTie(new ScoreLegatoGlyph(origin, this.beat, true));
70475
+ this.addTie(new ScoreLegatoGlyph(`score.legato.${origin.id}`, origin, this.beat, true));
70269
70476
  }
70270
70477
  }
70271
70478
  }
@@ -70645,6 +70852,9 @@
70645
70852
  this.addBeatGlyph(container);
70646
70853
  }
70647
70854
  }
70855
+ getNoteLine(note) {
70856
+ return this.accidentalHelper.getNoteSteps(note) / 2;
70857
+ }
70648
70858
  getNoteSteps(n) {
70649
70859
  return this.accidentalHelper.getNoteSteps(n);
70650
70860
  }
@@ -70701,43 +70911,15 @@
70701
70911
  /**
70702
70912
  * @internal
70703
70913
  */
70704
- class SlashTieGlyph extends TieGlyph {
70705
- startNote;
70706
- endNote;
70707
- constructor(startNote, endNote, forEnd = false) {
70708
- super(startNote.beat, endNote.beat, forEnd);
70709
- this.startNote = startNote;
70710
- this.endNote = endNote;
70711
- }
70712
- get _isLeftHandTap() {
70713
- return this.startNote === this.endNote;
70714
- }
70715
- getTieHeight(startX, startY, endX, endY) {
70716
- if (this._isLeftHandTap) {
70717
- return this.startNoteRenderer.smuflMetrics.tieHeight;
70718
- }
70719
- return super.getTieHeight(startX, startY, endX, endY);
70720
- }
70721
- getBeamDirection(_beat, _noteRenderer) {
70914
+ class SlashTieGlyph extends NoteTieGlyph {
70915
+ calculateTieDirection() {
70722
70916
  return BeamDirection.Down;
70723
70917
  }
70724
- static getBeamDirectionForNote(_note) {
70725
- return BeamDirection.Down;
70726
- }
70727
- getStartY() {
70728
- return this.startNoteRenderer.getNoteY(this.startNote, NoteYPosition.Center);
70729
- }
70730
- getEndY() {
70731
- return this.getStartY();
70732
- }
70733
- getStartX() {
70734
- if (this._isLeftHandTap) {
70735
- return this.getEndX() - this.renderer.smuflMetrics.leftHandTabTieWidth;
70736
- }
70737
- return this.startNoteRenderer.getNoteX(this.startNote, NoteXPosition.Right);
70918
+ getStartNotePosition() {
70919
+ return NoteXPosition.Right;
70738
70920
  }
70739
- getEndX() {
70740
- return this.endNoteRenderer.getNoteX(this.endNote, NoteXPosition.Left);
70921
+ getEndNotePosition() {
70922
+ return NoteXPosition.Left;
70741
70923
  }
70742
70924
  }
70743
70925
 
@@ -70752,12 +70934,12 @@
70752
70934
  return;
70753
70935
  }
70754
70936
  if (!this._tiedNoteTie && n.isTieOrigin && n.tieDestination.isVisible) {
70755
- const tie = new SlashTieGlyph(n, n.tieDestination, false);
70937
+ const tie = new SlashTieGlyph('slash.tie', n, n.tieDestination, false);
70756
70938
  this._tiedNoteTie = tie;
70757
70939
  this.addTie(tie);
70758
70940
  }
70759
70941
  if (!this._tiedNoteTie && n.isTieDestination) {
70760
- const tie = new SlashTieGlyph(n.tieOrigin, n, true);
70942
+ const tie = new SlashTieGlyph('slash.tie', n.tieOrigin, n, true);
70761
70943
  this._tiedNoteTie = tie;
70762
70944
  this.addTie(tie);
70763
70945
  }
@@ -70884,8 +71066,7 @@
70884
71066
  doLayout() {
70885
71067
  // create glyphs
70886
71068
  const sr = this.renderer;
70887
- const line = sr.getNoteLine();
70888
- const glyphY = sr.getLineY(line);
71069
+ const glyphY = sr.getLineY(0);
70889
71070
  if (this.container.beat.deadSlapped) {
70890
71071
  const deadSlapped = new DeadSlappedBeatGlyph();
70891
71072
  deadSlapped.renderer = this.renderer;
@@ -70918,22 +71099,23 @@
70918
71099
  //
70919
71100
  if (this.container.beat.dots > 0) {
70920
71101
  for (let i = 0; i < this.container.beat.dots; i++) {
70921
- this.addEffect(new AugmentationDotGlyph(0, sr.getLineY(sr.getNoteLine()) - sr.getLineHeight(0.5)));
71102
+ this.addEffect(new AugmentationDotGlyph(0, glyphY - sr.getLineHeight(0.5)));
70922
71103
  }
70923
71104
  }
70924
71105
  super.doLayout();
70925
71106
  if (this.container.beat.isEmpty) {
70926
- this.centerX = this.width / 2;
71107
+ this.onTimeX = this.width / 2;
70927
71108
  }
70928
71109
  else if (this.restGlyph) {
70929
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
71110
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
70930
71111
  }
70931
71112
  else if (this.noteHeads) {
70932
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
71113
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
70933
71114
  }
70934
71115
  else if (this.deadSlapped) {
70935
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
71116
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
70936
71117
  }
71118
+ this.middleX = this.onTimeX;
70937
71119
  }
70938
71120
  }
70939
71121
 
@@ -71000,7 +71182,7 @@
71000
71182
  this.registerOverflowTop(this.tupletSize);
71001
71183
  }
71002
71184
  }
71003
- getNoteLine() {
71185
+ getNoteLine(_note) {
71004
71186
  return 0;
71005
71187
  }
71006
71188
  getFlagTopY(beat, _direction) {
@@ -71337,77 +71519,6 @@
71337
71519
  }
71338
71520
  }
71339
71521
 
71340
- /**
71341
- * @internal
71342
- */
71343
- class TabSlurGlyph extends TabTieGlyph {
71344
- _direction;
71345
- _forSlide;
71346
- constructor(startNote, endNote, forSlide, forEnd = false) {
71347
- super(startNote, endNote, forEnd);
71348
- this._direction = TabTieGlyph.getBeamDirectionForNote(startNote);
71349
- this._forSlide = forSlide;
71350
- }
71351
- getTieHeight(startX, _startY, endX, _endY) {
71352
- return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight / 2;
71353
- }
71354
- tryExpand(startNote, endNote, forSlide, forEnd) {
71355
- // same type required
71356
- if (this._forSlide !== forSlide) {
71357
- return false;
71358
- }
71359
- if (this.forEnd !== forEnd) {
71360
- return false;
71361
- }
71362
- // same start and endbeat
71363
- if (this.startNote.beat.id !== startNote.beat.id) {
71364
- return false;
71365
- }
71366
- if (this.endNote.beat.id !== endNote.beat.id) {
71367
- return false;
71368
- }
71369
- // same draw direction
71370
- if (this._direction !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
71371
- return false;
71372
- }
71373
- // if we can expand, expand in correct direction
71374
- switch (this._direction) {
71375
- case BeamDirection.Up:
71376
- if (startNote.realValue > this.startNote.realValue) {
71377
- this.startNote = startNote;
71378
- this.startBeat = startNote.beat;
71379
- }
71380
- if (endNote.realValue > this.endNote.realValue) {
71381
- this.endNote = endNote;
71382
- this.endBeat = endNote.beat;
71383
- }
71384
- break;
71385
- case BeamDirection.Down:
71386
- if (startNote.realValue < this.startNote.realValue) {
71387
- this.startNote = startNote;
71388
- this.startBeat = startNote.beat;
71389
- }
71390
- if (endNote.realValue < this.endNote.realValue) {
71391
- this.endNote = endNote;
71392
- this.endBeat = endNote.beat;
71393
- }
71394
- break;
71395
- }
71396
- return true;
71397
- }
71398
- paint(cx, cy, canvas) {
71399
- const startNoteRenderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, this.startBeat.voice.bar);
71400
- const direction = this.getBeamDirection(this.startBeat, startNoteRenderer);
71401
- const slurId = `tab.slur.${this.startNote.beat.id}.${this.endNote.beat.id}.${direction}`;
71402
- const renderer = this.renderer;
71403
- const isSlurRendered = renderer.staff.getSharedLayoutData(slurId, false);
71404
- if (!isSlurRendered) {
71405
- renderer.staff.setSharedLayoutData(slurId, true);
71406
- super.paint(cx, cy, canvas);
71407
- }
71408
- }
71409
- }
71410
-
71411
71522
  /**
71412
71523
  * @internal
71413
71524
  */
@@ -71432,15 +71543,15 @@
71432
71543
  }
71433
71544
  const renderer = this.renderer;
71434
71545
  if (n.isTieOrigin && renderer.showTiedNotes && n.tieDestination.isVisible) {
71435
- const tie = new TabTieGlyph(n, n.tieDestination, false);
71546
+ const tie = new TabTieGlyph(`tab.tie.${n.id}`, n, n.tieDestination, false);
71436
71547
  this.addTie(tie);
71437
71548
  }
71438
71549
  if (n.isTieDestination && renderer.showTiedNotes) {
71439
- const tie = new TabTieGlyph(n.tieOrigin, n, true);
71550
+ const tie = new TabTieGlyph(`tab.tie.${n.tieOrigin.id}`, n.tieOrigin, n, true);
71440
71551
  this.addTie(tie);
71441
71552
  }
71442
71553
  if (n.isLeftHandTapped && !n.isHammerPullDestination) {
71443
- const tapSlur = new TabTieGlyph(n, n, false);
71554
+ const tapSlur = new TabTieGlyph(`tab.tie.leftHandTap.${n.id}`, n, n, false);
71444
71555
  this.addTie(tapSlur);
71445
71556
  }
71446
71557
  // start effect slur on first beat
@@ -71453,7 +71564,7 @@
71453
71564
  }
71454
71565
  }
71455
71566
  if (!expanded) {
71456
- const effectSlur = new TabSlurGlyph(n, n.effectSlurDestination, false, false);
71567
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.id}`, n, n.effectSlurDestination, false, false);
71457
71568
  this._effectSlurs.push(effectSlur);
71458
71569
  this.addTie(effectSlur);
71459
71570
  }
@@ -71468,7 +71579,7 @@
71468
71579
  }
71469
71580
  }
71470
71581
  if (!expanded) {
71471
- const effectSlur = new TabSlurGlyph(n.effectSlurOrigin, n, false, true);
71582
+ const effectSlur = new TabSlurGlyph(`tab.slur.effect.${n.effectSlurOrigin.id}`, n.effectSlurOrigin, n, false, true);
71472
71583
  this._effectSlurs.push(effectSlur);
71473
71584
  this.addTie(effectSlur);
71474
71585
  }
@@ -71887,7 +71998,7 @@
71887
71998
  }
71888
71999
  else {
71889
72000
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
71890
- const y = tabRenderer.getTabY(line);
72001
+ const y = tabRenderer.getLineY(line);
71891
72002
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71892
72003
  this.restGlyph = restGlyph;
71893
72004
  restGlyph.beat = this.container.beat;
@@ -71917,19 +72028,20 @@
71917
72028
  this.width = w;
71918
72029
  this.computedWidth = w;
71919
72030
  if (this.container.beat.isEmpty) {
71920
- this.centerX = this.width / 2;
72031
+ this.onTimeX = this.width / 2;
71921
72032
  }
71922
72033
  else if (this.restGlyph) {
71923
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
72034
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71924
72035
  }
71925
72036
  else if (this.noteNumbers) {
71926
- this.centerX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
72037
+ this.onTimeX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
71927
72038
  }
71928
72039
  else if (this.slash) {
71929
- this.centerX = this.slash.x + this.slash.width / 2;
72040
+ this.onTimeX = this.slash.x + this.slash.width / 2;
71930
72041
  }
72042
+ this.middleX = this.onTimeX;
71931
72043
  for (const g of centeredEffectGlyphs) {
71932
- g.x = this.centerX;
72044
+ g.x = this.onTimeX;
71933
72045
  }
71934
72046
  }
71935
72047
  updateBeamingHelper() {
@@ -71946,8 +72058,8 @@
71946
72058
  _createNoteGlyph(n) {
71947
72059
  const tr = this.renderer;
71948
72060
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
71949
- const l = n.beat.voice.bar.staff.tuning.length - n.string;
71950
- noteNumberGlyph.y = tr.getTabY(l);
72061
+ const l = tr.getNoteLine(n);
72062
+ noteNumberGlyph.y = tr.getLineY(l);
71951
72063
  noteNumberGlyph.renderer = this.renderer;
71952
72064
  noteNumberGlyph.doLayout();
71953
72065
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
@@ -72138,17 +72250,8 @@
72138
72250
  }
72139
72251
  return mode;
72140
72252
  }
72141
- /**
72142
- * Gets the relative y position of the given steps relative to first line.
72143
- * @param line the line of the particular string where 0 is the most top line
72144
- * @param correction
72145
- * @returns
72146
- */
72147
- getTabY(line) {
72148
- return super.getLineY(line);
72149
- }
72150
- getTabHeight(line) {
72151
- return super.getLineHeight(line);
72253
+ getNoteLine(note) {
72254
+ return this.bar.staff.tuning.length - note.string;
72152
72255
  }
72153
72256
  minString = Number.NaN;
72154
72257
  maxString = Number.NaN;
@@ -72237,7 +72340,7 @@
72237
72340
  if (this.isFirstOfLine) {
72238
72341
  const center = (this.bar.staff.tuning.length - 1) / 2;
72239
72342
  this.createStartSpacing();
72240
- this.addPreBeatGlyph(new TabClefGlyph(0, this.getTabY(center)));
72343
+ this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
72241
72344
  }
72242
72345
  // Time Signature
72243
72346
  if (this.showTimeSignature &&
@@ -72258,7 +72361,7 @@
72258
72361
  _createTimeSignatureGlyphs() {
72259
72362
  this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
72260
72363
  const lines = (this.bar.staff.tuning.length + 1) / 2 - 1;
72261
- this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getTabY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72364
+ this.addPreBeatGlyph(new TabTimeSignatureGlyph(0, this.getLineY(lines), this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, this.bar.masterBar.isFreeTime));
72262
72365
  }
72263
72366
  createVoiceGlyphs(v) {
72264
72367
  super.createVoiceGlyphs(v);