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

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.1639 (develop, build 1639)
2
+ * alphaTab v1.8.0-alpha.1643 (develop, build 1643)
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.1639';
213
- static date = '2025-12-09T02:16:01.440Z';
214
- static commit = 'af86866e4f9d89a3ccc34006d01473a549dbbe7f';
212
+ static version = '1.8.0-alpha.1643';
213
+ static date = '2025-12-13T02:07:47.380Z';
214
+ static commit = '6cdd7783a14244e9d7efb9f9b9dbad7a27b11eee';
215
215
  static print(print) {
216
216
  print(`alphaTab ${VersionInfo.version}`);
217
217
  print(`commit: ${VersionInfo.commit}`);
@@ -4721,6 +4721,24 @@
4721
4721
  static toArticulationId(plain) {
4722
4722
  return plain.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
4723
4723
  }
4724
+ static minBoundingBox(a, b) {
4725
+ if (Number.isNaN(a)) {
4726
+ return b;
4727
+ }
4728
+ else if (Number.isNaN(b)) {
4729
+ return a;
4730
+ }
4731
+ return a < b ? a : b;
4732
+ }
4733
+ static maxBoundingBox(a, b) {
4734
+ if (Number.isNaN(a)) {
4735
+ return b;
4736
+ }
4737
+ else if (Number.isNaN(b)) {
4738
+ return a;
4739
+ }
4740
+ return a > b ? a : b;
4741
+ }
4724
4742
  }
4725
4743
 
4726
4744
  /**
@@ -48303,6 +48321,39 @@
48303
48321
  }
48304
48322
  }
48305
48323
 
48324
+ /**
48325
+ * Lists the different position modes for {@link BarRendererBase.getBeatX}
48326
+ * @internal
48327
+ */
48328
+ var BeatXPosition;
48329
+ (function (BeatXPosition) {
48330
+ /**
48331
+ * Gets the pre-notes position which is located before the accidentals
48332
+ */
48333
+ BeatXPosition[BeatXPosition["PreNotes"] = 0] = "PreNotes";
48334
+ /**
48335
+ * Gets the on-notes position which is located after the accidentals but before the note heads.
48336
+ */
48337
+ BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
48338
+ /**
48339
+ * Gets the middle-notes position which is located after in the exact center of the note heads.
48340
+ */
48341
+ BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
48342
+ /**
48343
+ * Gets position of the stem for this beat
48344
+ */
48345
+ BeatXPosition[BeatXPosition["Stem"] = 3] = "Stem";
48346
+ /**
48347
+ * Get the post-notes position which is located at after the note heads.
48348
+ */
48349
+ BeatXPosition[BeatXPosition["PostNotes"] = 4] = "PostNotes";
48350
+ /**
48351
+ * Get the end-beat position which is located at the end of the beat. This position is almost
48352
+ * equal to the pre-notes position of the next beat.
48353
+ */
48354
+ BeatXPosition[BeatXPosition["EndBeat"] = 5] = "EndBeat";
48355
+ })(BeatXPosition || (BeatXPosition = {}));
48356
+
48306
48357
  /**
48307
48358
  * A glyph is a single symbol which can be added to a GlyphBarRenderer for automated
48308
48359
  * layouting and drawing of stacked symbols.
@@ -48330,198 +48381,6 @@
48330
48381
  }
48331
48382
  }
48332
48383
 
48333
- /**
48334
- * Effect-Glyphs implementing this public interface get notified
48335
- * as they are expanded over multiple beats.
48336
- * @internal
48337
- */
48338
- class EffectGlyph extends Glyph {
48339
- /**
48340
- * Gets or sets the beat where the glyph belongs to.
48341
- */
48342
- beat = null;
48343
- /**
48344
- * Gets or sets the next glyph of the same type in case
48345
- * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
48346
- */
48347
- nextGlyph = null;
48348
- /**
48349
- * Gets or sets the previous glyph of the same type in case
48350
- * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
48351
- */
48352
- previousGlyph = null;
48353
- constructor(x = 0, y = 0) {
48354
- super(x, y);
48355
- }
48356
- }
48357
-
48358
- /**
48359
- * @internal
48360
- */
48361
- class MusicFontGlyph extends EffectGlyph {
48362
- glyphScale = 0;
48363
- symbol;
48364
- center = false;
48365
- colorOverride;
48366
- offsetX = 0;
48367
- offsetY = 0;
48368
- constructor(x, y, glyphScale, symbol) {
48369
- super(x, y);
48370
- this.glyphScale = glyphScale;
48371
- this.symbol = symbol;
48372
- }
48373
- getBoundingBoxTop() {
48374
- const bBoxTop = this.renderer.smuflMetrics.glyphTop.get(this.symbol);
48375
- return this.y - this.offsetY - bBoxTop;
48376
- }
48377
- doLayout() {
48378
- this.width = this.renderer.smuflMetrics.glyphWidths.get(this.symbol) * this.glyphScale;
48379
- this.height = this.renderer.smuflMetrics.glyphHeights.get(this.symbol) * this.glyphScale;
48380
- }
48381
- paint(cx, cy, canvas) {
48382
- if (this.width === 0 && this.height === 0) {
48383
- return;
48384
- }
48385
- const c = canvas.color;
48386
- if (this.colorOverride) {
48387
- canvas.color = this.colorOverride;
48388
- }
48389
- canvas.fillMusicFontSymbol(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbol, this.center);
48390
- canvas.color = c;
48391
- }
48392
- }
48393
- /**
48394
- * @internal
48395
- */
48396
- class MusicFontTextGlyph extends EffectGlyph {
48397
- glyphScale = 0;
48398
- symbols;
48399
- center = false;
48400
- colorOverride;
48401
- offsetX = 0;
48402
- offsetY = 0;
48403
- constructor(x, y, glyphScale, symbols) {
48404
- super(x, y);
48405
- this.glyphScale = glyphScale;
48406
- this.symbols = symbols;
48407
- }
48408
- getBoundingBoxTop() {
48409
- let bBoxTop = 0;
48410
- for (let i = 0; i < this.symbols.length; i++) {
48411
- const gTop = this.renderer.smuflMetrics.glyphTop.get(this.symbols[i]);
48412
- if (i === 0 || gTop < bBoxTop) {
48413
- bBoxTop = gTop;
48414
- }
48415
- }
48416
- return this.y - this.offsetY - bBoxTop;
48417
- }
48418
- doLayout() {
48419
- this.width = 0;
48420
- this.height = 0;
48421
- for (let i = 0; i < this.symbols.length; i++) {
48422
- const gWidth = this.renderer.smuflMetrics.glyphWidths.get(this.symbols[i]) * this.glyphScale;
48423
- const gHeight = this.renderer.smuflMetrics.glyphHeights.get(this.symbols[i]) * this.glyphScale;
48424
- if (i === 0 || gWidth > this.width) {
48425
- this.width = gWidth;
48426
- }
48427
- if (i === 0 || gHeight > this.height) {
48428
- this.height = gHeight;
48429
- }
48430
- }
48431
- }
48432
- paint(cx, cy, canvas) {
48433
- if (this.width === 0 && this.height === 0) {
48434
- return;
48435
- }
48436
- const c = canvas.color;
48437
- if (this.colorOverride) {
48438
- canvas.color = this.colorOverride;
48439
- }
48440
- canvas.fillMusicFontSymbols(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbols, this.center);
48441
- canvas.color = c;
48442
- }
48443
- }
48444
-
48445
- /**
48446
- * @internal
48447
- */
48448
- class NoteHeadGlyph extends MusicFontGlyph {
48449
- // TODO: SmuFL
48450
- static GraceScale = 0.75;
48451
- centerOnStem = false;
48452
- constructor(x, y, duration, isGrace) {
48453
- super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, NoteHeadGlyph.getSymbol(duration));
48454
- }
48455
- paint(cx, cy, canvas) {
48456
- if (this.centerOnStem) {
48457
- this.center = true;
48458
- }
48459
- super.paint(cx, cy, canvas);
48460
- }
48461
- static getSymbol(duration) {
48462
- switch (duration) {
48463
- case Duration.QuadrupleWhole:
48464
- return MusicFontSymbol.NoteheadDoubleWholeSquare;
48465
- case Duration.DoubleWhole:
48466
- return MusicFontSymbol.NoteheadDoubleWhole;
48467
- case Duration.Whole:
48468
- return MusicFontSymbol.NoteheadWhole;
48469
- case Duration.Half:
48470
- return MusicFontSymbol.NoteheadHalf;
48471
- default:
48472
- return MusicFontSymbol.NoteheadBlack;
48473
- }
48474
- }
48475
- }
48476
-
48477
- /**
48478
- * @internal
48479
- */
48480
- class FlagGlyph extends MusicFontGlyph {
48481
- constructor(x, y, duration, direction, isGrace) {
48482
- super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
48483
- }
48484
- static getSymbol(duration, direction, isGrace) {
48485
- if (isGrace) {
48486
- duration = Duration.Eighth;
48487
- }
48488
- if (direction === BeamDirection.Up) {
48489
- switch (duration) {
48490
- case Duration.Eighth:
48491
- return MusicFontSymbol.Flag8thUp;
48492
- case Duration.Sixteenth:
48493
- return MusicFontSymbol.Flag16thUp;
48494
- case Duration.ThirtySecond:
48495
- return MusicFontSymbol.Flag32ndUp;
48496
- case Duration.SixtyFourth:
48497
- return MusicFontSymbol.Flag64thUp;
48498
- case Duration.OneHundredTwentyEighth:
48499
- return MusicFontSymbol.Flag128thUp;
48500
- case Duration.TwoHundredFiftySixth:
48501
- return MusicFontSymbol.Flag256thUp;
48502
- default:
48503
- return MusicFontSymbol.Flag8thUp;
48504
- }
48505
- }
48506
- switch (duration) {
48507
- case Duration.Eighth:
48508
- return MusicFontSymbol.Flag8thDown;
48509
- case Duration.Sixteenth:
48510
- return MusicFontSymbol.Flag16thDown;
48511
- case Duration.ThirtySecond:
48512
- return MusicFontSymbol.Flag32ndDown;
48513
- case Duration.SixtyFourth:
48514
- return MusicFontSymbol.Flag64thDown;
48515
- case Duration.OneHundredTwentyEighth:
48516
- return MusicFontSymbol.Flag128thDown;
48517
- case Duration.TwoHundredFiftySixth:
48518
- return MusicFontSymbol.Flag128thDown;
48519
- default:
48520
- return MusicFontSymbol.Flag8thDown;
48521
- }
48522
- }
48523
- }
48524
-
48525
48384
  /**
48526
48385
  * @internal
48527
48386
  */
@@ -48533,7 +48392,7 @@
48533
48392
  onNotes;
48534
48393
  minWidth = 0;
48535
48394
  get onTimeX() {
48536
- return this.onNotes.x + this.onNotes.centerX;
48395
+ return this.onNotes.x + this.onNotes.onTimeX;
48537
48396
  }
48538
48397
  constructor(beat, voiceContainer) {
48539
48398
  super(0, 0);
@@ -48548,28 +48407,20 @@
48548
48407
  this.renderer.registerTie(tie);
48549
48408
  }
48550
48409
  getBoundingBoxTop() {
48551
- return Math.min(this.preNotes.getBoundingBoxTop(), this.onNotes.getBoundingBoxTop());
48410
+ return ModelUtils.minBoundingBox(this.preNotes.getBoundingBoxTop(), this.onNotes.getBoundingBoxTop());
48552
48411
  }
48553
48412
  getBoundingBoxBottom() {
48554
- return Math.max(this.preNotes.getBoundingBoxBottom(), this.onNotes.getBoundingBoxBottom());
48413
+ return ModelUtils.maxBoundingBox(this.preNotes.getBoundingBoxBottom(), this.onNotes.getBoundingBoxBottom());
48555
48414
  }
48556
48415
  drawBeamHelperAsFlags(helper) {
48557
48416
  return helper.hasFlag(false, undefined);
48558
48417
  }
48418
+ get postBeatStretch() {
48419
+ return this.onNotes.computedWidth - this.onNotes.onTimeX;
48420
+ }
48559
48421
  registerLayoutingInfo(layoutings) {
48560
- const preBeatStretch = this.preNotes.computedWidth + this.onNotes.centerX;
48561
- let postBeatStretch = this.onNotes.computedWidth - this.onNotes.centerX;
48562
- // make space for flag
48563
- const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48564
- if (this.beat.graceType !== GraceType.None) {
48565
- // always use flag size as spacing on grace notes
48566
- postBeatStretch +=
48567
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
48568
- }
48569
- else if (helper && this.drawBeamHelperAsFlags(helper)) {
48570
- postBeatStretch +=
48571
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
48572
- }
48422
+ const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
48423
+ let postBeatStretch = this.postBeatStretch;
48573
48424
  for (const tie of this._ties) {
48574
48425
  const tg = tie;
48575
48426
  postBeatStretch += tg.width;
@@ -48584,7 +48435,6 @@
48584
48435
  });
48585
48436
  }
48586
48437
  applyLayoutingInfo(_info) {
48587
- this.onNotes.updateBeamingHelper();
48588
48438
  this.updateWidth();
48589
48439
  }
48590
48440
  doLayout() {
@@ -48596,7 +48446,6 @@
48596
48446
  this.onNotes.renderer = this.renderer;
48597
48447
  this.onNotes.container = this;
48598
48448
  this.onNotes.doLayout();
48599
- this.onNotes.updateBeamingHelper();
48600
48449
  let i = this.beat.notes.length - 1;
48601
48450
  while (i >= 0) {
48602
48451
  this.createTies(this.beat.notes[i--]);
@@ -48605,15 +48454,6 @@
48605
48454
  }
48606
48455
  updateWidth() {
48607
48456
  this.minWidth = this.preNotes.width + this.onNotes.width;
48608
- if (!this.beat.isRest) {
48609
- if (this.onNotes.beamingHelper.beats.length === 1) {
48610
- // make space for flag
48611
- if (this.beat.duration >= Duration.Eighth) {
48612
- const symbol = FlagGlyph.getSymbol(this.beat.duration, this.onNotes.beamingHelper.direction, this.beat.graceType !== GraceType.None);
48613
- this.minWidth += this.renderer.smuflMetrics.glyphWidths.get(symbol);
48614
- }
48615
- }
48616
- }
48617
48457
  let tieWidth = 0;
48618
48458
  for (const tie of this._ties) {
48619
48459
  const tg = tie;
@@ -48624,10 +48464,6 @@
48624
48464
  this.minWidth += tieWidth;
48625
48465
  this.width = this.minWidth;
48626
48466
  }
48627
- scaleToWidth(beatWidth) {
48628
- this.onNotes.updateBeamingHelper();
48629
- this.width = beatWidth;
48630
- }
48631
48467
  createTies(_n) {
48632
48468
  }
48633
48469
  static getGroupId(beat) {
@@ -48637,8 +48473,11 @@
48637
48473
  // var c = canvas.color;
48638
48474
  // canvas.color = Color.random();
48639
48475
  // canvas.fillRect(cx + this.x, cy + this.y + this.preNotes.getBoundingBoxTop(), this.width, this.renderer.height);
48640
- // canvas.color = Color.random();
48641
48476
  // canvas.fillRect(cx + this.x, cy + this.y + this.onNotes.getBoundingBoxTop(), this.width, this.renderer.height);
48477
+ // canvas.color = Color.random();
48478
+ // const top = this.getBoundingBoxTop();
48479
+ // const bottom = this.getBoundingBoxBottom();
48480
+ // canvas.fillRect(cx + this.x, cy + this.y + top, this.width, bottom-top);
48642
48481
  // canvas.color = c;
48643
48482
  // var c = canvas.color;
48644
48483
  // var ta = canvas.textAlign;
@@ -48701,7 +48540,7 @@
48701
48540
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48702
48541
  beatBoundings.realBounds.w = this.width;
48703
48542
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48704
- beatBoundings.onNotesX = cx + this.x + this.onNotes.centerX;
48543
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48705
48544
  }
48706
48545
  else {
48707
48546
  beatBoundings.visualBounds = new Bounds();
@@ -48717,7 +48556,7 @@
48717
48556
  }
48718
48557
  let visualEndX = 0;
48719
48558
  if (!this.onNotes.isEmpty) {
48720
- visualEndX = cx + this.x + this.onNotes.x + this.onNotes.width;
48559
+ visualEndX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX + this.postBeatStretch;
48721
48560
  }
48722
48561
  else if (!this.preNotes.isEmpty) {
48723
48562
  visualEndX = cx + this.x + this.preNotes.x + this.preNotes.width;
@@ -48726,12 +48565,6 @@
48726
48565
  visualEndX = cx + this.x + this.width;
48727
48566
  }
48728
48567
  beatBoundings.visualBounds.w = visualEndX - beatBoundings.visualBounds.x;
48729
- const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48730
- if ((helper && this.drawBeamHelperAsFlags(helper)) || this.beat.graceType !== GraceType.None) {
48731
- beatBoundings.visualBounds.w +=
48732
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) *
48733
- (this.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1);
48734
- }
48735
48568
  beatBoundings.visualBounds.y = barBounds.visualBounds.y;
48736
48569
  beatBoundings.visualBounds.h = barBounds.visualBounds.h;
48737
48570
  beatBoundings.realBounds = new Bounds();
@@ -48739,13 +48572,33 @@
48739
48572
  beatBoundings.realBounds.y = barBounds.realBounds.y;
48740
48573
  beatBoundings.realBounds.w = this.width;
48741
48574
  beatBoundings.realBounds.h = barBounds.realBounds.h;
48742
- beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.centerX;
48575
+ beatBoundings.onNotesX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX;
48743
48576
  }
48744
48577
  barBounds.addBeat(beatBoundings);
48745
48578
  if (this.renderer.settings.core.includeNoteBounds) {
48746
48579
  this.onNotes.buildBoundingsLookup(beatBoundings, cx + this.x, cy + this.y);
48747
48580
  }
48748
48581
  }
48582
+ getBeatX(requestedPosition, useSharedSizes = false) {
48583
+ switch (requestedPosition) {
48584
+ case BeatXPosition.PreNotes:
48585
+ return this.preNotes.x;
48586
+ case BeatXPosition.OnNotes:
48587
+ return this.onNotes.x;
48588
+ case BeatXPosition.MiddleNotes:
48589
+ return this.onNotes.x + this.onNotes.middleX;
48590
+ case BeatXPosition.Stem:
48591
+ return this.onNotes.x + this.onNotes.stemX;
48592
+ case BeatXPosition.PostNotes:
48593
+ const onNoteSize = useSharedSizes
48594
+ ? (this.renderer.layoutingInfo.getBeatSizes(this.beat)?.onBeatSize ?? this.onNotes.width)
48595
+ : this.onNotes.width;
48596
+ return this.onNotes.x + onNoteSize;
48597
+ case BeatXPosition.EndBeat:
48598
+ return this.width;
48599
+ }
48600
+ return this.preNotes.x;
48601
+ }
48749
48602
  }
48750
48603
 
48751
48604
  /**
@@ -54364,6 +54217,7 @@
54364
54217
  class ScalableHtmlElementContainer extends HtmlElementContainer {
54365
54218
  _xscale;
54366
54219
  _yscale;
54220
+ centerAtPosition = false;
54367
54221
  constructor(element, xscale, yscale) {
54368
54222
  super(element);
54369
54223
  this._xscale = xscale;
@@ -54405,7 +54259,11 @@
54405
54259
  else {
54406
54260
  h = h / this._yscale;
54407
54261
  }
54408
- this.element.style.transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54262
+ let transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
54263
+ if (this.centerAtPosition) {
54264
+ transform += ` translateX(-50%)`;
54265
+ }
54266
+ this.element.style.transform = transform;
54409
54267
  this.element.style.transformOrigin = 'top left';
54410
54268
  this.lastBounds.x = x;
54411
54269
  this.lastBounds.y = y;
@@ -55266,6 +55124,7 @@
55266
55124
  beatCursor.style.willChange = 'transform';
55267
55125
  beatCursorContainer.width = 3;
55268
55126
  beatCursorContainer.height = 1;
55127
+ beatCursorContainer.centerAtPosition = true;
55269
55128
  beatCursorContainer.setBounds(0, 0, 1, 1);
55270
55129
  // add cursors to UI
55271
55130
  element.insertBefore(cursorWrapper, element.firstChild);
@@ -56498,6 +56357,31 @@
56498
56357
  EffectBarGlyphSizing[EffectBarGlyphSizing["FullBar"] = 5] = "FullBar";
56499
56358
  })(EffectBarGlyphSizing || (EffectBarGlyphSizing = {}));
56500
56359
 
56360
+ /**
56361
+ * Effect-Glyphs implementing this public interface get notified
56362
+ * as they are expanded over multiple beats.
56363
+ * @internal
56364
+ */
56365
+ class EffectGlyph extends Glyph {
56366
+ /**
56367
+ * Gets or sets the beat where the glyph belongs to.
56368
+ */
56369
+ beat = null;
56370
+ /**
56371
+ * Gets or sets the next glyph of the same type in case
56372
+ * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
56373
+ */
56374
+ nextGlyph = null;
56375
+ /**
56376
+ * Gets or sets the previous glyph of the same type in case
56377
+ * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
56378
+ */
56379
+ previousGlyph = null;
56380
+ constructor(x = 0, y = 0) {
56381
+ super(x, y);
56382
+ }
56383
+ }
56384
+
56501
56385
  /**
56502
56386
  * @internal
56503
56387
  */
@@ -56625,39 +56509,6 @@
56625
56509
  }
56626
56510
  }
56627
56511
 
56628
- /**
56629
- * Lists the different position modes for {@link BarRendererBase.getBeatX}
56630
- * @internal
56631
- */
56632
- var BeatXPosition;
56633
- (function (BeatXPosition) {
56634
- /**
56635
- * Gets the pre-notes position which is located before the accidentals
56636
- */
56637
- BeatXPosition[BeatXPosition["PreNotes"] = 0] = "PreNotes";
56638
- /**
56639
- * Gets the on-notes position which is located after the accidentals but before the note heads.
56640
- */
56641
- BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
56642
- /**
56643
- * Gets the middle-notes position which is located after in the middle the note heads.
56644
- */
56645
- BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
56646
- /**
56647
- * Gets position of the stem for this beat
56648
- */
56649
- BeatXPosition[BeatXPosition["Stem"] = 3] = "Stem";
56650
- /**
56651
- * Get the post-notes position which is located at after the note heads.
56652
- */
56653
- BeatXPosition[BeatXPosition["PostNotes"] = 4] = "PostNotes";
56654
- /**
56655
- * Get the end-beat position which is located at the end of the beat. This position is almost
56656
- * equal to the pre-notes position of the next beat.
56657
- */
56658
- BeatXPosition[BeatXPosition["EndBeat"] = 5] = "EndBeat";
56659
- })(BeatXPosition || (BeatXPosition = {}));
56660
-
56661
56512
  /**
56662
56513
  * @internal
56663
56514
  */
@@ -57256,6 +57107,93 @@
57256
57107
  }
57257
57108
  }
57258
57109
 
57110
+ /**
57111
+ * @internal
57112
+ */
57113
+ class MusicFontGlyph extends EffectGlyph {
57114
+ glyphScale = 0;
57115
+ symbol;
57116
+ center = false;
57117
+ colorOverride;
57118
+ offsetX = 0;
57119
+ offsetY = 0;
57120
+ constructor(x, y, glyphScale, symbol) {
57121
+ super(x, y);
57122
+ this.glyphScale = glyphScale;
57123
+ this.symbol = symbol;
57124
+ }
57125
+ getBoundingBoxTop() {
57126
+ const bBoxTop = this.renderer.smuflMetrics.glyphTop.get(this.symbol);
57127
+ return this.y - this.offsetY - bBoxTop;
57128
+ }
57129
+ doLayout() {
57130
+ this.width = this.renderer.smuflMetrics.glyphWidths.get(this.symbol) * this.glyphScale;
57131
+ this.height = this.renderer.smuflMetrics.glyphHeights.get(this.symbol) * this.glyphScale;
57132
+ }
57133
+ paint(cx, cy, canvas) {
57134
+ if (this.width === 0 && this.height === 0) {
57135
+ return;
57136
+ }
57137
+ const c = canvas.color;
57138
+ if (this.colorOverride) {
57139
+ canvas.color = this.colorOverride;
57140
+ }
57141
+ canvas.fillMusicFontSymbol(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbol, this.center);
57142
+ canvas.color = c;
57143
+ }
57144
+ }
57145
+ /**
57146
+ * @internal
57147
+ */
57148
+ class MusicFontTextGlyph extends EffectGlyph {
57149
+ glyphScale = 0;
57150
+ symbols;
57151
+ center = false;
57152
+ colorOverride;
57153
+ offsetX = 0;
57154
+ offsetY = 0;
57155
+ constructor(x, y, glyphScale, symbols) {
57156
+ super(x, y);
57157
+ this.glyphScale = glyphScale;
57158
+ this.symbols = symbols;
57159
+ }
57160
+ getBoundingBoxTop() {
57161
+ let bBoxTop = 0;
57162
+ for (let i = 0; i < this.symbols.length; i++) {
57163
+ const gTop = this.renderer.smuflMetrics.glyphTop.get(this.symbols[i]);
57164
+ if (i === 0 || gTop < bBoxTop) {
57165
+ bBoxTop = gTop;
57166
+ }
57167
+ }
57168
+ return this.y - this.offsetY - bBoxTop;
57169
+ }
57170
+ doLayout() {
57171
+ this.width = 0;
57172
+ this.height = 0;
57173
+ for (let i = 0; i < this.symbols.length; i++) {
57174
+ const gWidth = this.renderer.smuflMetrics.glyphWidths.get(this.symbols[i]) * this.glyphScale;
57175
+ const gHeight = this.renderer.smuflMetrics.glyphHeights.get(this.symbols[i]) * this.glyphScale;
57176
+ if (i === 0 || gWidth > this.width) {
57177
+ this.width = gWidth;
57178
+ }
57179
+ if (i === 0 || gHeight > this.height) {
57180
+ this.height = gHeight;
57181
+ }
57182
+ }
57183
+ }
57184
+ paint(cx, cy, canvas) {
57185
+ if (this.width === 0 && this.height === 0) {
57186
+ return;
57187
+ }
57188
+ const c = canvas.color;
57189
+ if (this.colorOverride) {
57190
+ canvas.color = this.colorOverride;
57191
+ }
57192
+ canvas.fillMusicFontSymbols(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbols, this.center);
57193
+ canvas.color = c;
57194
+ }
57195
+ }
57196
+
57259
57197
  /**
57260
57198
  * @internal
57261
57199
  */
@@ -57494,17 +57432,6 @@
57494
57432
  }
57495
57433
  }
57496
57434
 
57497
- /**
57498
- * This simple glyph allows to put an empty region in to a BarRenderer.
57499
- * @internal
57500
- */
57501
- class SpacingGlyph extends Glyph {
57502
- constructor(x, y, width) {
57503
- super(x, y);
57504
- this.width = width;
57505
- }
57506
- }
57507
-
57508
57435
  /**
57509
57436
  * This glyph allows to group several other glyphs to be
57510
57437
  * drawn at the same x position
@@ -57520,34 +57447,20 @@
57520
57447
  const glyphs = this.glyphs;
57521
57448
  if (glyphs) {
57522
57449
  for (const g of glyphs) {
57523
- // only count real visual glyphs
57524
- if (g instanceof SpacingGlyph) {
57525
- continue;
57526
- }
57527
- const gTop = g.getBoundingBoxTop();
57528
- if (Number.isNaN(top) || gTop < top) {
57529
- top = gTop;
57530
- }
57450
+ top = ModelUtils.minBoundingBox(top, g.getBoundingBoxTop());
57531
57451
  }
57532
57452
  }
57533
- return Number.isNaN(top) ? this.y : top;
57453
+ return top;
57534
57454
  }
57535
57455
  getBoundingBoxBottom() {
57536
57456
  let bottom = Number.NaN;
57537
57457
  const glyphs = this.glyphs;
57538
57458
  if (glyphs) {
57539
57459
  for (const g of glyphs) {
57540
- // only count real visual glyphs
57541
- if (g instanceof SpacingGlyph) {
57542
- continue;
57543
- }
57544
- const gBottom = g.getBoundingBoxBottom();
57545
- if (Number.isNaN(bottom) || gBottom > bottom) {
57546
- bottom = gBottom;
57547
- }
57460
+ bottom = ModelUtils.maxBoundingBox(bottom, g.getBoundingBoxBottom());
57548
57461
  }
57549
57462
  }
57550
- return Number.isNaN(bottom) ? this.y + this.height : bottom;
57463
+ return bottom;
57551
57464
  }
57552
57465
  doLayout() {
57553
57466
  if (!this.glyphs || this.glyphs.length === 0) {
@@ -57963,6 +57876,38 @@
57963
57876
  }
57964
57877
  }
57965
57878
 
57879
+ /**
57880
+ * @internal
57881
+ */
57882
+ class NoteHeadGlyph extends MusicFontGlyph {
57883
+ // TODO: SmuFL
57884
+ static GraceScale = 0.75;
57885
+ centerOnStem = false;
57886
+ constructor(x, y, duration, isGrace) {
57887
+ super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, NoteHeadGlyph.getSymbol(duration));
57888
+ }
57889
+ paint(cx, cy, canvas) {
57890
+ if (this.centerOnStem) {
57891
+ this.center = true;
57892
+ }
57893
+ super.paint(cx, cy, canvas);
57894
+ }
57895
+ static getSymbol(duration) {
57896
+ switch (duration) {
57897
+ case Duration.QuadrupleWhole:
57898
+ return MusicFontSymbol.NoteheadDoubleWholeSquare;
57899
+ case Duration.DoubleWhole:
57900
+ return MusicFontSymbol.NoteheadDoubleWhole;
57901
+ case Duration.Whole:
57902
+ return MusicFontSymbol.NoteheadWhole;
57903
+ case Duration.Half:
57904
+ return MusicFontSymbol.NoteheadHalf;
57905
+ default:
57906
+ return MusicFontSymbol.NoteheadBlack;
57907
+ }
57908
+ }
57909
+ }
57910
+
57966
57911
  /**
57967
57912
  * @internal
57968
57913
  */
@@ -59581,12 +59526,12 @@
59581
59526
  // of the next glyph
59582
59527
  if (i > 0) {
59583
59528
  const beatWidth = currentBeatGlyph.x - beatGlyphs[i - 1].x;
59584
- beatGlyphs[i - 1].scaleToWidth(beatWidth);
59529
+ beatGlyphs[i - 1].width = beatWidth;
59585
59530
  }
59586
59531
  // for the last glyph size based on the full width
59587
59532
  if (i === j - 1) {
59588
59533
  const beatWidth = this.width - beatGlyphs[beatGlyphs.length - 1].x;
59589
- currentBeatGlyph.scaleToWidth(beatWidth);
59534
+ currentBeatGlyph.width = beatWidth;
59590
59535
  }
59591
59536
  }
59592
59537
  }
@@ -61286,7 +61231,7 @@
61286
61231
  if (!masterBarBoundsLookup.has(renderer.bar.masterBar.index)) {
61287
61232
  masterBarBounds = new MasterBarBounds();
61288
61233
  masterBarBounds.index = renderer.bar.masterBar.index;
61289
- masterBarBounds.isFirstOfLine = renderer.isFirstOfLine;
61234
+ masterBarBounds.isFirstOfLine = renderer.isFirstOfStaff;
61290
61235
  masterBarBounds.realBounds = new Bounds();
61291
61236
  masterBarBounds.realBounds.x = x + renderer.x;
61292
61237
  masterBarBounds.realBounds.y = realTop;
@@ -61818,10 +61763,9 @@
61818
61763
  * @internal
61819
61764
  */
61820
61765
  class BeatOnNoteGlyphBase extends BeatGlyphBase {
61821
- beamingHelper;
61822
- centerX = 0;
61823
- updateBeamingHelper() {
61824
- }
61766
+ onTimeX = 0;
61767
+ middleX = 0;
61768
+ stemX = 0;
61825
61769
  buildBoundingsLookup(_beatBounds, _cx, _cy) {
61826
61770
  }
61827
61771
  getNoteX(_note, _requestedPosition) {
@@ -61963,14 +61907,6 @@
61963
61907
  }
61964
61908
  }
61965
61909
 
61966
- /**
61967
- * @internal
61968
- */
61969
- class BeatLinePositions {
61970
- staffId = '';
61971
- up = 0;
61972
- down = 0;
61973
- }
61974
61910
  /**
61975
61911
  * @internal
61976
61912
  */
@@ -62001,10 +61937,7 @@
62001
61937
  */
62002
61938
  class BeamingHelper {
62003
61939
  _staff;
62004
- _beatLineXPositions = new Map();
62005
61940
  _renderer;
62006
- _firstNonRestBeat = null;
62007
- _lastNonRestBeat = null;
62008
61941
  voice = null;
62009
61942
  beats = [];
62010
61943
  shortestDuration = Duration.QuadrupleWhole;
@@ -62014,6 +61947,7 @@
62014
61947
  */
62015
61948
  hasTuplet = false;
62016
61949
  slashBeats = [];
61950
+ restBeats = [];
62017
61951
  lowestNoteInHelper = null;
62018
61952
  _lowestNoteCompareValueInHelper = -1;
62019
61953
  highestNoteInHelper = null;
@@ -62021,25 +61955,21 @@
62021
61955
  invertBeamDirection = false;
62022
61956
  preferredBeamDirection = null;
62023
61957
  graceType = GraceType.None;
62024
- minRestSteps = null;
62025
- beatOfMinRestSteps = null;
62026
- maxRestSteps = null;
62027
- beatOfMaxRestSteps = null;
62028
61958
  get isRestBeamHelper() {
62029
61959
  return this.beats.length === 1 && this.beats[0].isRest;
62030
61960
  }
62031
- hasLine(forceFlagOnSingleBeat, beat) {
62032
- return ((forceFlagOnSingleBeat && this._beatHasLine(beat)) ||
62033
- (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasLine(beat)));
61961
+ hasStem(forceFlagOnSingleBeat, beat) {
61962
+ return ((forceFlagOnSingleBeat && this._beatHasStem(beat)) ||
61963
+ (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasStem(beat)));
62034
61964
  }
62035
- _beatHasLine(beat) {
61965
+ _beatHasStem(beat) {
62036
61966
  return beat.duration > Duration.Whole;
62037
61967
  }
62038
61968
  hasFlag(forceFlagOnSingleBeat, beat) {
62039
- return ((forceFlagOnSingleBeat && this._beatHasFlag(beat)) ||
62040
- (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasFlag(this.beats[0])));
61969
+ return ((forceFlagOnSingleBeat && BeamingHelper.beatHasFlag(beat)) ||
61970
+ (!forceFlagOnSingleBeat && this.beats.length === 1 && BeamingHelper.beatHasFlag(this.beats[0])));
62041
61971
  }
62042
- _beatHasFlag(beat) {
61972
+ static beatHasFlag(beat) {
62043
61973
  return (!beat.deadSlapped && !beat.isRest && (beat.duration > Duration.Quarter || beat.graceType !== GraceType.None));
62044
61974
  }
62045
61975
  constructor(staff, renderer) {
@@ -62047,38 +61977,12 @@
62047
61977
  this._renderer = renderer;
62048
61978
  this.beats = [];
62049
61979
  }
62050
- getBeatLineX(beat, direction) {
62051
- direction = direction ?? this.direction;
62052
- if (this.hasBeatLineX(beat)) {
62053
- if (direction === BeamDirection.Up) {
62054
- return this._beatLineXPositions.get(beat.index).up;
62055
- }
62056
- return this._beatLineXPositions.get(beat.index).down;
62057
- }
62058
- return 0;
62059
- }
62060
- hasBeatLineX(beat) {
62061
- return this._beatLineXPositions.has(beat.index);
62062
- }
62063
- registerBeatLineX(staffId, beat, up, down) {
62064
- const positions = this._getOrCreateBeatPositions(beat);
62065
- positions.staffId = staffId;
62066
- positions.up = up;
62067
- positions.down = down;
61980
+ alignWithBeats() {
62068
61981
  for (const v of this.drawingInfos.values()) {
62069
- if (v.startBeat === beat) {
62070
- v.startX = this.getBeatLineX(beat);
62071
- }
62072
- else if (v.endBeat === beat) {
62073
- v.endX = this.getBeatLineX(beat);
62074
- }
62075
- }
62076
- }
62077
- _getOrCreateBeatPositions(beat) {
62078
- if (!this._beatLineXPositions.has(beat.index)) {
62079
- this._beatLineXPositions.set(beat.index, new BeatLinePositions());
61982
+ v.startX = this._renderer.getBeatX(v.startBeat, BeatXPosition.Stem);
61983
+ v.endX = this._renderer.getBeatX(v.endBeat, BeatXPosition.Stem);
61984
+ this.drawingInfos.clear();
62080
61985
  }
62081
- return this._beatLineXPositions.get(beat.index);
62082
61986
  }
62083
61987
  direction = BeamDirection.Up;
62084
61988
  finish() {
@@ -62144,36 +62048,6 @@
62144
62048
  }
62145
62049
  return [0, 0];
62146
62050
  }
62147
- /**
62148
- * Registers a rest beat within the accidental helper so the rest
62149
- * symbol is considered properly during beaming.
62150
- * @param beat The rest beat.
62151
- * @param steps The steps on which the rest symbol is placed
62152
- */
62153
- applyRest(beat, steps) {
62154
- // do not accept rests after the last beat which has notes
62155
- if ((this._lastNonRestBeat && beat.index >= this._lastNonRestBeat.index) ||
62156
- (this._firstNonRestBeat && beat.index <= this._firstNonRestBeat.index)) {
62157
- return;
62158
- }
62159
- // correct the line of the glyph to a note which would
62160
- // be placed at the upper / lower end of the glyph.
62161
- let aboveRest = steps;
62162
- let belowRest = steps;
62163
- const offsets = BeamingHelper.computeLineHeightsForRest(beat.duration);
62164
- aboveRest -= offsets[0];
62165
- belowRest += offsets[1];
62166
- const minRestSteps = this.minRestSteps;
62167
- const maxRestSteps = this.maxRestSteps;
62168
- if (minRestSteps === null || minRestSteps > aboveRest) {
62169
- this.minRestSteps = aboveRest;
62170
- this.beatOfMinRestSteps = beat;
62171
- }
62172
- if (maxRestSteps === null || maxRestSteps < belowRest) {
62173
- this.maxRestSteps = belowRest;
62174
- this.beatOfMaxRestSteps = beat;
62175
- }
62176
- }
62177
62051
  _invert(direction) {
62178
62052
  if (!this.invertBeamDirection) {
62179
62053
  return direction;
@@ -62237,14 +62111,13 @@
62237
62111
  if (this.shortestDuration < beat.duration) {
62238
62112
  this.shortestDuration = beat.duration;
62239
62113
  }
62240
- if (!this._firstNonRestBeat) {
62241
- this._firstNonRestBeat = beat;
62242
- }
62243
- this._lastNonRestBeat = beat;
62244
62114
  }
62245
62115
  else if (this.beats.length === 0) {
62246
62116
  this.beats.push(beat);
62247
62117
  }
62118
+ else {
62119
+ this.restBeats.push(beat);
62120
+ }
62248
62121
  if (beat.slashed) {
62249
62122
  this.slashBeats.push(beat);
62250
62123
  }
@@ -62353,19 +62226,6 @@
62353
62226
  get beatOfHighestNote() {
62354
62227
  return this.highestNoteInHelper.beat;
62355
62228
  }
62356
- /**
62357
- * Returns whether the the position of the given beat, was registered by the staff of the given ID
62358
- * @param staffId
62359
- * @param beat
62360
- * @returns
62361
- */
62362
- isPositionFrom(staffId, beat) {
62363
- if (!this._beatLineXPositions.has(beat.index)) {
62364
- return true;
62365
- }
62366
- return (this._beatLineXPositions.get(beat.index).staffId === staffId ||
62367
- !this._beatLineXPositions.get(beat.index).staffId);
62368
- }
62369
62229
  drawingInfos = new Map();
62370
62230
  }
62371
62231
 
@@ -62517,8 +62377,8 @@
62517
62377
  */
62518
62378
  class BarHelpers {
62519
62379
  _renderer;
62380
+ _beamHelperLookup = new Map();
62520
62381
  beamHelpers = [];
62521
- beamHelperLookup = [];
62522
62382
  collisionHelper;
62523
62383
  preferredBeamDirection = null;
62524
62384
  constructor(renderer) {
@@ -62533,7 +62393,6 @@
62533
62393
  for (let i = 0, j = bar.voices.length; i < j; i++) {
62534
62394
  const v = bar.voices[i];
62535
62395
  this.beamHelpers.push([]);
62536
- this.beamHelperLookup.push(new Map());
62537
62396
  for (let k = 0, l = v.beats.length; k < l; k++) {
62538
62397
  const b = v.beats[k];
62539
62398
  let helperForBeat;
@@ -62542,6 +62401,9 @@
62542
62401
  }
62543
62402
  else {
62544
62403
  helperForBeat = currentBeamHelper;
62404
+ if (currentGraceBeamHelper) {
62405
+ currentGraceBeamHelper.finish();
62406
+ }
62545
62407
  currentGraceBeamHelper = null;
62546
62408
  }
62547
62409
  // if a new beaming helper was started, we close our tuplet grouping as well
@@ -62562,7 +62424,7 @@
62562
62424
  }
62563
62425
  this.beamHelpers[v.index].push(helperForBeat);
62564
62426
  }
62565
- this.beamHelperLookup[v.index].set(b.index, helperForBeat);
62427
+ this._beamHelperLookup.set(b.id, helperForBeat);
62566
62428
  }
62567
62429
  if (currentBeamHelper) {
62568
62430
  currentBeamHelper.finish();
@@ -62575,7 +62437,7 @@
62575
62437
  }
62576
62438
  }
62577
62439
  getBeamingHelperForBeat(beat) {
62578
- return this.beamHelperLookup[beat.voice.index].get(beat.index);
62440
+ return this._beamHelperLookup.has(beat.id) ? this._beamHelperLookup.get(beat.id) : undefined;
62579
62441
  }
62580
62442
  }
62581
62443
 
@@ -62685,6 +62547,9 @@
62685
62547
  return this._contentBottomOverflow + this.bottomEffects.height;
62686
62548
  }
62687
62549
  helpers;
62550
+ get collisionHelper() {
62551
+ return this.helpers.collisionHelper;
62552
+ }
62688
62553
  /**
62689
62554
  * Gets or sets whether this renderer is linked to the next one
62690
62555
  * by some glyphs like a vibrato effect
@@ -62744,6 +62609,11 @@
62744
62609
  for (const container of this._voiceContainers.values()) {
62745
62610
  container.scaleToWidth(containerWidth);
62746
62611
  }
62612
+ for (const v of this.helpers.beamHelpers) {
62613
+ for (const h of v) {
62614
+ h.alignWithBeats();
62615
+ }
62616
+ }
62747
62617
  this._postBeatGlyphs.x = this._preBeatGlyphs.x + this._preBeatGlyphs.width + containerWidth;
62748
62618
  this.width = width;
62749
62619
  this.topEffects.alignGlyphs();
@@ -62772,10 +62642,13 @@
62772
62642
  get barDisplayWidth() {
62773
62643
  return this.staff.system.staves.length > 1 ? this.bar.masterBar.displayWidth : this.bar.displayWidth;
62774
62644
  }
62775
- wasFirstOfLine = false;
62776
- get isFirstOfLine() {
62645
+ wasFirstOfStaff = false;
62646
+ get isFirstOfStaff() {
62777
62647
  return this.index === 0;
62778
62648
  }
62649
+ get isLastOfStaff() {
62650
+ return this.index === this.staff.barRenderers.length - 1;
62651
+ }
62779
62652
  get isLast() {
62780
62653
  return !this.bar || this.bar.index === this.scoreRenderer.layout.lastBarIndex;
62781
62654
  }
@@ -62990,6 +62863,18 @@
62990
62863
  }
62991
62864
  }
62992
62865
  }
62866
+ for (const v of this._voiceContainers.values()) {
62867
+ for (const b of v.beatGlyphs) {
62868
+ const topY = b.getBoundingBoxTop();
62869
+ if (topY < 0) {
62870
+ this.registerOverflowTop(topY * -1);
62871
+ }
62872
+ const bottomY = b.getBoundingBoxBottom();
62873
+ if (bottomY > rendererBottom) {
62874
+ this.registerOverflowBottom(bottomY - rendererBottom);
62875
+ }
62876
+ }
62877
+ }
62993
62878
  const beatEffectsMinY = this.beatEffectsMinY;
62994
62879
  if (!Number.isNaN(beatEffectsMinY)) {
62995
62880
  const beatEffectTopOverflow = -beatEffectsMinY;
@@ -63043,7 +62928,6 @@
63043
62928
  g.renderer = this;
63044
62929
  g.preNotes.renderer = this;
63045
62930
  g.onNotes.renderer = this;
63046
- g.onNotes.beamingHelper = this.helpers.beamHelperLookup[g.beat.voice.index].get(g.beat.index);
63047
62931
  this.getVoiceContainer(g.beat.voice).addGlyph(g);
63048
62932
  }
63049
62933
  getVoiceContainer(voice) {
@@ -63122,7 +63006,7 @@
63122
63006
  this._postBeatGlyphs.addGlyph(g);
63123
63007
  }
63124
63008
  createPreBeatGlyphs() {
63125
- this.wasFirstOfLine = this.isFirstOfLine;
63009
+ this.wasFirstOfStaff = this.isFirstOfStaff;
63126
63010
  }
63127
63011
  createBeatGlyphs() {
63128
63012
  for (const voice of this.bar.voices) {
@@ -63149,26 +63033,7 @@
63149
63033
  getBeatX(beat, requestedPosition = BeatXPosition.PreNotes, useSharedSizes = false) {
63150
63034
  const container = this.getBeatContainer(beat);
63151
63035
  if (container) {
63152
- switch (requestedPosition) {
63153
- case BeatXPosition.PreNotes:
63154
- return container.voiceContainer.x + container.x;
63155
- case BeatXPosition.OnNotes:
63156
- return container.voiceContainer.x + container.x + container.onNotes.x;
63157
- case BeatXPosition.MiddleNotes:
63158
- return container.voiceContainer.x + container.x + container.onTimeX;
63159
- case BeatXPosition.Stem:
63160
- const offset = container.onNotes.beamingHelper
63161
- ? container.onNotes.beamingHelper.getBeatLineX(beat)
63162
- : container.onNotes.x + container.onNotes.width / 2;
63163
- return container.voiceContainer.x + offset;
63164
- case BeatXPosition.PostNotes:
63165
- const onNoteSize = useSharedSizes
63166
- ? (this.layoutingInfo.getBeatSizes(beat)?.onBeatSize ?? container.onNotes.width)
63167
- : container.onNotes.width;
63168
- return container.voiceContainer.x + container.x + container.onNotes.x + onNoteSize;
63169
- case BeatXPosition.EndBeat:
63170
- return container.voiceContainer.x + container.x + container.width;
63171
- }
63036
+ return container.voiceContainer.x + container.x + container.getBeatX(requestedPosition, useSharedSizes);
63172
63037
  }
63173
63038
  return 0;
63174
63039
  }
@@ -63203,7 +63068,7 @@
63203
63068
  this.updateSizes();
63204
63069
  // there are some glyphs which are shown only for renderers at the line start, so we simply recreate them
63205
63070
  // but we only need to recreate them for the renderers that were the first of the line or are now the first of the line
63206
- if ((this.wasFirstOfLine && !this.isFirstOfLine) || (!this.wasFirstOfLine && this.isFirstOfLine)) {
63071
+ if ((this.wasFirstOfStaff && !this.isFirstOfStaff) || (!this.wasFirstOfStaff && this.isFirstOfStaff)) {
63207
63072
  this.recreatePreBeatGlyphs();
63208
63073
  this._postBeatGlyphs.doLayout();
63209
63074
  }
@@ -63240,7 +63105,7 @@
63240
63105
  completeBeamingHelper(_helper) {
63241
63106
  }
63242
63107
  getBeatDirection(beat) {
63243
- return this.helpers.getBeamingHelperForBeat(beat).direction;
63108
+ return this.helpers.getBeamingHelperForBeat(beat)?.direction ?? BeamDirection.Up;
63244
63109
  }
63245
63110
  }
63246
63111
 
@@ -65607,8 +65472,18 @@
65607
65472
  }
65608
65473
  const lineRenderer = this.renderer;
65609
65474
  const lineYOffset = lineRenderer.smuflMetrics.staffLineThickness;
65610
- const top = this.y - lineYOffset;
65611
- const bottom = this.y + this.renderer.height;
65475
+ let top = this.y;
65476
+ let bottom = this.y;
65477
+ if (lineRenderer.drawnLineCount < 2 ||
65478
+ (!this._isRight && lineRenderer.isFirstOfStaff) ||
65479
+ (this._isRight && lineRenderer.isLastOfStaff)) {
65480
+ top -= lineYOffset;
65481
+ bottom += lineRenderer.height;
65482
+ }
65483
+ else {
65484
+ top += lineRenderer.getLineY(0);
65485
+ bottom += lineRenderer.getLineY(lineRenderer.drawnLineCount - 1);
65486
+ }
65612
65487
  const h = bottom - top;
65613
65488
  // round up to have pixel-aligned bar lines, x-shift will be used during rendering
65614
65489
  // to avoid shifting again all glyphs
@@ -65694,6 +65569,54 @@
65694
65569
  }
65695
65570
  }
65696
65571
 
65572
+ /**
65573
+ * @internal
65574
+ */
65575
+ class FlagGlyph extends MusicFontGlyph {
65576
+ constructor(x, y, duration, direction, isGrace) {
65577
+ super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
65578
+ }
65579
+ static getSymbol(duration, direction, isGrace) {
65580
+ if (isGrace) {
65581
+ duration = Duration.Eighth;
65582
+ }
65583
+ if (direction === BeamDirection.Up) {
65584
+ switch (duration) {
65585
+ case Duration.Eighth:
65586
+ return MusicFontSymbol.Flag8thUp;
65587
+ case Duration.Sixteenth:
65588
+ return MusicFontSymbol.Flag16thUp;
65589
+ case Duration.ThirtySecond:
65590
+ return MusicFontSymbol.Flag32ndUp;
65591
+ case Duration.SixtyFourth:
65592
+ return MusicFontSymbol.Flag64thUp;
65593
+ case Duration.OneHundredTwentyEighth:
65594
+ return MusicFontSymbol.Flag128thUp;
65595
+ case Duration.TwoHundredFiftySixth:
65596
+ return MusicFontSymbol.Flag256thUp;
65597
+ default:
65598
+ return MusicFontSymbol.Flag8thUp;
65599
+ }
65600
+ }
65601
+ switch (duration) {
65602
+ case Duration.Eighth:
65603
+ return MusicFontSymbol.Flag8thDown;
65604
+ case Duration.Sixteenth:
65605
+ return MusicFontSymbol.Flag16thDown;
65606
+ case Duration.ThirtySecond:
65607
+ return MusicFontSymbol.Flag32ndDown;
65608
+ case Duration.SixtyFourth:
65609
+ return MusicFontSymbol.Flag64thDown;
65610
+ case Duration.OneHundredTwentyEighth:
65611
+ return MusicFontSymbol.Flag128thDown;
65612
+ case Duration.TwoHundredFiftySixth:
65613
+ return MusicFontSymbol.Flag128thDown;
65614
+ default:
65615
+ return MusicFontSymbol.Flag8thDown;
65616
+ }
65617
+ }
65618
+ }
65619
+
65697
65620
  /**
65698
65621
  * @internal
65699
65622
  */
@@ -65729,6 +65652,23 @@
65729
65652
  }
65730
65653
  }
65731
65654
 
65655
+ /**
65656
+ * This simple glyph allows to put an empty region in to a BarRenderer.
65657
+ * @internal
65658
+ */
65659
+ class SpacingGlyph extends Glyph {
65660
+ constructor(x, y, width) {
65661
+ super(x, y);
65662
+ this.width = width;
65663
+ }
65664
+ getBoundingBoxTop() {
65665
+ return Number.NaN;
65666
+ }
65667
+ getBoundingBoxBottom() {
65668
+ return Number.NaN;
65669
+ }
65670
+ }
65671
+
65732
65672
  /**
65733
65673
  * This is a base class for any bar renderer which renders music notation on a staff
65734
65674
  * with lines like Standard Notation, Guitar Tablatures and Slash Notation.
@@ -65852,7 +65792,7 @@
65852
65792
  if (this.hasVoiceContainer(voice)) {
65853
65793
  const container = this.getVoiceContainer(voice);
65854
65794
  for (const tupletGroup of container.tupletGroups) {
65855
- this._paintTupletHelper(cx + this.beatGlyphsStart, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65795
+ this._paintTupletHelper(cx, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65856
65796
  }
65857
65797
  }
65858
65798
  }
@@ -65926,26 +65866,27 @@
65926
65866
  // check if we need to paint simple footer
65927
65867
  const offset = this.tupletOffset;
65928
65868
  const size = this.tupletSize;
65869
+ const shift = offset + size * 0.5;
65929
65870
  const _ = ElementStyleHelper.beat(canvas, beatElement, h.beats[0]);
65930
65871
  try {
65931
65872
  const l = canvas.lineWidth;
65932
65873
  canvas.lineWidth = this.smuflMetrics.tupletBracketThickness;
65933
65874
  if (h.beats.length === 1 || !h.isFull) {
65934
65875
  for (const beat of h.beats) {
65935
- const beamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(beat.index);
65876
+ const beamingHelper = this.helpers.getBeamingHelperForBeat(beat);
65936
65877
  if (!beamingHelper) {
65937
65878
  continue;
65938
65879
  }
65939
65880
  const direction = this.getTupletBeamDirection(beamingHelper);
65940
- const tupletX = beamingHelper.getBeatLineX(beat);
65881
+ const tupletX = this.getBeatX(beat, BeatXPosition.Stem);
65941
65882
  let tupletY = this.calculateBeamYWithDirection(beamingHelper, tupletX, direction);
65942
65883
  if (direction === BeamDirection.Down) {
65943
- tupletY += offset + size;
65884
+ tupletY += shift;
65944
65885
  }
65945
65886
  else {
65946
- tupletY -= offset + size;
65887
+ tupletY -= shift;
65947
65888
  }
65948
- canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY, 1, s, true);
65889
+ canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY + size * 0.5, 1, s, true);
65949
65890
  }
65950
65891
  }
65951
65892
  else {
@@ -65975,12 +65916,12 @@
65975
65916
  }
65976
65917
  //
65977
65918
  // Calculate the overall area of the tuplet bracket
65978
- const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes) - this.beatGlyphsStart;
65979
- const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes) - this.beatGlyphsStart;
65919
+ const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes);
65920
+ const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes);
65980
65921
  //
65981
65922
  // calculate the y positions for our bracket
65982
- const firstNonRestBeamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(firstNonRestBeat.index);
65983
- const lastNonRestBeamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(lastNonRestBeat.index);
65923
+ const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
65924
+ const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
65984
65925
  const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
65985
65926
  let startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
65986
65927
  let endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
@@ -65989,7 +65930,6 @@
65989
65930
  endY = startY;
65990
65931
  }
65991
65932
  // align line centered in available space
65992
- const shift = offset + size * 0.5;
65993
65933
  if (direction === BeamDirection.Down) {
65994
65934
  startY += shift;
65995
65935
  endY += shift;
@@ -66054,14 +65994,24 @@
66054
65994
  paintBeams(cx, cy, canvas, flagsElement, beamsElement) {
66055
65995
  for (const v of this.helpers.beamHelpers) {
66056
65996
  for (const h of v) {
66057
- this._paintBeamHelper(cx + this.beatGlyphsStart, cy, canvas, h, flagsElement, beamsElement);
65997
+ this.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
66058
65998
  }
66059
65999
  }
66060
66000
  }
66061
66001
  drawBeamHelperAsFlags(h) {
66062
66002
  return h.beats.length === 1;
66063
66003
  }
66064
- _paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
66004
+ hasFlag(beat) {
66005
+ if (beat.isRest) {
66006
+ return false;
66007
+ }
66008
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
66009
+ if (helper) {
66010
+ return helper.hasFlag(this.drawBeamHelperAsFlags(helper), beat);
66011
+ }
66012
+ return BeamingHelper.beatHasFlag(beat);
66013
+ }
66014
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
66065
66015
  canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
66066
66016
  if (!h.isRestBeamHelper) {
66067
66017
  if (this.drawBeamHelperAsFlags(h)) {
@@ -66072,7 +66022,7 @@
66072
66022
  }
66073
66023
  }
66074
66024
  }
66075
- shouldPaintFlag(beat, h) {
66025
+ shouldPaintFlag(beat) {
66076
66026
  // no flags for bend grace beats
66077
66027
  if (beat.graceType === GraceType.BendGrace) {
66078
66028
  return false;
@@ -66080,10 +66030,6 @@
66080
66030
  if (beat.deadSlapped) {
66081
66031
  return false;
66082
66032
  }
66083
- // we don't have an X-position: cannot paint a flag
66084
- if (!h.hasBeatLineX(beat)) {
66085
- return false;
66086
- }
66087
66033
  // no flags for any grace notes on songbook mode
66088
66034
  if (beat.graceType !== GraceType.None && this.settings.notation.notationMode === exports.NotationMode.SongBook) {
66089
66035
  return false;
@@ -66098,14 +66044,14 @@
66098
66044
  }
66099
66045
  paintFlag(cx, cy, canvas, h, flagsElement) {
66100
66046
  for (const beat of h.beats) {
66101
- if (!this.shouldPaintFlag(beat, h)) {
66047
+ if (!this.shouldPaintFlag(beat)) {
66102
66048
  continue;
66103
66049
  }
66104
66050
  const isGrace = beat.graceType !== GraceType.None;
66105
66051
  //
66106
66052
  // draw line
66107
66053
  //
66108
- const beatLineX = h.getBeatLineX(beat);
66054
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66109
66055
  const direction = this.getBeamDirection(h);
66110
66056
  const topY = cy + this.y + this.getFlagTopY(beat, direction);
66111
66057
  const bottomY = cy + this.y + this.getFlagBottomY(beat, direction);
@@ -66116,7 +66062,7 @@
66116
66062
  else {
66117
66063
  flagY = topY;
66118
66064
  }
66119
- if (!h.hasLine(true, beat)) {
66065
+ if (!h.hasStem(true, beat)) {
66120
66066
  continue;
66121
66067
  }
66122
66068
  this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, topY, bottomY, canvas);
@@ -66202,10 +66148,10 @@
66202
66148
  }
66203
66149
  for (let i = 0, j = h.beats.length; i < j; i++) {
66204
66150
  const beat = h.beats[i];
66205
- if (!h.hasBeatLineX(beat) || beat.deadSlapped) {
66151
+ if (beat.deadSlapped) {
66206
66152
  continue;
66207
66153
  }
66208
- const beatLineX = h.getBeatLineX(beat);
66154
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66209
66155
  const y1 = cy + this.y + this.getBarLineStart(beat, direction);
66210
66156
  // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
66211
66157
  // when combining stems and beams on sub-pixel level
@@ -66243,7 +66189,7 @@
66243
66189
  barEndY = barY + this.calculateBeamY(h, barEndX);
66244
66190
  LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66245
66191
  // end part
66246
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66192
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66247
66193
  barStartX = barEndX - brokenBarOffset;
66248
66194
  barStartY = barY + this.calculateBeamY(h, barStartX);
66249
66195
  barEndY = barY + this.calculateBeamY(h, barEndX);
@@ -66253,7 +66199,7 @@
66253
66199
  if (isFullBarJoin) {
66254
66200
  // full bar?
66255
66201
  barStartX = beatLineX;
66256
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66202
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66257
66203
  }
66258
66204
  else if (i === 0 || !BeamingHelper.isFullBarJoin(h.beats[i - 1], beat, barIndex)) {
66259
66205
  barStartX = beatLineX;
@@ -66288,7 +66234,7 @@
66288
66234
  }
66289
66235
  }
66290
66236
  if (h.graceType === GraceType.BeforeBeat) {
66291
- const beatLineX = h.getBeatLineX(h.beats[0]);
66237
+ const beatLineX = this.getBeatX(h.beats[0], BeatXPosition.Stem);
66292
66238
  const flagWidth = this.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
66293
66239
  let slashY = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
66294
66240
  slashY += barSize + barSpacing;
@@ -66315,20 +66261,7 @@
66315
66261
  const noteOverflowPadding = this.getLineHeight(0.5);
66316
66262
  for (const v of this.helpers.beamHelpers) {
66317
66263
  for (const h of v) {
66318
- if (h.isRestBeamHelper) {
66319
- if (h.minRestSteps) {
66320
- const topY = this.getLineY(h.maxRestSteps / 2) - noteOverflowPadding;
66321
- if (topY < maxNoteY) {
66322
- maxNoteY = topY;
66323
- }
66324
- }
66325
- if (h.maxRestSteps) {
66326
- const bottomY = this.getLineY(h.maxRestSteps & 2) + noteOverflowPadding;
66327
- if (bottomY < maxNoteY) {
66328
- maxNoteY = bottomY;
66329
- }
66330
- }
66331
- }
66264
+ if (h.isRestBeamHelper) ;
66332
66265
  else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
66333
66266
  if (h.direction === BeamDirection.Up) {
66334
66267
  let topY = this.getFlagTopY(h.beats[0], h.direction);
@@ -66379,17 +66312,6 @@
66379
66312
  }
66380
66313
  }
66381
66314
  }
66382
- const beatContainer = this.getBeatContainer(h.beats[0]);
66383
- if (beatContainer) {
66384
- const bBoxTop = beatContainer.getBoundingBoxTop();
66385
- const bBoxBottom = beatContainer.getBoundingBoxBottom();
66386
- if (bBoxBottom > minNoteY) {
66387
- minNoteY = bBoxBottom;
66388
- }
66389
- if (bBoxTop < maxNoteY) {
66390
- maxNoteY = bBoxTop;
66391
- }
66392
- }
66393
66315
  }
66394
66316
  }
66395
66317
  if (maxNoteY < rendererTop) {
@@ -66405,25 +66327,6 @@
66405
66327
  }
66406
66328
  const scale = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66407
66329
  const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
66408
- let stemSize = this.smuflMetrics.standardStemLength * scale;
66409
- if (h.tremoloDuration) {
66410
- // for 16th and shorter beats we need more space for all tremolos
66411
- // for 8th beats we need only more space for 32nd tremolos
66412
- // the logic here is not perfect but there is no SMuFL guideline
66413
- // on how tremolos need to extend stems
66414
- const oneBeamSize = (this.smuflMetrics.beamThickness + this.smuflMetrics.beamSpacing) * scale;
66415
- if (h.shortestDuration > Duration.Eighth) {
66416
- if (h.tremoloDuration === Duration.Eighth) {
66417
- stemSize += oneBeamSize;
66418
- }
66419
- else {
66420
- stemSize += oneBeamSize * 1.5;
66421
- }
66422
- }
66423
- else if (h.tremoloDuration === Duration.ThirtySecond) {
66424
- stemSize += oneBeamSize * 1.5;
66425
- }
66426
- }
66427
66330
  const drawingInfo = new BeamingHelperDrawInfo();
66428
66331
  h.drawingInfos.set(direction, drawingInfo);
66429
66332
  // the beaming logic works like this:
@@ -66436,33 +66339,17 @@
66436
66339
  const isRest = h.isRestBeamHelper;
66437
66340
  // 1. put direct diagonal line.
66438
66341
  drawingInfo.startBeat = firstBeat;
66439
- drawingInfo.startX = h.getBeatLineX(firstBeat);
66440
- if (isRest) {
66441
- drawingInfo.startY =
66442
- direction === BeamDirection.Up
66443
- ? this.getLineY(h.minRestSteps / 2)
66444
- : this.getLineY(h.maxRestSteps / 2);
66445
- }
66446
- else {
66447
- drawingInfo.startY =
66448
- direction === BeamDirection.Up
66449
- ? this.getFlagTopY(firstBeat, direction)
66450
- : this.getFlagBottomY(firstBeat, direction);
66451
- }
66342
+ drawingInfo.startX = this.getBeatX(firstBeat, BeatXPosition.Stem);
66343
+ drawingInfo.startY =
66344
+ direction === BeamDirection.Up
66345
+ ? this.getFlagTopY(firstBeat, direction)
66346
+ : this.getFlagBottomY(firstBeat, direction);
66452
66347
  drawingInfo.endBeat = lastBeat;
66453
- drawingInfo.endX = h.getBeatLineX(lastBeat);
66454
- if (isRest) {
66455
- drawingInfo.endY =
66456
- direction === BeamDirection.Up
66457
- ? this.getLineY(h.minRestSteps / 2)
66458
- : this.getLineY(h.maxRestSteps / 2);
66459
- }
66460
- else {
66461
- drawingInfo.endY =
66462
- direction === BeamDirection.Up
66463
- ? this.getFlagTopY(lastBeat, direction)
66464
- : this.getFlagBottomY(lastBeat, direction);
66465
- }
66348
+ drawingInfo.endX = this.getBeatX(lastBeat, BeatXPosition.Stem);
66349
+ drawingInfo.endY =
66350
+ direction === BeamDirection.Up
66351
+ ? this.getFlagTopY(lastBeat, direction)
66352
+ : this.getFlagBottomY(lastBeat, direction);
66466
66353
  // 2. ensure max slope
66467
66354
  // we use the min/max notes to place the beam along their real position
66468
66355
  // we only want a maximum of 10 offset for their gradient
@@ -66487,12 +66374,41 @@
66487
66374
  drawingInfo.startY - drawingInfo.endY > maxSlope) {
66488
66375
  drawingInfo.startY = drawingInfo.endY + maxSlope;
66489
66376
  }
66490
- // 3. let middle elements shift up/down
66377
+ // 3. adjust beam drawing order
66378
+ // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66379
+ // here we shift accordingly
66380
+ let barDrawingShift = 0;
66381
+ if (barCount > 2 && !isRest) {
66382
+ const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66383
+ const beamThickness = this.smuflMetrics.beamThickness * scale;
66384
+ const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66385
+ if (direction === BeamDirection.Up) {
66386
+ const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66387
+ const barTopY = bottomBarY - totalBarsHeight;
66388
+ const diff = drawingInfo.startY - barTopY;
66389
+ if (diff > 0) {
66390
+ barDrawingShift = diff * -1;
66391
+ drawingInfo.startY -= diff;
66392
+ drawingInfo.endY -= diff;
66393
+ }
66394
+ }
66395
+ else {
66396
+ const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66397
+ const barBottomY = topBarY + totalBarsHeight;
66398
+ const diff = barBottomY - drawingInfo.startY;
66399
+ if (diff > 0) {
66400
+ barDrawingShift = diff;
66401
+ drawingInfo.startY += diff;
66402
+ drawingInfo.endY += diff;
66403
+ }
66404
+ }
66405
+ }
66406
+ // 4. let middle elements shift up/down
66491
66407
  if (h.beats.length > 1) {
66492
66408
  // check if highest note shifts bar up or down
66493
66409
  if (direction === BeamDirection.Up) {
66494
- const yNeededForHighestNote = this.getLineY(this.getMinLineOfBeat(h.beatOfHighestNote)) - stemSize;
66495
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfHighestNote));
66410
+ const yNeededForHighestNote = barDrawingShift + this.getFlagTopY(h.beatOfHighestNote, direction);
66411
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfHighestNote, BeatXPosition.Stem));
66496
66412
  const diff = yGivenByCurrentValues - yNeededForHighestNote;
66497
66413
  if (diff > 0) {
66498
66414
  drawingInfo.startY -= diff;
@@ -66500,8 +66416,8 @@
66500
66416
  }
66501
66417
  }
66502
66418
  else {
66503
- const yNeededForLowestNote = this.getLineY(this.getMaxLineOfBeat(h.beatOfLowestNote)) + stemSize;
66504
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfLowestNote));
66419
+ const yNeededForLowestNote = barDrawingShift + this.getFlagBottomY(h.beatOfLowestNote, direction);
66420
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfLowestNote, BeatXPosition.Stem));
66505
66421
  const diff = yNeededForLowestNote - yGivenByCurrentValues;
66506
66422
  if (diff > 0) {
66507
66423
  drawingInfo.startY += diff;
@@ -66509,33 +66425,39 @@
66509
66425
  }
66510
66426
  }
66511
66427
  // check if rest shifts bar up or down
66512
- if (h.minRestSteps !== null || h.maxRestSteps !== null) {
66428
+ let barSpacing = 0;
66429
+ if (h.restBeats.length > 0) {
66430
+ // space needed for the bars, rests need to be below them
66513
66431
  const scaleMod = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66514
- let barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66515
- barSpacing += this.smuflMetrics.beamSpacing;
66516
- if (direction === BeamDirection.Up && h.minRestSteps !== null) {
66517
- const yNeededForRest = this.getLineY(h.minRestSteps / 2) - barSpacing;
66518
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMinRestSteps));
66519
- const diff = yGivenByCurrentValues - yNeededForRest;
66520
- if (diff > 0) {
66521
- drawingInfo.startY -= diff;
66522
- drawingInfo.endY -= diff;
66432
+ barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66433
+ }
66434
+ for (const b of h.restBeats) {
66435
+ // rest beats which are "under" the beam
66436
+ if (b.isRest && b.index < h.beats[h.beats.length - 1].index) {
66437
+ if (direction === BeamDirection.Up) {
66438
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxTop() - barSpacing;
66439
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66440
+ const diff = yGivenByCurrentValues - yNeededForRest;
66441
+ if (diff > 0) {
66442
+ drawingInfo.startY -= diff;
66443
+ drawingInfo.endY -= diff;
66444
+ }
66523
66445
  }
66524
- }
66525
- else if (direction === BeamDirection.Down && h.maxRestSteps !== null) {
66526
- const yNeededForRest = this.getLineHeight(h.maxRestSteps / 2) + barSpacing;
66527
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMaxRestSteps));
66528
- const diff = yNeededForRest - yGivenByCurrentValues;
66529
- if (diff > 0) {
66530
- drawingInfo.startY += diff;
66531
- drawingInfo.endY += diff;
66446
+ else if (direction === BeamDirection.Down) {
66447
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxBottom() + barSpacing;
66448
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66449
+ const diff = yNeededForRest - yGivenByCurrentValues;
66450
+ if (diff > 0) {
66451
+ drawingInfo.startY += diff;
66452
+ drawingInfo.endY += diff;
66453
+ }
66532
66454
  }
66533
66455
  }
66534
66456
  }
66535
66457
  // check if slash shifts bar up or down
66536
66458
  if (h.slashBeats.length > 0) {
66537
66459
  for (const b of h.slashBeats) {
66538
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(b));
66460
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66539
66461
  const yNeededForSlash = h.direction === BeamDirection.Up
66540
66462
  ? this.getFlagTopY(b, h.direction)
66541
66463
  : this.getFlagBottomY(b, h.direction);
@@ -66547,31 +66469,6 @@
66547
66469
  }
66548
66470
  }
66549
66471
  }
66550
- // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66551
- // here we shift accordingly
66552
- if (barCount > 2 && !isRest) {
66553
- const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66554
- const beamThickness = this.smuflMetrics.beamThickness * scale;
66555
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66556
- if (direction === BeamDirection.Up) {
66557
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66558
- const barTopY = bottomBarY - totalBarsHeight;
66559
- const diff = drawingInfo.startY - barTopY;
66560
- if (diff > 0) {
66561
- drawingInfo.startY -= diff;
66562
- drawingInfo.endY -= diff;
66563
- }
66564
- }
66565
- else {
66566
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66567
- const barBottomY = topBarY + totalBarsHeight;
66568
- const diff = barBottomY - drawingInfo.startY;
66569
- if (diff > 0) {
66570
- drawingInfo.startY += diff;
66571
- drawingInfo.endY += diff;
66572
- }
66573
- }
66574
- }
66575
66472
  }
66576
66473
  getMinLineOfBeat(_beat) {
66577
66474
  return 0;
@@ -67092,20 +66989,6 @@
67092
66989
  }
67093
66990
  return 0;
67094
66991
  }
67095
- updateBeamingHelper() {
67096
- if (this.beamingHelper) {
67097
- let g = null;
67098
- if (this.noteHeads) {
67099
- g = this.noteHeads;
67100
- }
67101
- else if (this.deadSlapped) {
67102
- g = this.deadSlapped;
67103
- }
67104
- if (g) {
67105
- this.beamingHelper.registerBeatLineX('numbered', this.container.beat, this.container.x + this.x + g.x, this.container.x + this.x + g.x + g.width);
67106
- }
67107
- }
67108
- }
67109
66992
  static majorKeySignatureOneValues = [
67110
66993
  // Flats
67111
66994
  59, 66, 61, 68, 63, 58, 65,
@@ -67221,14 +67104,16 @@
67221
67104
  }
67222
67105
  super.doLayout();
67223
67106
  if (this.container.beat.isEmpty) {
67224
- this.centerX = this.width / 2;
67107
+ this.onTimeX = this.width / 2;
67225
67108
  }
67226
67109
  else if (this.noteHeads) {
67227
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
67110
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
67228
67111
  }
67229
67112
  else if (this.deadSlapped) {
67230
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
67113
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67231
67114
  }
67115
+ this.middleX = this.onTimeX;
67116
+ this.stemX = this.middleX;
67232
67117
  }
67233
67118
  }
67234
67119
 
@@ -67641,7 +67526,7 @@
67641
67526
  const barSize = this.smuflMetrics.numberedBarRendererBarSize;
67642
67527
  const barCount = ModelUtils.getIndex(beat.duration) - 2;
67643
67528
  const barStart = cy + this.y;
67644
- const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes) - this.beatGlyphsStart;
67529
+ const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes);
67645
67530
  const beamY = this.calculateBeamY(h, beatLineX);
67646
67531
  for (let barIndex = 0; barIndex < barCount; barIndex++) {
67647
67532
  let barStartX = 0;
@@ -67650,11 +67535,11 @@
67650
67535
  const barY = barStart + barIndex * barSpacing;
67651
67536
  if (i === h.beats.length - 1) {
67652
67537
  barStartX = beatLineX;
67653
- barEndX = this.getBeatX(beat, BeatXPosition.PostNotes) - this.beatGlyphsStart;
67538
+ barEndX = this.getBeatX(beat, BeatXPosition.PostNotes);
67654
67539
  }
67655
67540
  else {
67656
67541
  barStartX = beatLineX;
67657
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes) - this.beatGlyphsStart;
67542
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes);
67658
67543
  }
67659
67544
  barStartY = barY + beamY;
67660
67545
  canvas.fillRect(cx + this.x + barStartX, barStartY, barEndX - barStartX, barSize);
@@ -67672,7 +67557,7 @@
67672
67557
  dotsY = barStart + beamY + barCount * (barSpacing + barSize);
67673
67558
  dotsOffset = dotSpacing;
67674
67559
  }
67675
- const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes) - this.beatGlyphsStart;
67560
+ const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes);
67676
67561
  dotCount = Math.abs(dotCount);
67677
67562
  for (let d = 0; d < dotCount; d++) {
67678
67563
  CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + dotX, dotsY, 1, MusicFontSymbol.AugmentationDot, true);
@@ -67721,7 +67606,7 @@
67721
67606
  return this.getLineY(0) - noteHeadHeight / 2;
67722
67607
  }
67723
67608
  createPreBeatGlyphs() {
67724
- this.wasFirstOfLine = this.isFirstOfLine;
67609
+ this.wasFirstOfStaff = this.isFirstOfStaff;
67725
67610
  if (this.index === 0 || (this.bar.masterBar.isRepeatStart && this._isOnlyNumbered)) {
67726
67611
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
67727
67612
  }
@@ -67777,6 +67662,11 @@
67777
67662
  }
67778
67663
  paintBeamingStem(_beat, _cy, _x, _topY, _bottomY, _canvas) {
67779
67664
  }
67665
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
67666
+ if (h.voice?.index === 0) {
67667
+ super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
67668
+ }
67669
+ }
67780
67670
  }
67781
67671
 
67782
67672
  /**
@@ -68094,6 +67984,7 @@
68094
67984
  upLineX = 0;
68095
67985
  downLineX = 0;
68096
67986
  noteStartX = 0;
67987
+ onTimeX = 0;
68097
67988
  constructor() {
68098
67989
  super(0, 0);
68099
67990
  }
@@ -68180,6 +68071,8 @@
68180
68071
  // align all notes so that they align with the stem positions
68181
68072
  const stemPosition = anyDisplaced || direction === BeamDirection.Up ? stemUpX : stemDownX;
68182
68073
  let w = 0;
68074
+ let displacedWidth = 0;
68075
+ let nonDisplacedWidth = 0;
68183
68076
  for (let i = 0, j = this._infos.length; i < j; i++) {
68184
68077
  const g = this._infos[i].glyph;
68185
68078
  const alignDisplaced = displaced.get(i);
@@ -68195,7 +68088,14 @@
68195
68088
  }
68196
68089
  }
68197
68090
  g.x += this.noteStartX;
68198
- w = Math.max(w, g.x + g.width);
68091
+ const gw = g.x + g.width;
68092
+ w = Math.max(w, gw);
68093
+ if (alignDisplaced) {
68094
+ displacedWidth = Math.max(displacedWidth, gw);
68095
+ }
68096
+ else {
68097
+ nonDisplacedWidth = Math.max(nonDisplacedWidth, gw);
68098
+ }
68199
68099
  // after size calculation, re-align glyph to stem if needed
68200
68100
  if (g instanceof NoteHeadGlyph && g.centerOnStem) {
68201
68101
  g.x = stemPosition;
@@ -68209,6 +68109,23 @@
68209
68109
  this.upLineX = stemUpX;
68210
68110
  this.downLineX = stemDownX;
68211
68111
  }
68112
+ // the center of score notes, (used for aligning the beat to the right on-time position)
68113
+ // is always the center of the "correct note" position.
68114
+ // * If the stem is upwards, the center is the middle of the left hand side note head
68115
+ // * If the stem is downards, the center is the middle of the right-hand-side note head
68116
+ if (anyDisplaced) {
68117
+ if (direction === BeamDirection.Up) {
68118
+ this.onTimeX = nonDisplacedWidth / 2;
68119
+ }
68120
+ else {
68121
+ const displacedRawWith = displacedWidth - stemPosition;
68122
+ this.onTimeX = stemPosition + (displacedRawWith / 2);
68123
+ }
68124
+ }
68125
+ else {
68126
+ // for no displaced notes it is simply the center
68127
+ this.onTimeX = w / 2;
68128
+ }
68212
68129
  this.width = w;
68213
68130
  }
68214
68131
  paint(cx, cy, canvas) {
@@ -68286,9 +68203,8 @@
68286
68203
  aboveBeatEffects = new Map();
68287
68204
  belowBeatEffects = new Map();
68288
68205
  beat;
68289
- beamingHelper;
68290
68206
  get direction() {
68291
- return this.beamingHelper.direction;
68207
+ return this.renderer.getBeatDirection(this.beat);
68292
68208
  }
68293
68209
  get scale() {
68294
68210
  return this.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
@@ -68330,7 +68246,7 @@
68330
68246
  : 0) * scale;
68331
68247
  // stem size according to duration
68332
68248
  pos -= this.renderer.smuflMetrics.standardStemLength * scale;
68333
- const topCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68249
+ const topCenterY = this.renderer.centerStaffStemY(this.direction);
68334
68250
  return Math.min(topCenterY, pos);
68335
68251
  case NoteYPosition.Top:
68336
68252
  pos -= n.height / 2;
@@ -68347,7 +68263,7 @@
68347
68263
  : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
68348
68264
  // stem size according to duration
68349
68265
  pos += this.renderer.smuflMetrics.standardStemLength * scale;
68350
- const bottomCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68266
+ const bottomCenterY = this.renderer.centerStaffStemY(this.direction);
68351
68267
  return Math.max(bottomCenterY, pos);
68352
68268
  case NoteYPosition.StemUp:
68353
68269
  pos -=
@@ -68372,11 +68288,6 @@
68372
68288
  addEffectNoteGlyph(noteGlyph, noteLine) {
68373
68289
  super.add(noteGlyph, noteLine);
68374
68290
  }
68375
- updateBeamingHelper(cx) {
68376
- if (this.beamingHelper) {
68377
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.upLineX, cx + this.x + this.downLineX);
68378
- }
68379
- }
68380
68291
  doLayout() {
68381
68292
  super.doLayout();
68382
68293
  const scoreRenderer = this.renderer;
@@ -68385,6 +68296,7 @@
68385
68296
  this._deadSlapped.renderer = this.renderer;
68386
68297
  this._deadSlapped.doLayout();
68387
68298
  this.width = this._deadSlapped.width;
68299
+ this.onTimeX = this.width / 2;
68388
68300
  }
68389
68301
  let aboveBeatEffectsY = 0;
68390
68302
  let belowBeatEffectsY = 0;
@@ -68503,7 +68415,6 @@
68503
68415
  * @internal
68504
68416
  */
68505
68417
  class ScoreRestGlyph extends MusicFontGlyph {
68506
- beamingHelper;
68507
68418
  constructor(x, y, duration) {
68508
68419
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
68509
68420
  }
@@ -68535,11 +68446,6 @@
68535
68446
  return MusicFontSymbol.None;
68536
68447
  }
68537
68448
  }
68538
- updateBeamingHelper(cx) {
68539
- if (this.beamingHelper) {
68540
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
68541
- }
68542
- }
68543
68449
  paint(cx, cy, canvas) {
68544
68450
  this.internalPaint(cx, cy, canvas, BeatSubElement.StandardNotationRests);
68545
68451
  }
@@ -69031,10 +68937,11 @@
69031
68937
  */
69032
68938
  class SlashNoteHeadGlyph extends MusicFontGlyph {
69033
68939
  beatEffects = new Map();
69034
- beamingHelper;
69035
68940
  noteHeadElement = NoteSubElement.SlashNoteHead;
69036
68941
  effectElement = BeatSubElement.SlashEffects;
69037
68942
  _symbol;
68943
+ upLineX = 0;
68944
+ downLineX = 0;
69038
68945
  constructor(x, y, duration, isGrace, beat) {
69039
68946
  super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, SlashNoteHeadGlyph.getSymbol(duration));
69040
68947
  this._symbol = SlashNoteHeadGlyph.getSymbol(duration);
@@ -69085,6 +68992,15 @@
69085
68992
  if (!Number.isNaN(minEffectY)) {
69086
68993
  this.renderer.registerBeatEffectOverflows(minEffectY, maxEffectY);
69087
68994
  }
68995
+ const symbol = this._symbol;
68996
+ const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
68997
+ ? this.renderer.smuflMetrics.stemUp.get(symbol).x
68998
+ : 0;
68999
+ this.upLineX = stemInfoUp;
69000
+ const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69001
+ ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69002
+ : 0;
69003
+ this.downLineX = stemInfoDown;
69088
69004
  }
69089
69005
  static getSymbol(duration) {
69090
69006
  switch (duration) {
@@ -69098,25 +69014,13 @@
69098
69014
  return MusicFontSymbol.NoteheadSlashHorizontalEnds;
69099
69015
  }
69100
69016
  }
69101
- updateBeamingHelper(cx) {
69102
- if (this.beamingHelper) {
69103
- const symbol = this._symbol;
69104
- const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
69105
- ? this.renderer.smuflMetrics.stemUp.get(symbol).x
69106
- : 0;
69107
- const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69108
- ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69109
- : 0;
69110
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + stemInfoUp, cx + this.x + stemInfoDown);
69111
- }
69112
- }
69113
69017
  }
69114
69018
 
69115
69019
  /**
69116
69020
  * @internal
69117
69021
  */
69118
69022
  class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69119
- _collisionOffset = -1e3;
69023
+ _collisionOffset = Number.NaN;
69120
69024
  _skipPaint = false;
69121
69025
  _whammy;
69122
69026
  noteHeads = null;
@@ -69167,22 +69071,19 @@
69167
69071
  getNoteY(note, requestedPosition) {
69168
69072
  return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
69169
69073
  }
69170
- updateBeamingHelper() {
69171
- if (this.noteHeads) {
69172
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
69074
+ applyRestCollisionOffset() {
69075
+ if (!this.restGlyph) {
69076
+ return;
69173
69077
  }
69174
- else if (this.restGlyph) {
69175
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
69176
- if (this.renderer.bar.isMultiVoice && this._collisionOffset === -1e3) {
69177
- this._collisionOffset = this.renderer.helpers.collisionHelper.applyRestCollisionOffset(this.container.beat, this.restGlyph.y, this.renderer.getScoreHeight(1));
69178
- this.y += this._collisionOffset;
69179
- const existingRests = this.renderer.helpers.collisionHelper.restDurationsByDisplayTime;
69180
- if (existingRests.has(this.container.beat.playbackStart) &&
69181
- existingRests.get(this.container.beat.playbackStart).has(this.container.beat.playbackDuration) &&
69182
- existingRests.get(this.container.beat.playbackStart).get(this.container.beat.playbackDuration) !==
69183
- this.container.beat.id) {
69184
- this._skipPaint = true;
69185
- }
69078
+ if (this.renderer.bar.isMultiVoice && Number.isNaN(this._collisionOffset)) {
69079
+ this._collisionOffset = this.renderer.collisionHelper.applyRestCollisionOffset(this.container.beat, this.restGlyph.y, this.renderer.getScoreHeight(1));
69080
+ this.y += this._collisionOffset;
69081
+ const existingRests = this.renderer.collisionHelper.restDurationsByDisplayTime;
69082
+ if (existingRests.has(this.container.beat.playbackStart) &&
69083
+ existingRests.get(this.container.beat.playbackStart).has(this.container.beat.playbackDuration) &&
69084
+ existingRests.get(this.container.beat.playbackStart).get(this.container.beat.playbackDuration) !==
69085
+ this.container.beat.id) {
69086
+ this._skipPaint = true;
69186
69087
  }
69187
69088
  }
69188
69089
  }
@@ -69202,7 +69103,6 @@
69202
69103
  const noteHeads = new ScoreNoteChordGlyph();
69203
69104
  this.noteHeads = noteHeads;
69204
69105
  noteHeads.beat = this.container.beat;
69205
- noteHeads.beamingHelper = this.beamingHelper;
69206
69106
  const ghost = new GhostNoteContainerGlyph(false);
69207
69107
  ghost.renderer = this.renderer;
69208
69108
  for (const note of this.container.beat.notes) {
@@ -69252,22 +69152,18 @@
69252
69152
  const restGlyph = new ScoreRestGlyph(0, sr.getScoreY(steps), this.container.beat.duration);
69253
69153
  this.restGlyph = restGlyph;
69254
69154
  restGlyph.beat = this.container.beat;
69255
- restGlyph.beamingHelper = this.beamingHelper;
69256
69155
  this.addNormal(restGlyph);
69257
69156
  if (this.renderer.bar.isMultiVoice) {
69258
69157
  if (this.container.beat.voice.index === 0) {
69259
69158
  const restSizes = BeamingHelper.computeLineHeightsForRest(this.container.beat.duration);
69260
69159
  const restTop = restGlyph.y - sr.getScoreHeight(restSizes[0]);
69261
69160
  const restBottom = restGlyph.y + sr.getScoreHeight(restSizes[1]);
69262
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69161
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69263
69162
  }
69264
69163
  else {
69265
- this.renderer.helpers.collisionHelper.registerRest(this.container.beat);
69164
+ this.renderer.collisionHelper.registerRest(this.container.beat);
69266
69165
  }
69267
69166
  }
69268
- if (this.beamingHelper) {
69269
- this.beamingHelper.applyRest(this.container.beat, steps);
69270
- }
69271
69167
  //
69272
69168
  // Note dots
69273
69169
  //
@@ -69282,14 +69178,24 @@
69282
69178
  }
69283
69179
  }
69284
69180
  super.doLayout();
69181
+ this.applyRestCollisionOffset();
69285
69182
  if (this.container.beat.isEmpty) {
69286
- this.centerX = this.width / 2;
69183
+ this.onTimeX = this.width / 2;
69184
+ this.middleX = this.onTimeX;
69185
+ this.stemX = this.middleX;
69287
69186
  }
69288
69187
  else if (this.restGlyph) {
69289
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
69188
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
69189
+ this.middleX = this.onTimeX;
69190
+ this.stemX = this.middleX;
69290
69191
  }
69291
69192
  else if (this.noteHeads) {
69292
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
69193
+ this.onTimeX = this.noteHeads.x + this.noteHeads.onTimeX;
69194
+ this.middleX = this.noteHeads.x + this.noteHeads.width / 2;
69195
+ const direction = this.renderer.getBeatDirection(this.container.beat);
69196
+ this.stemX =
69197
+ this.noteHeads.x +
69198
+ (direction === BeamDirection.Up ? this.noteHeads.upLineX : this.noteHeads.downLineX);
69293
69199
  }
69294
69200
  }
69295
69201
  _createBeatDot(line, group) {
@@ -69361,7 +69267,7 @@
69361
69267
  }
69362
69268
  const belowBeatEffects = this.noteHeads.belowBeatEffects;
69363
69269
  const aboveBeatEffects = this.noteHeads.aboveBeatEffects;
69364
- const outsideBeatEffects = this.beamingHelper.direction === BeamDirection.Up
69270
+ const outsideBeatEffects = this.renderer.getBeatDirection(this.container.beat) === BeamDirection.Up
69365
69271
  ? this.noteHeads.belowBeatEffects
69366
69272
  : this.noteHeads.aboveBeatEffects;
69367
69273
  if (n.isStaccato && !belowBeatEffects.has('Staccato')) {
@@ -69797,7 +69703,7 @@
69797
69703
  // if we have a line break we draw only a line until the end
69798
69704
  if (!endNoteRenderer || endNoteRenderer.staff !== startNoteRenderer.staff) {
69799
69705
  const endX = cx + startNoteRenderer.x + startNoteRenderer.width;
69800
- const noteValueToDraw = note.tieDestination.realValue;
69706
+ const noteValueToDraw = note.tieDestination.displayValue;
69801
69707
  startNoteRenderer.accidentalHelper.applyAccidentalForValue(note.beat, noteValueToDraw, false, true);
69802
69708
  const endY = cy +
69803
69709
  startNoteRenderer.y +
@@ -70333,6 +70239,22 @@
70333
70239
  doLayout() {
70334
70240
  this._effectSlur = null;
70335
70241
  this._effectEndSlur = null;
70242
+ // make space for flag
70243
+ const sr = this.renderer;
70244
+ const beat = this.beat;
70245
+ const isGrace = beat.graceType !== GraceType.None;
70246
+ if (sr.hasFlag(beat)) {
70247
+ const direction = this.renderer.getBeatDirection(beat);
70248
+ const scale = isGrace ? NoteHeadGlyph.GraceScale : 1;
70249
+ const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
70250
+ const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
70251
+ this._flagStretch = flagWidth;
70252
+ }
70253
+ else if (isGrace) {
70254
+ // always use flag size as spacing on grace notes
70255
+ const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
70256
+ this._flagStretch = graceSpacing;
70257
+ }
70336
70258
  super.doLayout();
70337
70259
  if (this._bend) {
70338
70260
  this._bend.renderer = this.renderer;
@@ -70342,7 +70264,7 @@
70342
70264
  }
70343
70265
  getBoundingBoxTop() {
70344
70266
  if (this._bend !== null) {
70345
- return Math.min(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70267
+ return ModelUtils.minBoundingBox(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70346
70268
  }
70347
70269
  else {
70348
70270
  return super.getBoundingBoxTop();
@@ -70350,7 +70272,7 @@
70350
70272
  }
70351
70273
  getBoundingBoxBottom() {
70352
70274
  if (this._bend !== null) {
70353
- return Math.max(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70275
+ return ModelUtils.maxBoundingBox(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70354
70276
  }
70355
70277
  else {
70356
70278
  return super.getBoundingBoxBottom();
@@ -70398,7 +70320,7 @@
70398
70320
  }
70399
70321
  // end effect slur on last beat
70400
70322
  if (!this._effectEndSlur && n.beat.isEffectSlurDestination && n.beat.effectSlurOrigin) {
70401
- const direction = this.onNotes.beamingHelper.direction;
70323
+ const direction = this.renderer.getBeatDirection(n.beat);
70402
70324
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70403
70325
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70404
70326
  const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
@@ -70437,6 +70359,15 @@
70437
70359
  }
70438
70360
  }
70439
70361
  }
70362
+ _flagStretch = 0;
70363
+ get postBeatStretch() {
70364
+ return super.postBeatStretch + this._flagStretch;
70365
+ }
70366
+ updateWidth() {
70367
+ super.updateWidth();
70368
+ this.width += this._flagStretch;
70369
+ this.minWidth += this._flagStretch;
70370
+ }
70440
70371
  }
70441
70372
 
70442
70373
  /**
@@ -70471,7 +70402,7 @@
70471
70402
  return this.smuflMetrics.oneStaffSpace;
70472
70403
  }
70473
70404
  get heightLineCount() {
70474
- return 5;
70405
+ return Math.max(5, this.bar.staff.standardNotationLineCount);
70475
70406
  }
70476
70407
  get drawnLineCount() {
70477
70408
  return this.bar.staff.standardNotationLineCount;
@@ -70528,7 +70459,9 @@
70528
70459
  slashY -= this.smuflMetrics.stemUp.has(symbol)
70529
70460
  ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
70530
70461
  : 0;
70531
- slashY -= this.smuflMetrics.standardStemLength + scale;
70462
+ if (!beat.isRest) {
70463
+ slashY -= this.smuflMetrics.standardStemLength + scale;
70464
+ }
70532
70465
  }
70533
70466
  return slashY;
70534
70467
  }
@@ -70537,7 +70470,7 @@
70537
70470
  return this.getBeatContainer(beat).onNotes.getNoteY(minNote, direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown);
70538
70471
  }
70539
70472
  let y = this.getScoreY(this.accidentalHelper.getMinSteps(beat));
70540
- if (direction === BeamDirection.Up) {
70473
+ if (direction === BeamDirection.Up && !beat.isRest) {
70541
70474
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
70542
70475
  y -= this.smuflMetrics.standardStemLength * scale;
70543
70476
  }
@@ -70575,14 +70508,14 @@
70575
70508
  getBeamDirection(helper) {
70576
70509
  return helper.direction;
70577
70510
  }
70578
- centerStaffStemY(helper) {
70511
+ centerStaffStemY(direction) {
70579
70512
  const isStandardFive = this.bar.staff.standardNotationLineCount === Staff.DefaultStandardNotationLineCount;
70580
70513
  if (isStandardFive) {
70581
70514
  // center on the middle line for a standard 5-line staff
70582
70515
  return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
70583
70516
  }
70584
70517
  // for other staff line counts, we align the stem either on the top or bottom line
70585
- if (helper.direction === BeamDirection.Up) {
70518
+ if (direction === BeamDirection.Up) {
70586
70519
  return this.getScoreY(this.bar.staff.standardNotationLineCount * 2);
70587
70520
  }
70588
70521
  return this.getScoreY(0);
@@ -70684,7 +70617,7 @@
70684
70617
  createLinePreBeatGlyphs() {
70685
70618
  // Clef
70686
70619
  let hasClef = false;
70687
- if (this.isFirstOfLine ||
70620
+ if (this.isFirstOfStaff ||
70688
70621
  this.bar.clef !== this.bar.previousBar.clef ||
70689
70622
  this.bar.clefOttava !== this.bar.previousBar.clefOttava) {
70690
70623
  // SMUFL: Clefs should be positioned such that the pitch the clef refers to is on the baseline
@@ -70889,6 +70822,25 @@
70889
70822
  */
70890
70823
  class SlashBeatContainerGlyph extends BeatContainerGlyph {
70891
70824
  _tiedNoteTie = null;
70825
+ doLayout() {
70826
+ // make space for flag
70827
+ const sr = this.renderer;
70828
+ const beat = this.beat;
70829
+ const isGrace = beat.graceType !== GraceType.None;
70830
+ if (sr.hasFlag(beat)) {
70831
+ const direction = this.renderer.getBeatDirection(beat);
70832
+ const scale = isGrace ? NoteHeadGlyph.GraceScale : 1;
70833
+ const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
70834
+ const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
70835
+ this._flagStretch = flagWidth;
70836
+ }
70837
+ else if (isGrace) {
70838
+ // always use flag size as spacing on grace notes
70839
+ const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
70840
+ this._flagStretch = graceSpacing;
70841
+ }
70842
+ super.doLayout();
70843
+ }
70892
70844
  createTies(n) {
70893
70845
  // create a tie if any effect requires it
70894
70846
  if (!n.isVisible) {
@@ -70905,17 +70857,21 @@
70905
70857
  this.addTie(tie);
70906
70858
  }
70907
70859
  }
70860
+ _flagStretch = 0;
70861
+ get postBeatStretch() {
70862
+ return super.postBeatStretch + this._flagStretch;
70863
+ }
70864
+ updateWidth() {
70865
+ super.updateWidth();
70866
+ this.width += this._flagStretch;
70867
+ this.minWidth += this._flagStretch;
70868
+ }
70908
70869
  }
70909
70870
 
70910
70871
  /**
70911
70872
  * @internal
70912
70873
  */
70913
70874
  class SlashRestGlyph extends ScoreRestGlyph {
70914
- updateBeamingHelper(cx) {
70915
- if (this.beamingHelper) {
70916
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
70917
- }
70918
- }
70919
70875
  paint(cx, cy, canvas) {
70920
70876
  super.internalPaint(cx, cy, canvas, BeatSubElement.SlashRests);
70921
70877
  }
@@ -71011,19 +70967,6 @@
71011
70967
  }
71012
70968
  return 0;
71013
70969
  }
71014
- updateBeamingHelper() {
71015
- if (this.noteHeads) {
71016
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
71017
- }
71018
- else if (this.deadSlapped) {
71019
- if (this.beamingHelper) {
71020
- this.beamingHelper.registerBeatLineX('slash', this.container.beat, this.container.x + this.x + this.deadSlapped.x + this.width, this.container.x + this.x + this.deadSlapped.x);
71021
- }
71022
- }
71023
- else if (this.restGlyph) {
71024
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
71025
- }
71026
- }
71027
70970
  doLayout() {
71028
70971
  // create glyphs
71029
70972
  const sr = this.renderer;
@@ -71041,18 +70984,13 @@
71041
70984
  const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat.duration, isGrace, this.container.beat);
71042
70985
  this.noteHeads = noteHeadGlyph;
71043
70986
  noteHeadGlyph.beat = this.container.beat;
71044
- noteHeadGlyph.beamingHelper = this.beamingHelper;
71045
70987
  this.addNormal(noteHeadGlyph);
71046
70988
  }
71047
70989
  else {
71048
70990
  const restGlyph = new SlashRestGlyph(0, glyphY, this.container.beat.duration);
71049
70991
  this.restGlyph = restGlyph;
71050
70992
  restGlyph.beat = this.container.beat;
71051
- restGlyph.beamingHelper = this.beamingHelper;
71052
70993
  this.addNormal(restGlyph);
71053
- if (this.beamingHelper) {
71054
- this.beamingHelper.applyRest(this.container.beat, 0);
71055
- }
71056
70994
  }
71057
70995
  }
71058
70996
  //
@@ -71065,17 +71003,25 @@
71065
71003
  }
71066
71004
  super.doLayout();
71067
71005
  if (this.container.beat.isEmpty) {
71068
- this.centerX = this.width / 2;
71006
+ this.onTimeX = this.width / 2;
71007
+ this.stemX = this.onTimeX;
71069
71008
  }
71070
71009
  else if (this.restGlyph) {
71071
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
71010
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71011
+ this.stemX = this.onTimeX;
71072
71012
  }
71073
71013
  else if (this.noteHeads) {
71074
- this.centerX = this.noteHeads.x + this.noteHeads.width / 2;
71014
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
71015
+ const direction = this.renderer.getBeatDirection(this.container.beat);
71016
+ this.stemX =
71017
+ this.noteHeads.x +
71018
+ (direction === BeamDirection.Up ? this.noteHeads.upLineX : this.noteHeads.downLineX);
71075
71019
  }
71076
71020
  else if (this.deadSlapped) {
71077
- this.centerX = this.deadSlapped.x + this.deadSlapped.width / 2;
71021
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
71022
+ this.stemX = this.onTimeX;
71078
71023
  }
71024
+ this.middleX = this.onTimeX;
71079
71025
  }
71080
71026
  }
71081
71027
 
@@ -71150,7 +71096,9 @@
71150
71096
  const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
71151
71097
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
71152
71098
  slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
71153
- slashY -= this.smuflMetrics.standardStemLength + scale;
71099
+ if (!beat.isRest) {
71100
+ slashY -= this.smuflMetrics.standardStemLength + scale;
71101
+ }
71154
71102
  return slashY;
71155
71103
  }
71156
71104
  getFlagBottomY(beat, _direction) {
@@ -71234,6 +71182,11 @@
71234
71182
  _?.[Symbol.dispose]?.();
71235
71183
  }
71236
71184
  }
71185
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
71186
+ if (h.voice?.index === 0) {
71187
+ super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
71188
+ }
71189
+ }
71237
71190
  }
71238
71191
 
71239
71192
  /**
@@ -71690,7 +71643,6 @@
71690
71643
  _deadSlapped = null;
71691
71644
  _isGrace;
71692
71645
  beat;
71693
- beamingHelper;
71694
71646
  maxStringNote = null;
71695
71647
  minStringNote = null;
71696
71648
  beatEffects = new Map();
@@ -71839,11 +71791,6 @@
71839
71791
  }
71840
71792
  }
71841
71793
  }
71842
- updateBeamingHelper(cx) {
71843
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71844
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71845
- }
71846
- }
71847
71794
  }
71848
71795
 
71849
71796
  /**
@@ -71851,7 +71798,6 @@
71851
71798
  */
71852
71799
  class TabRestGlyph extends MusicFontGlyph {
71853
71800
  _isVisibleRest;
71854
- beamingHelper;
71855
71801
  constructor(x, y, isVisibleRest, duration) {
71856
71802
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
71857
71803
  this._isVisibleRest = isVisibleRest;
@@ -71859,11 +71805,6 @@
71859
71805
  doLayout() {
71860
71806
  super.doLayout();
71861
71807
  }
71862
- updateBeamingHelper(cx) {
71863
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71864
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71865
- }
71866
- }
71867
71808
  paint(cx, cy, canvas) {
71868
71809
  if (this._isVisibleRest) {
71869
71810
  const _ = ElementStyleHelper.beat(canvas, BeatSubElement.GuitarTabRests, this.beat);
@@ -71920,7 +71861,6 @@
71920
71861
  slashNoteHead.effectElement = BeatSubElement.GuitarTabEffects;
71921
71862
  this.slash = slashNoteHead;
71922
71863
  slashNoteHead.beat = this.container.beat;
71923
- slashNoteHead.beamingHelper = this.beamingHelper;
71924
71864
  this.addNormal(slashNoteHead);
71925
71865
  beatEffects = slashNoteHead.beatEffects;
71926
71866
  }
@@ -71928,7 +71868,6 @@
71928
71868
  const tabNoteNumbers = new TabNoteChordGlyph(0, 0, isGrace);
71929
71869
  this.noteNumbers = tabNoteNumbers;
71930
71870
  tabNoteNumbers.beat = this.container.beat;
71931
- tabNoteNumbers.beamingHelper = this.beamingHelper;
71932
71871
  for (const note of this.container.beat.notes) {
71933
71872
  if (note.isVisible) {
71934
71873
  this._createNoteGlyph(note);
@@ -71950,9 +71889,9 @@
71950
71889
  // Note dots
71951
71890
  //
71952
71891
  if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== exports.TabRhythmMode.Hidden) {
71892
+ const y = tabRenderer.getFlagAndBarPos();
71953
71893
  for (let i = 0; i < this.container.beat.dots; i++) {
71954
- this.addEffect(new AugmentationDotGlyph(0, tabRenderer.lineOffset * tabRenderer.bar.staff.tuning.length +
71955
- tabRenderer.settings.notation.rhythmHeight));
71894
+ this.addEffect(new AugmentationDotGlyph(0, y));
71956
71895
  }
71957
71896
  }
71958
71897
  }
@@ -71962,7 +71901,6 @@
71962
71901
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71963
71902
  this.restGlyph = restGlyph;
71964
71903
  restGlyph.beat = this.container.beat;
71965
- restGlyph.beamingHelper = this.beamingHelper;
71966
71904
  this.addNormal(restGlyph);
71967
71905
  //
71968
71906
  // Note dots
@@ -71988,30 +71926,21 @@
71988
71926
  this.width = w;
71989
71927
  this.computedWidth = w;
71990
71928
  if (this.container.beat.isEmpty) {
71991
- this.centerX = this.width / 2;
71929
+ this.onTimeX = this.width / 2;
71992
71930
  }
71993
71931
  else if (this.restGlyph) {
71994
- this.centerX = this.restGlyph.x + this.restGlyph.width / 2;
71932
+ this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71995
71933
  }
71996
71934
  else if (this.noteNumbers) {
71997
- this.centerX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
71935
+ this.onTimeX = this.noteNumbers.x + this.noteNumbers.noteStringWidth / 2;
71998
71936
  }
71999
71937
  else if (this.slash) {
72000
- this.centerX = this.slash.x + this.slash.width / 2;
71938
+ this.onTimeX = this.slash.x + this.slash.width / 2;
72001
71939
  }
71940
+ this.middleX = this.onTimeX;
71941
+ this.stemX = this.middleX;
72002
71942
  for (const g of centeredEffectGlyphs) {
72003
- g.x = this.centerX;
72004
- }
72005
- }
72006
- updateBeamingHelper() {
72007
- if (this.noteNumbers) {
72008
- this.noteNumbers.updateBeamingHelper(this.container.x + this.x);
72009
- }
72010
- else if (this.restGlyph) {
72011
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
72012
- }
72013
- else if (this.slash) {
72014
- this.slash.updateBeamingHelper(this.container.x + this.x);
71943
+ g.x = this.onTimeX;
72015
71944
  }
72016
71945
  }
72017
71946
  _createNoteGlyph(n) {
@@ -72024,7 +71953,7 @@
72024
71953
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
72025
71954
  const topY = noteNumberGlyph.y - noteNumberGlyph.height / 2;
72026
71955
  const bottomY = topY + noteNumberGlyph.height;
72027
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
71956
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
72028
71957
  const minString = tr.minString;
72029
71958
  const maxString = tr.maxString;
72030
71959
  if (Number.isNaN(minString) || minString < n.string) {
@@ -72296,7 +72225,7 @@
72296
72225
  }
72297
72226
  createLinePreBeatGlyphs() {
72298
72227
  // Clef
72299
- if (this.isFirstOfLine) {
72228
+ if (this.isFirstOfStaff) {
72300
72229
  const center = (this.bar.staff.tuning.length - 1) / 2;
72301
72230
  this.createStartSpacing();
72302
72231
  this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
@@ -72387,14 +72316,14 @@
72387
72316
  // currently only used for duplets
72388
72317
  return this.getFlagAndBarPos();
72389
72318
  }
72390
- shouldPaintFlag(beat, h) {
72391
- if (!super.shouldPaintFlag(beat, h)) {
72319
+ shouldPaintFlag(beat) {
72320
+ if (!super.shouldPaintFlag(beat)) {
72392
72321
  return false;
72393
72322
  }
72394
72323
  if (beat.graceType !== GraceType.None) {
72395
72324
  return false;
72396
72325
  }
72397
- return this.drawBeamHelperAsFlags(h);
72326
+ return true;
72398
72327
  }
72399
72328
  paintBeamingStem(beat, cy, x, topY, bottomY, canvas) {
72400
72329
  if (bottomY < topY) {