@coderline/alphatab 1.8.0-alpha.1640 → 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.1640 (develop, build 1640)
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.1640';
213
- static date = '2025-12-10T02:19:08.776Z';
214
- static commit = '343f59ee6b39b4f3a41636b3c33db34bccf631f9';
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
  */
@@ -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
48422
  const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
48561
- let postBeatStretch = this.onNotes.computedWidth - this.onNotes.onTimeX;
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
- }
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;
@@ -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();
@@ -48746,6 +48579,26 @@
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
  /**
@@ -56504,6 +56357,31 @@
56504
56357
  EffectBarGlyphSizing[EffectBarGlyphSizing["FullBar"] = 5] = "FullBar";
56505
56358
  })(EffectBarGlyphSizing || (EffectBarGlyphSizing = {}));
56506
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
+
56507
56385
  /**
56508
56386
  * @internal
56509
56387
  */
@@ -56631,39 +56509,6 @@
56631
56509
  }
56632
56510
  }
56633
56511
 
56634
- /**
56635
- * Lists the different position modes for {@link BarRendererBase.getBeatX}
56636
- * @internal
56637
- */
56638
- var BeatXPosition;
56639
- (function (BeatXPosition) {
56640
- /**
56641
- * Gets the pre-notes position which is located before the accidentals
56642
- */
56643
- BeatXPosition[BeatXPosition["PreNotes"] = 0] = "PreNotes";
56644
- /**
56645
- * Gets the on-notes position which is located after the accidentals but before the note heads.
56646
- */
56647
- BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
56648
- /**
56649
- * Gets the middle-notes position which is located after in the exact center of the note heads.
56650
- */
56651
- BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
56652
- /**
56653
- * Gets position of the stem for this beat
56654
- */
56655
- BeatXPosition[BeatXPosition["Stem"] = 3] = "Stem";
56656
- /**
56657
- * Get the post-notes position which is located at after the note heads.
56658
- */
56659
- BeatXPosition[BeatXPosition["PostNotes"] = 4] = "PostNotes";
56660
- /**
56661
- * Get the end-beat position which is located at the end of the beat. This position is almost
56662
- * equal to the pre-notes position of the next beat.
56663
- */
56664
- BeatXPosition[BeatXPosition["EndBeat"] = 5] = "EndBeat";
56665
- })(BeatXPosition || (BeatXPosition = {}));
56666
-
56667
56512
  /**
56668
56513
  * @internal
56669
56514
  */
@@ -57262,6 +57107,93 @@
57262
57107
  }
57263
57108
  }
57264
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
+
57265
57197
  /**
57266
57198
  * @internal
57267
57199
  */
@@ -57500,17 +57432,6 @@
57500
57432
  }
57501
57433
  }
57502
57434
 
57503
- /**
57504
- * This simple glyph allows to put an empty region in to a BarRenderer.
57505
- * @internal
57506
- */
57507
- class SpacingGlyph extends Glyph {
57508
- constructor(x, y, width) {
57509
- super(x, y);
57510
- this.width = width;
57511
- }
57512
- }
57513
-
57514
57435
  /**
57515
57436
  * This glyph allows to group several other glyphs to be
57516
57437
  * drawn at the same x position
@@ -57526,34 +57447,20 @@
57526
57447
  const glyphs = this.glyphs;
57527
57448
  if (glyphs) {
57528
57449
  for (const g of glyphs) {
57529
- // only count real visual glyphs
57530
- if (g instanceof SpacingGlyph) {
57531
- continue;
57532
- }
57533
- const gTop = g.getBoundingBoxTop();
57534
- if (Number.isNaN(top) || gTop < top) {
57535
- top = gTop;
57536
- }
57450
+ top = ModelUtils.minBoundingBox(top, g.getBoundingBoxTop());
57537
57451
  }
57538
57452
  }
57539
- return Number.isNaN(top) ? this.y : top;
57453
+ return top;
57540
57454
  }
57541
57455
  getBoundingBoxBottom() {
57542
57456
  let bottom = Number.NaN;
57543
57457
  const glyphs = this.glyphs;
57544
57458
  if (glyphs) {
57545
57459
  for (const g of glyphs) {
57546
- // only count real visual glyphs
57547
- if (g instanceof SpacingGlyph) {
57548
- continue;
57549
- }
57550
- const gBottom = g.getBoundingBoxBottom();
57551
- if (Number.isNaN(bottom) || gBottom > bottom) {
57552
- bottom = gBottom;
57553
- }
57460
+ bottom = ModelUtils.maxBoundingBox(bottom, g.getBoundingBoxBottom());
57554
57461
  }
57555
57462
  }
57556
- return Number.isNaN(bottom) ? this.y + this.height : bottom;
57463
+ return bottom;
57557
57464
  }
57558
57465
  doLayout() {
57559
57466
  if (!this.glyphs || this.glyphs.length === 0) {
@@ -57969,6 +57876,38 @@
57969
57876
  }
57970
57877
  }
57971
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
+
57972
57911
  /**
57973
57912
  * @internal
57974
57913
  */
@@ -59587,12 +59526,12 @@
59587
59526
  // of the next glyph
59588
59527
  if (i > 0) {
59589
59528
  const beatWidth = currentBeatGlyph.x - beatGlyphs[i - 1].x;
59590
- beatGlyphs[i - 1].scaleToWidth(beatWidth);
59529
+ beatGlyphs[i - 1].width = beatWidth;
59591
59530
  }
59592
59531
  // for the last glyph size based on the full width
59593
59532
  if (i === j - 1) {
59594
59533
  const beatWidth = this.width - beatGlyphs[beatGlyphs.length - 1].x;
59595
- currentBeatGlyph.scaleToWidth(beatWidth);
59534
+ currentBeatGlyph.width = beatWidth;
59596
59535
  }
59597
59536
  }
59598
59537
  }
@@ -61292,7 +61231,7 @@
61292
61231
  if (!masterBarBoundsLookup.has(renderer.bar.masterBar.index)) {
61293
61232
  masterBarBounds = new MasterBarBounds();
61294
61233
  masterBarBounds.index = renderer.bar.masterBar.index;
61295
- masterBarBounds.isFirstOfLine = renderer.isFirstOfLine;
61234
+ masterBarBounds.isFirstOfLine = renderer.isFirstOfStaff;
61296
61235
  masterBarBounds.realBounds = new Bounds();
61297
61236
  masterBarBounds.realBounds.x = x + renderer.x;
61298
61237
  masterBarBounds.realBounds.y = realTop;
@@ -61824,11 +61763,9 @@
61824
61763
  * @internal
61825
61764
  */
61826
61765
  class BeatOnNoteGlyphBase extends BeatGlyphBase {
61827
- beamingHelper;
61828
61766
  onTimeX = 0;
61829
61767
  middleX = 0;
61830
- updateBeamingHelper() {
61831
- }
61768
+ stemX = 0;
61832
61769
  buildBoundingsLookup(_beatBounds, _cx, _cy) {
61833
61770
  }
61834
61771
  getNoteX(_note, _requestedPosition) {
@@ -61970,14 +61907,6 @@
61970
61907
  }
61971
61908
  }
61972
61909
 
61973
- /**
61974
- * @internal
61975
- */
61976
- class BeatLinePositions {
61977
- staffId = '';
61978
- up = 0;
61979
- down = 0;
61980
- }
61981
61910
  /**
61982
61911
  * @internal
61983
61912
  */
@@ -62008,10 +61937,7 @@
62008
61937
  */
62009
61938
  class BeamingHelper {
62010
61939
  _staff;
62011
- _beatLineXPositions = new Map();
62012
61940
  _renderer;
62013
- _firstNonRestBeat = null;
62014
- _lastNonRestBeat = null;
62015
61941
  voice = null;
62016
61942
  beats = [];
62017
61943
  shortestDuration = Duration.QuadrupleWhole;
@@ -62021,6 +61947,7 @@
62021
61947
  */
62022
61948
  hasTuplet = false;
62023
61949
  slashBeats = [];
61950
+ restBeats = [];
62024
61951
  lowestNoteInHelper = null;
62025
61952
  _lowestNoteCompareValueInHelper = -1;
62026
61953
  highestNoteInHelper = null;
@@ -62028,25 +61955,21 @@
62028
61955
  invertBeamDirection = false;
62029
61956
  preferredBeamDirection = null;
62030
61957
  graceType = GraceType.None;
62031
- minRestSteps = null;
62032
- beatOfMinRestSteps = null;
62033
- maxRestSteps = null;
62034
- beatOfMaxRestSteps = null;
62035
61958
  get isRestBeamHelper() {
62036
61959
  return this.beats.length === 1 && this.beats[0].isRest;
62037
61960
  }
62038
- hasLine(forceFlagOnSingleBeat, beat) {
62039
- return ((forceFlagOnSingleBeat && this._beatHasLine(beat)) ||
62040
- (!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)));
62041
61964
  }
62042
- _beatHasLine(beat) {
61965
+ _beatHasStem(beat) {
62043
61966
  return beat.duration > Duration.Whole;
62044
61967
  }
62045
61968
  hasFlag(forceFlagOnSingleBeat, beat) {
62046
- return ((forceFlagOnSingleBeat && this._beatHasFlag(beat)) ||
62047
- (!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])));
62048
61971
  }
62049
- _beatHasFlag(beat) {
61972
+ static beatHasFlag(beat) {
62050
61973
  return (!beat.deadSlapped && !beat.isRest && (beat.duration > Duration.Quarter || beat.graceType !== GraceType.None));
62051
61974
  }
62052
61975
  constructor(staff, renderer) {
@@ -62054,38 +61977,12 @@
62054
61977
  this._renderer = renderer;
62055
61978
  this.beats = [];
62056
61979
  }
62057
- getBeatLineX(beat, direction) {
62058
- direction = direction ?? this.direction;
62059
- if (this.hasBeatLineX(beat)) {
62060
- if (direction === BeamDirection.Up) {
62061
- return this._beatLineXPositions.get(beat.index).up;
62062
- }
62063
- return this._beatLineXPositions.get(beat.index).down;
62064
- }
62065
- return 0;
62066
- }
62067
- hasBeatLineX(beat) {
62068
- return this._beatLineXPositions.has(beat.index);
62069
- }
62070
- registerBeatLineX(staffId, beat, up, down) {
62071
- const positions = this._getOrCreateBeatPositions(beat);
62072
- positions.staffId = staffId;
62073
- positions.up = up;
62074
- positions.down = down;
61980
+ alignWithBeats() {
62075
61981
  for (const v of this.drawingInfos.values()) {
62076
- if (v.startBeat === beat) {
62077
- v.startX = this.getBeatLineX(beat);
62078
- }
62079
- else if (v.endBeat === beat) {
62080
- v.endX = this.getBeatLineX(beat);
62081
- }
62082
- }
62083
- }
62084
- _getOrCreateBeatPositions(beat) {
62085
- if (!this._beatLineXPositions.has(beat.index)) {
62086
- 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();
62087
61985
  }
62088
- return this._beatLineXPositions.get(beat.index);
62089
61986
  }
62090
61987
  direction = BeamDirection.Up;
62091
61988
  finish() {
@@ -62151,36 +62048,6 @@
62151
62048
  }
62152
62049
  return [0, 0];
62153
62050
  }
62154
- /**
62155
- * Registers a rest beat within the accidental helper so the rest
62156
- * symbol is considered properly during beaming.
62157
- * @param beat The rest beat.
62158
- * @param steps The steps on which the rest symbol is placed
62159
- */
62160
- applyRest(beat, steps) {
62161
- // do not accept rests after the last beat which has notes
62162
- if ((this._lastNonRestBeat && beat.index >= this._lastNonRestBeat.index) ||
62163
- (this._firstNonRestBeat && beat.index <= this._firstNonRestBeat.index)) {
62164
- return;
62165
- }
62166
- // correct the line of the glyph to a note which would
62167
- // be placed at the upper / lower end of the glyph.
62168
- let aboveRest = steps;
62169
- let belowRest = steps;
62170
- const offsets = BeamingHelper.computeLineHeightsForRest(beat.duration);
62171
- aboveRest -= offsets[0];
62172
- belowRest += offsets[1];
62173
- const minRestSteps = this.minRestSteps;
62174
- const maxRestSteps = this.maxRestSteps;
62175
- if (minRestSteps === null || minRestSteps > aboveRest) {
62176
- this.minRestSteps = aboveRest;
62177
- this.beatOfMinRestSteps = beat;
62178
- }
62179
- if (maxRestSteps === null || maxRestSteps < belowRest) {
62180
- this.maxRestSteps = belowRest;
62181
- this.beatOfMaxRestSteps = beat;
62182
- }
62183
- }
62184
62051
  _invert(direction) {
62185
62052
  if (!this.invertBeamDirection) {
62186
62053
  return direction;
@@ -62244,14 +62111,13 @@
62244
62111
  if (this.shortestDuration < beat.duration) {
62245
62112
  this.shortestDuration = beat.duration;
62246
62113
  }
62247
- if (!this._firstNonRestBeat) {
62248
- this._firstNonRestBeat = beat;
62249
- }
62250
- this._lastNonRestBeat = beat;
62251
62114
  }
62252
62115
  else if (this.beats.length === 0) {
62253
62116
  this.beats.push(beat);
62254
62117
  }
62118
+ else {
62119
+ this.restBeats.push(beat);
62120
+ }
62255
62121
  if (beat.slashed) {
62256
62122
  this.slashBeats.push(beat);
62257
62123
  }
@@ -62360,19 +62226,6 @@
62360
62226
  get beatOfHighestNote() {
62361
62227
  return this.highestNoteInHelper.beat;
62362
62228
  }
62363
- /**
62364
- * Returns whether the the position of the given beat, was registered by the staff of the given ID
62365
- * @param staffId
62366
- * @param beat
62367
- * @returns
62368
- */
62369
- isPositionFrom(staffId, beat) {
62370
- if (!this._beatLineXPositions.has(beat.index)) {
62371
- return true;
62372
- }
62373
- return (this._beatLineXPositions.get(beat.index).staffId === staffId ||
62374
- !this._beatLineXPositions.get(beat.index).staffId);
62375
- }
62376
62229
  drawingInfos = new Map();
62377
62230
  }
62378
62231
 
@@ -62524,8 +62377,8 @@
62524
62377
  */
62525
62378
  class BarHelpers {
62526
62379
  _renderer;
62380
+ _beamHelperLookup = new Map();
62527
62381
  beamHelpers = [];
62528
- beamHelperLookup = [];
62529
62382
  collisionHelper;
62530
62383
  preferredBeamDirection = null;
62531
62384
  constructor(renderer) {
@@ -62540,7 +62393,6 @@
62540
62393
  for (let i = 0, j = bar.voices.length; i < j; i++) {
62541
62394
  const v = bar.voices[i];
62542
62395
  this.beamHelpers.push([]);
62543
- this.beamHelperLookup.push(new Map());
62544
62396
  for (let k = 0, l = v.beats.length; k < l; k++) {
62545
62397
  const b = v.beats[k];
62546
62398
  let helperForBeat;
@@ -62549,6 +62401,9 @@
62549
62401
  }
62550
62402
  else {
62551
62403
  helperForBeat = currentBeamHelper;
62404
+ if (currentGraceBeamHelper) {
62405
+ currentGraceBeamHelper.finish();
62406
+ }
62552
62407
  currentGraceBeamHelper = null;
62553
62408
  }
62554
62409
  // if a new beaming helper was started, we close our tuplet grouping as well
@@ -62569,7 +62424,7 @@
62569
62424
  }
62570
62425
  this.beamHelpers[v.index].push(helperForBeat);
62571
62426
  }
62572
- this.beamHelperLookup[v.index].set(b.index, helperForBeat);
62427
+ this._beamHelperLookup.set(b.id, helperForBeat);
62573
62428
  }
62574
62429
  if (currentBeamHelper) {
62575
62430
  currentBeamHelper.finish();
@@ -62582,7 +62437,7 @@
62582
62437
  }
62583
62438
  }
62584
62439
  getBeamingHelperForBeat(beat) {
62585
- return this.beamHelperLookup[beat.voice.index].get(beat.index);
62440
+ return this._beamHelperLookup.has(beat.id) ? this._beamHelperLookup.get(beat.id) : undefined;
62586
62441
  }
62587
62442
  }
62588
62443
 
@@ -62692,6 +62547,9 @@
62692
62547
  return this._contentBottomOverflow + this.bottomEffects.height;
62693
62548
  }
62694
62549
  helpers;
62550
+ get collisionHelper() {
62551
+ return this.helpers.collisionHelper;
62552
+ }
62695
62553
  /**
62696
62554
  * Gets or sets whether this renderer is linked to the next one
62697
62555
  * by some glyphs like a vibrato effect
@@ -62751,6 +62609,11 @@
62751
62609
  for (const container of this._voiceContainers.values()) {
62752
62610
  container.scaleToWidth(containerWidth);
62753
62611
  }
62612
+ for (const v of this.helpers.beamHelpers) {
62613
+ for (const h of v) {
62614
+ h.alignWithBeats();
62615
+ }
62616
+ }
62754
62617
  this._postBeatGlyphs.x = this._preBeatGlyphs.x + this._preBeatGlyphs.width + containerWidth;
62755
62618
  this.width = width;
62756
62619
  this.topEffects.alignGlyphs();
@@ -62779,10 +62642,13 @@
62779
62642
  get barDisplayWidth() {
62780
62643
  return this.staff.system.staves.length > 1 ? this.bar.masterBar.displayWidth : this.bar.displayWidth;
62781
62644
  }
62782
- wasFirstOfLine = false;
62783
- get isFirstOfLine() {
62645
+ wasFirstOfStaff = false;
62646
+ get isFirstOfStaff() {
62784
62647
  return this.index === 0;
62785
62648
  }
62649
+ get isLastOfStaff() {
62650
+ return this.index === this.staff.barRenderers.length - 1;
62651
+ }
62786
62652
  get isLast() {
62787
62653
  return !this.bar || this.bar.index === this.scoreRenderer.layout.lastBarIndex;
62788
62654
  }
@@ -62997,6 +62863,18 @@
62997
62863
  }
62998
62864
  }
62999
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
+ }
63000
62878
  const beatEffectsMinY = this.beatEffectsMinY;
63001
62879
  if (!Number.isNaN(beatEffectsMinY)) {
63002
62880
  const beatEffectTopOverflow = -beatEffectsMinY;
@@ -63050,7 +62928,6 @@
63050
62928
  g.renderer = this;
63051
62929
  g.preNotes.renderer = this;
63052
62930
  g.onNotes.renderer = this;
63053
- g.onNotes.beamingHelper = this.helpers.beamHelperLookup[g.beat.voice.index].get(g.beat.index);
63054
62931
  this.getVoiceContainer(g.beat.voice).addGlyph(g);
63055
62932
  }
63056
62933
  getVoiceContainer(voice) {
@@ -63129,7 +63006,7 @@
63129
63006
  this._postBeatGlyphs.addGlyph(g);
63130
63007
  }
63131
63008
  createPreBeatGlyphs() {
63132
- this.wasFirstOfLine = this.isFirstOfLine;
63009
+ this.wasFirstOfStaff = this.isFirstOfStaff;
63133
63010
  }
63134
63011
  createBeatGlyphs() {
63135
63012
  for (const voice of this.bar.voices) {
@@ -63156,26 +63033,7 @@
63156
63033
  getBeatX(beat, requestedPosition = BeatXPosition.PreNotes, useSharedSizes = false) {
63157
63034
  const container = this.getBeatContainer(beat);
63158
63035
  if (container) {
63159
- switch (requestedPosition) {
63160
- case BeatXPosition.PreNotes:
63161
- return container.voiceContainer.x + container.x;
63162
- case BeatXPosition.OnNotes:
63163
- return container.voiceContainer.x + container.x + container.onNotes.x;
63164
- case BeatXPosition.MiddleNotes:
63165
- return container.voiceContainer.x + container.x + container.onNotes.x + container.onNotes.middleX;
63166
- case BeatXPosition.Stem:
63167
- const offset = container.onNotes.beamingHelper
63168
- ? container.onNotes.beamingHelper.getBeatLineX(beat)
63169
- : container.onNotes.x + container.onNotes.width / 2;
63170
- return container.voiceContainer.x + offset;
63171
- case BeatXPosition.PostNotes:
63172
- const onNoteSize = useSharedSizes
63173
- ? (this.layoutingInfo.getBeatSizes(beat)?.onBeatSize ?? container.onNotes.width)
63174
- : container.onNotes.width;
63175
- return container.voiceContainer.x + container.x + container.onNotes.x + onNoteSize;
63176
- case BeatXPosition.EndBeat:
63177
- return container.voiceContainer.x + container.x + container.width;
63178
- }
63036
+ return container.voiceContainer.x + container.x + container.getBeatX(requestedPosition, useSharedSizes);
63179
63037
  }
63180
63038
  return 0;
63181
63039
  }
@@ -63210,7 +63068,7 @@
63210
63068
  this.updateSizes();
63211
63069
  // there are some glyphs which are shown only for renderers at the line start, so we simply recreate them
63212
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
63213
- if ((this.wasFirstOfLine && !this.isFirstOfLine) || (!this.wasFirstOfLine && this.isFirstOfLine)) {
63071
+ if ((this.wasFirstOfStaff && !this.isFirstOfStaff) || (!this.wasFirstOfStaff && this.isFirstOfStaff)) {
63214
63072
  this.recreatePreBeatGlyphs();
63215
63073
  this._postBeatGlyphs.doLayout();
63216
63074
  }
@@ -63247,7 +63105,7 @@
63247
63105
  completeBeamingHelper(_helper) {
63248
63106
  }
63249
63107
  getBeatDirection(beat) {
63250
- return this.helpers.getBeamingHelperForBeat(beat).direction;
63108
+ return this.helpers.getBeamingHelperForBeat(beat)?.direction ?? BeamDirection.Up;
63251
63109
  }
63252
63110
  }
63253
63111
 
@@ -65614,8 +65472,18 @@
65614
65472
  }
65615
65473
  const lineRenderer = this.renderer;
65616
65474
  const lineYOffset = lineRenderer.smuflMetrics.staffLineThickness;
65617
- const top = this.y - lineYOffset;
65618
- 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
+ }
65619
65487
  const h = bottom - top;
65620
65488
  // round up to have pixel-aligned bar lines, x-shift will be used during rendering
65621
65489
  // to avoid shifting again all glyphs
@@ -65701,6 +65569,54 @@
65701
65569
  }
65702
65570
  }
65703
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
+
65704
65620
  /**
65705
65621
  * @internal
65706
65622
  */
@@ -65736,6 +65652,23 @@
65736
65652
  }
65737
65653
  }
65738
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
+
65739
65672
  /**
65740
65673
  * This is a base class for any bar renderer which renders music notation on a staff
65741
65674
  * with lines like Standard Notation, Guitar Tablatures and Slash Notation.
@@ -65859,7 +65792,7 @@
65859
65792
  if (this.hasVoiceContainer(voice)) {
65860
65793
  const container = this.getVoiceContainer(voice);
65861
65794
  for (const tupletGroup of container.tupletGroups) {
65862
- this._paintTupletHelper(cx + this.beatGlyphsStart, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65795
+ this._paintTupletHelper(cx, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65863
65796
  }
65864
65797
  }
65865
65798
  }
@@ -65933,26 +65866,27 @@
65933
65866
  // check if we need to paint simple footer
65934
65867
  const offset = this.tupletOffset;
65935
65868
  const size = this.tupletSize;
65869
+ const shift = offset + size * 0.5;
65936
65870
  const _ = ElementStyleHelper.beat(canvas, beatElement, h.beats[0]);
65937
65871
  try {
65938
65872
  const l = canvas.lineWidth;
65939
65873
  canvas.lineWidth = this.smuflMetrics.tupletBracketThickness;
65940
65874
  if (h.beats.length === 1 || !h.isFull) {
65941
65875
  for (const beat of h.beats) {
65942
- const beamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(beat.index);
65876
+ const beamingHelper = this.helpers.getBeamingHelperForBeat(beat);
65943
65877
  if (!beamingHelper) {
65944
65878
  continue;
65945
65879
  }
65946
65880
  const direction = this.getTupletBeamDirection(beamingHelper);
65947
- const tupletX = beamingHelper.getBeatLineX(beat);
65881
+ const tupletX = this.getBeatX(beat, BeatXPosition.Stem);
65948
65882
  let tupletY = this.calculateBeamYWithDirection(beamingHelper, tupletX, direction);
65949
65883
  if (direction === BeamDirection.Down) {
65950
- tupletY += offset + size;
65884
+ tupletY += shift;
65951
65885
  }
65952
65886
  else {
65953
- tupletY -= offset + size;
65887
+ tupletY -= shift;
65954
65888
  }
65955
- 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);
65956
65890
  }
65957
65891
  }
65958
65892
  else {
@@ -65982,12 +65916,12 @@
65982
65916
  }
65983
65917
  //
65984
65918
  // Calculate the overall area of the tuplet bracket
65985
- const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes) - this.beatGlyphsStart;
65986
- 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);
65987
65921
  //
65988
65922
  // calculate the y positions for our bracket
65989
- const firstNonRestBeamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(firstNonRestBeat.index);
65990
- 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);
65991
65925
  const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
65992
65926
  let startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
65993
65927
  let endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
@@ -65996,7 +65930,6 @@
65996
65930
  endY = startY;
65997
65931
  }
65998
65932
  // align line centered in available space
65999
- const shift = offset + size * 0.5;
66000
65933
  if (direction === BeamDirection.Down) {
66001
65934
  startY += shift;
66002
65935
  endY += shift;
@@ -66061,14 +65994,24 @@
66061
65994
  paintBeams(cx, cy, canvas, flagsElement, beamsElement) {
66062
65995
  for (const v of this.helpers.beamHelpers) {
66063
65996
  for (const h of v) {
66064
- this._paintBeamHelper(cx + this.beatGlyphsStart, cy, canvas, h, flagsElement, beamsElement);
65997
+ this.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
66065
65998
  }
66066
65999
  }
66067
66000
  }
66068
66001
  drawBeamHelperAsFlags(h) {
66069
66002
  return h.beats.length === 1;
66070
66003
  }
66071
- _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) {
66072
66015
  canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
66073
66016
  if (!h.isRestBeamHelper) {
66074
66017
  if (this.drawBeamHelperAsFlags(h)) {
@@ -66079,7 +66022,7 @@
66079
66022
  }
66080
66023
  }
66081
66024
  }
66082
- shouldPaintFlag(beat, h) {
66025
+ shouldPaintFlag(beat) {
66083
66026
  // no flags for bend grace beats
66084
66027
  if (beat.graceType === GraceType.BendGrace) {
66085
66028
  return false;
@@ -66087,10 +66030,6 @@
66087
66030
  if (beat.deadSlapped) {
66088
66031
  return false;
66089
66032
  }
66090
- // we don't have an X-position: cannot paint a flag
66091
- if (!h.hasBeatLineX(beat)) {
66092
- return false;
66093
- }
66094
66033
  // no flags for any grace notes on songbook mode
66095
66034
  if (beat.graceType !== GraceType.None && this.settings.notation.notationMode === exports.NotationMode.SongBook) {
66096
66035
  return false;
@@ -66105,14 +66044,14 @@
66105
66044
  }
66106
66045
  paintFlag(cx, cy, canvas, h, flagsElement) {
66107
66046
  for (const beat of h.beats) {
66108
- if (!this.shouldPaintFlag(beat, h)) {
66047
+ if (!this.shouldPaintFlag(beat)) {
66109
66048
  continue;
66110
66049
  }
66111
66050
  const isGrace = beat.graceType !== GraceType.None;
66112
66051
  //
66113
66052
  // draw line
66114
66053
  //
66115
- const beatLineX = h.getBeatLineX(beat);
66054
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66116
66055
  const direction = this.getBeamDirection(h);
66117
66056
  const topY = cy + this.y + this.getFlagTopY(beat, direction);
66118
66057
  const bottomY = cy + this.y + this.getFlagBottomY(beat, direction);
@@ -66123,7 +66062,7 @@
66123
66062
  else {
66124
66063
  flagY = topY;
66125
66064
  }
66126
- if (!h.hasLine(true, beat)) {
66065
+ if (!h.hasStem(true, beat)) {
66127
66066
  continue;
66128
66067
  }
66129
66068
  this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, topY, bottomY, canvas);
@@ -66209,10 +66148,10 @@
66209
66148
  }
66210
66149
  for (let i = 0, j = h.beats.length; i < j; i++) {
66211
66150
  const beat = h.beats[i];
66212
- if (!h.hasBeatLineX(beat) || beat.deadSlapped) {
66151
+ if (beat.deadSlapped) {
66213
66152
  continue;
66214
66153
  }
66215
- const beatLineX = h.getBeatLineX(beat);
66154
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66216
66155
  const y1 = cy + this.y + this.getBarLineStart(beat, direction);
66217
66156
  // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
66218
66157
  // when combining stems and beams on sub-pixel level
@@ -66250,7 +66189,7 @@
66250
66189
  barEndY = barY + this.calculateBeamY(h, barEndX);
66251
66190
  LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66252
66191
  // end part
66253
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66192
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66254
66193
  barStartX = barEndX - brokenBarOffset;
66255
66194
  barStartY = barY + this.calculateBeamY(h, barStartX);
66256
66195
  barEndY = barY + this.calculateBeamY(h, barEndX);
@@ -66260,7 +66199,7 @@
66260
66199
  if (isFullBarJoin) {
66261
66200
  // full bar?
66262
66201
  barStartX = beatLineX;
66263
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66202
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66264
66203
  }
66265
66204
  else if (i === 0 || !BeamingHelper.isFullBarJoin(h.beats[i - 1], beat, barIndex)) {
66266
66205
  barStartX = beatLineX;
@@ -66295,7 +66234,7 @@
66295
66234
  }
66296
66235
  }
66297
66236
  if (h.graceType === GraceType.BeforeBeat) {
66298
- const beatLineX = h.getBeatLineX(h.beats[0]);
66237
+ const beatLineX = this.getBeatX(h.beats[0], BeatXPosition.Stem);
66299
66238
  const flagWidth = this.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
66300
66239
  let slashY = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
66301
66240
  slashY += barSize + barSpacing;
@@ -66322,20 +66261,7 @@
66322
66261
  const noteOverflowPadding = this.getLineHeight(0.5);
66323
66262
  for (const v of this.helpers.beamHelpers) {
66324
66263
  for (const h of v) {
66325
- if (h.isRestBeamHelper) {
66326
- if (h.minRestSteps) {
66327
- const topY = this.getLineY(h.maxRestSteps / 2) - noteOverflowPadding;
66328
- if (topY < maxNoteY) {
66329
- maxNoteY = topY;
66330
- }
66331
- }
66332
- if (h.maxRestSteps) {
66333
- const bottomY = this.getLineY(h.maxRestSteps & 2) + noteOverflowPadding;
66334
- if (bottomY < maxNoteY) {
66335
- maxNoteY = bottomY;
66336
- }
66337
- }
66338
- }
66264
+ if (h.isRestBeamHelper) ;
66339
66265
  else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
66340
66266
  if (h.direction === BeamDirection.Up) {
66341
66267
  let topY = this.getFlagTopY(h.beats[0], h.direction);
@@ -66386,17 +66312,6 @@
66386
66312
  }
66387
66313
  }
66388
66314
  }
66389
- const beatContainer = this.getBeatContainer(h.beats[0]);
66390
- if (beatContainer) {
66391
- const bBoxTop = beatContainer.getBoundingBoxTop();
66392
- const bBoxBottom = beatContainer.getBoundingBoxBottom();
66393
- if (bBoxBottom > minNoteY) {
66394
- minNoteY = bBoxBottom;
66395
- }
66396
- if (bBoxTop < maxNoteY) {
66397
- maxNoteY = bBoxTop;
66398
- }
66399
- }
66400
66315
  }
66401
66316
  }
66402
66317
  if (maxNoteY < rendererTop) {
@@ -66412,25 +66327,6 @@
66412
66327
  }
66413
66328
  const scale = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66414
66329
  const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
66415
- let stemSize = this.smuflMetrics.standardStemLength * scale;
66416
- if (h.tremoloDuration) {
66417
- // for 16th and shorter beats we need more space for all tremolos
66418
- // for 8th beats we need only more space for 32nd tremolos
66419
- // the logic here is not perfect but there is no SMuFL guideline
66420
- // on how tremolos need to extend stems
66421
- const oneBeamSize = (this.smuflMetrics.beamThickness + this.smuflMetrics.beamSpacing) * scale;
66422
- if (h.shortestDuration > Duration.Eighth) {
66423
- if (h.tremoloDuration === Duration.Eighth) {
66424
- stemSize += oneBeamSize;
66425
- }
66426
- else {
66427
- stemSize += oneBeamSize * 1.5;
66428
- }
66429
- }
66430
- else if (h.tremoloDuration === Duration.ThirtySecond) {
66431
- stemSize += oneBeamSize * 1.5;
66432
- }
66433
- }
66434
66330
  const drawingInfo = new BeamingHelperDrawInfo();
66435
66331
  h.drawingInfos.set(direction, drawingInfo);
66436
66332
  // the beaming logic works like this:
@@ -66443,33 +66339,17 @@
66443
66339
  const isRest = h.isRestBeamHelper;
66444
66340
  // 1. put direct diagonal line.
66445
66341
  drawingInfo.startBeat = firstBeat;
66446
- drawingInfo.startX = h.getBeatLineX(firstBeat);
66447
- if (isRest) {
66448
- drawingInfo.startY =
66449
- direction === BeamDirection.Up
66450
- ? this.getLineY(h.minRestSteps / 2)
66451
- : this.getLineY(h.maxRestSteps / 2);
66452
- }
66453
- else {
66454
- drawingInfo.startY =
66455
- direction === BeamDirection.Up
66456
- ? this.getFlagTopY(firstBeat, direction)
66457
- : this.getFlagBottomY(firstBeat, direction);
66458
- }
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);
66459
66347
  drawingInfo.endBeat = lastBeat;
66460
- drawingInfo.endX = h.getBeatLineX(lastBeat);
66461
- if (isRest) {
66462
- drawingInfo.endY =
66463
- direction === BeamDirection.Up
66464
- ? this.getLineY(h.minRestSteps / 2)
66465
- : this.getLineY(h.maxRestSteps / 2);
66466
- }
66467
- else {
66468
- drawingInfo.endY =
66469
- direction === BeamDirection.Up
66470
- ? this.getFlagTopY(lastBeat, direction)
66471
- : this.getFlagBottomY(lastBeat, direction);
66472
- }
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);
66473
66353
  // 2. ensure max slope
66474
66354
  // we use the min/max notes to place the beam along their real position
66475
66355
  // we only want a maximum of 10 offset for their gradient
@@ -66494,12 +66374,41 @@
66494
66374
  drawingInfo.startY - drawingInfo.endY > maxSlope) {
66495
66375
  drawingInfo.startY = drawingInfo.endY + maxSlope;
66496
66376
  }
66497
- // 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
66498
66407
  if (h.beats.length > 1) {
66499
66408
  // check if highest note shifts bar up or down
66500
66409
  if (direction === BeamDirection.Up) {
66501
- const yNeededForHighestNote = this.getLineY(this.getMinLineOfBeat(h.beatOfHighestNote)) - stemSize;
66502
- 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));
66503
66412
  const diff = yGivenByCurrentValues - yNeededForHighestNote;
66504
66413
  if (diff > 0) {
66505
66414
  drawingInfo.startY -= diff;
@@ -66507,8 +66416,8 @@
66507
66416
  }
66508
66417
  }
66509
66418
  else {
66510
- const yNeededForLowestNote = this.getLineY(this.getMaxLineOfBeat(h.beatOfLowestNote)) + stemSize;
66511
- 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));
66512
66421
  const diff = yNeededForLowestNote - yGivenByCurrentValues;
66513
66422
  if (diff > 0) {
66514
66423
  drawingInfo.startY += diff;
@@ -66516,33 +66425,39 @@
66516
66425
  }
66517
66426
  }
66518
66427
  // check if rest shifts bar up or down
66519
- 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
66520
66431
  const scaleMod = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66521
- let barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66522
- barSpacing += this.smuflMetrics.beamSpacing;
66523
- if (direction === BeamDirection.Up && h.minRestSteps !== null) {
66524
- const yNeededForRest = this.getLineY(h.minRestSteps / 2) - barSpacing;
66525
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMinRestSteps));
66526
- const diff = yGivenByCurrentValues - yNeededForRest;
66527
- if (diff > 0) {
66528
- drawingInfo.startY -= diff;
66529
- 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
+ }
66530
66445
  }
66531
- }
66532
- else if (direction === BeamDirection.Down && h.maxRestSteps !== null) {
66533
- const yNeededForRest = this.getLineHeight(h.maxRestSteps / 2) + barSpacing;
66534
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMaxRestSteps));
66535
- const diff = yNeededForRest - yGivenByCurrentValues;
66536
- if (diff > 0) {
66537
- drawingInfo.startY += diff;
66538
- 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
+ }
66539
66454
  }
66540
66455
  }
66541
66456
  }
66542
66457
  // check if slash shifts bar up or down
66543
66458
  if (h.slashBeats.length > 0) {
66544
66459
  for (const b of h.slashBeats) {
66545
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(b));
66460
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66546
66461
  const yNeededForSlash = h.direction === BeamDirection.Up
66547
66462
  ? this.getFlagTopY(b, h.direction)
66548
66463
  : this.getFlagBottomY(b, h.direction);
@@ -66554,31 +66469,6 @@
66554
66469
  }
66555
66470
  }
66556
66471
  }
66557
- // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66558
- // here we shift accordingly
66559
- if (barCount > 2 && !isRest) {
66560
- const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66561
- const beamThickness = this.smuflMetrics.beamThickness * scale;
66562
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66563
- if (direction === BeamDirection.Up) {
66564
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66565
- const barTopY = bottomBarY - totalBarsHeight;
66566
- const diff = drawingInfo.startY - barTopY;
66567
- if (diff > 0) {
66568
- drawingInfo.startY -= diff;
66569
- drawingInfo.endY -= diff;
66570
- }
66571
- }
66572
- else {
66573
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66574
- const barBottomY = topBarY + totalBarsHeight;
66575
- const diff = barBottomY - drawingInfo.startY;
66576
- if (diff > 0) {
66577
- drawingInfo.startY += diff;
66578
- drawingInfo.endY += diff;
66579
- }
66580
- }
66581
- }
66582
66472
  }
66583
66473
  getMinLineOfBeat(_beat) {
66584
66474
  return 0;
@@ -67099,20 +66989,6 @@
67099
66989
  }
67100
66990
  return 0;
67101
66991
  }
67102
- updateBeamingHelper() {
67103
- if (this.beamingHelper) {
67104
- let g = null;
67105
- if (this.noteHeads) {
67106
- g = this.noteHeads;
67107
- }
67108
- else if (this.deadSlapped) {
67109
- g = this.deadSlapped;
67110
- }
67111
- if (g) {
67112
- this.beamingHelper.registerBeatLineX('numbered', this.container.beat, this.container.x + this.x + g.x, this.container.x + this.x + g.x + g.width);
67113
- }
67114
- }
67115
- }
67116
66992
  static majorKeySignatureOneValues = [
67117
66993
  // Flats
67118
66994
  59, 66, 61, 68, 63, 58, 65,
@@ -67237,6 +67113,7 @@
67237
67113
  this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67238
67114
  }
67239
67115
  this.middleX = this.onTimeX;
67116
+ this.stemX = this.middleX;
67240
67117
  }
67241
67118
  }
67242
67119
 
@@ -67649,7 +67526,7 @@
67649
67526
  const barSize = this.smuflMetrics.numberedBarRendererBarSize;
67650
67527
  const barCount = ModelUtils.getIndex(beat.duration) - 2;
67651
67528
  const barStart = cy + this.y;
67652
- const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes) - this.beatGlyphsStart;
67529
+ const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes);
67653
67530
  const beamY = this.calculateBeamY(h, beatLineX);
67654
67531
  for (let barIndex = 0; barIndex < barCount; barIndex++) {
67655
67532
  let barStartX = 0;
@@ -67658,11 +67535,11 @@
67658
67535
  const barY = barStart + barIndex * barSpacing;
67659
67536
  if (i === h.beats.length - 1) {
67660
67537
  barStartX = beatLineX;
67661
- barEndX = this.getBeatX(beat, BeatXPosition.PostNotes) - this.beatGlyphsStart;
67538
+ barEndX = this.getBeatX(beat, BeatXPosition.PostNotes);
67662
67539
  }
67663
67540
  else {
67664
67541
  barStartX = beatLineX;
67665
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes) - this.beatGlyphsStart;
67542
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes);
67666
67543
  }
67667
67544
  barStartY = barY + beamY;
67668
67545
  canvas.fillRect(cx + this.x + barStartX, barStartY, barEndX - barStartX, barSize);
@@ -67680,7 +67557,7 @@
67680
67557
  dotsY = barStart + beamY + barCount * (barSpacing + barSize);
67681
67558
  dotsOffset = dotSpacing;
67682
67559
  }
67683
- const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes) - this.beatGlyphsStart;
67560
+ const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes);
67684
67561
  dotCount = Math.abs(dotCount);
67685
67562
  for (let d = 0; d < dotCount; d++) {
67686
67563
  CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + dotX, dotsY, 1, MusicFontSymbol.AugmentationDot, true);
@@ -67729,7 +67606,7 @@
67729
67606
  return this.getLineY(0) - noteHeadHeight / 2;
67730
67607
  }
67731
67608
  createPreBeatGlyphs() {
67732
- this.wasFirstOfLine = this.isFirstOfLine;
67609
+ this.wasFirstOfStaff = this.isFirstOfStaff;
67733
67610
  if (this.index === 0 || (this.bar.masterBar.isRepeatStart && this._isOnlyNumbered)) {
67734
67611
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
67735
67612
  }
@@ -67785,6 +67662,11 @@
67785
67662
  }
67786
67663
  paintBeamingStem(_beat, _cy, _x, _topY, _bottomY, _canvas) {
67787
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
+ }
67788
67670
  }
67789
67671
 
67790
67672
  /**
@@ -68321,9 +68203,8 @@
68321
68203
  aboveBeatEffects = new Map();
68322
68204
  belowBeatEffects = new Map();
68323
68205
  beat;
68324
- beamingHelper;
68325
68206
  get direction() {
68326
- return this.beamingHelper.direction;
68207
+ return this.renderer.getBeatDirection(this.beat);
68327
68208
  }
68328
68209
  get scale() {
68329
68210
  return this.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
@@ -68365,7 +68246,7 @@
68365
68246
  : 0) * scale;
68366
68247
  // stem size according to duration
68367
68248
  pos -= this.renderer.smuflMetrics.standardStemLength * scale;
68368
- const topCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68249
+ const topCenterY = this.renderer.centerStaffStemY(this.direction);
68369
68250
  return Math.min(topCenterY, pos);
68370
68251
  case NoteYPosition.Top:
68371
68252
  pos -= n.height / 2;
@@ -68382,7 +68263,7 @@
68382
68263
  : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
68383
68264
  // stem size according to duration
68384
68265
  pos += this.renderer.smuflMetrics.standardStemLength * scale;
68385
- const bottomCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68266
+ const bottomCenterY = this.renderer.centerStaffStemY(this.direction);
68386
68267
  return Math.max(bottomCenterY, pos);
68387
68268
  case NoteYPosition.StemUp:
68388
68269
  pos -=
@@ -68407,11 +68288,6 @@
68407
68288
  addEffectNoteGlyph(noteGlyph, noteLine) {
68408
68289
  super.add(noteGlyph, noteLine);
68409
68290
  }
68410
- updateBeamingHelper(cx) {
68411
- if (this.beamingHelper) {
68412
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.upLineX, cx + this.x + this.downLineX);
68413
- }
68414
- }
68415
68291
  doLayout() {
68416
68292
  super.doLayout();
68417
68293
  const scoreRenderer = this.renderer;
@@ -68539,7 +68415,6 @@
68539
68415
  * @internal
68540
68416
  */
68541
68417
  class ScoreRestGlyph extends MusicFontGlyph {
68542
- beamingHelper;
68543
68418
  constructor(x, y, duration) {
68544
68419
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
68545
68420
  }
@@ -68571,11 +68446,6 @@
68571
68446
  return MusicFontSymbol.None;
68572
68447
  }
68573
68448
  }
68574
- updateBeamingHelper(cx) {
68575
- if (this.beamingHelper) {
68576
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
68577
- }
68578
- }
68579
68449
  paint(cx, cy, canvas) {
68580
68450
  this.internalPaint(cx, cy, canvas, BeatSubElement.StandardNotationRests);
68581
68451
  }
@@ -69067,10 +68937,11 @@
69067
68937
  */
69068
68938
  class SlashNoteHeadGlyph extends MusicFontGlyph {
69069
68939
  beatEffects = new Map();
69070
- beamingHelper;
69071
68940
  noteHeadElement = NoteSubElement.SlashNoteHead;
69072
68941
  effectElement = BeatSubElement.SlashEffects;
69073
68942
  _symbol;
68943
+ upLineX = 0;
68944
+ downLineX = 0;
69074
68945
  constructor(x, y, duration, isGrace, beat) {
69075
68946
  super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, SlashNoteHeadGlyph.getSymbol(duration));
69076
68947
  this._symbol = SlashNoteHeadGlyph.getSymbol(duration);
@@ -69121,6 +68992,15 @@
69121
68992
  if (!Number.isNaN(minEffectY)) {
69122
68993
  this.renderer.registerBeatEffectOverflows(minEffectY, maxEffectY);
69123
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;
69124
69004
  }
69125
69005
  static getSymbol(duration) {
69126
69006
  switch (duration) {
@@ -69134,25 +69014,13 @@
69134
69014
  return MusicFontSymbol.NoteheadSlashHorizontalEnds;
69135
69015
  }
69136
69016
  }
69137
- updateBeamingHelper(cx) {
69138
- if (this.beamingHelper) {
69139
- const symbol = this._symbol;
69140
- const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
69141
- ? this.renderer.smuflMetrics.stemUp.get(symbol).x
69142
- : 0;
69143
- const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69144
- ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69145
- : 0;
69146
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + stemInfoUp, cx + this.x + stemInfoDown);
69147
- }
69148
- }
69149
69017
  }
69150
69018
 
69151
69019
  /**
69152
69020
  * @internal
69153
69021
  */
69154
69022
  class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69155
- _collisionOffset = -1e3;
69023
+ _collisionOffset = Number.NaN;
69156
69024
  _skipPaint = false;
69157
69025
  _whammy;
69158
69026
  noteHeads = null;
@@ -69203,22 +69071,19 @@
69203
69071
  getNoteY(note, requestedPosition) {
69204
69072
  return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
69205
69073
  }
69206
- updateBeamingHelper() {
69207
- if (this.noteHeads) {
69208
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
69074
+ applyRestCollisionOffset() {
69075
+ if (!this.restGlyph) {
69076
+ return;
69209
69077
  }
69210
- else if (this.restGlyph) {
69211
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
69212
- if (this.renderer.bar.isMultiVoice && this._collisionOffset === -1e3) {
69213
- this._collisionOffset = this.renderer.helpers.collisionHelper.applyRestCollisionOffset(this.container.beat, this.restGlyph.y, this.renderer.getScoreHeight(1));
69214
- this.y += this._collisionOffset;
69215
- const existingRests = this.renderer.helpers.collisionHelper.restDurationsByDisplayTime;
69216
- if (existingRests.has(this.container.beat.playbackStart) &&
69217
- existingRests.get(this.container.beat.playbackStart).has(this.container.beat.playbackDuration) &&
69218
- existingRests.get(this.container.beat.playbackStart).get(this.container.beat.playbackDuration) !==
69219
- this.container.beat.id) {
69220
- this._skipPaint = true;
69221
- }
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;
69222
69087
  }
69223
69088
  }
69224
69089
  }
@@ -69238,7 +69103,6 @@
69238
69103
  const noteHeads = new ScoreNoteChordGlyph();
69239
69104
  this.noteHeads = noteHeads;
69240
69105
  noteHeads.beat = this.container.beat;
69241
- noteHeads.beamingHelper = this.beamingHelper;
69242
69106
  const ghost = new GhostNoteContainerGlyph(false);
69243
69107
  ghost.renderer = this.renderer;
69244
69108
  for (const note of this.container.beat.notes) {
@@ -69288,22 +69152,18 @@
69288
69152
  const restGlyph = new ScoreRestGlyph(0, sr.getScoreY(steps), this.container.beat.duration);
69289
69153
  this.restGlyph = restGlyph;
69290
69154
  restGlyph.beat = this.container.beat;
69291
- restGlyph.beamingHelper = this.beamingHelper;
69292
69155
  this.addNormal(restGlyph);
69293
69156
  if (this.renderer.bar.isMultiVoice) {
69294
69157
  if (this.container.beat.voice.index === 0) {
69295
69158
  const restSizes = BeamingHelper.computeLineHeightsForRest(this.container.beat.duration);
69296
69159
  const restTop = restGlyph.y - sr.getScoreHeight(restSizes[0]);
69297
69160
  const restBottom = restGlyph.y + sr.getScoreHeight(restSizes[1]);
69298
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69161
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69299
69162
  }
69300
69163
  else {
69301
- this.renderer.helpers.collisionHelper.registerRest(this.container.beat);
69164
+ this.renderer.collisionHelper.registerRest(this.container.beat);
69302
69165
  }
69303
69166
  }
69304
- if (this.beamingHelper) {
69305
- this.beamingHelper.applyRest(this.container.beat, steps);
69306
- }
69307
69167
  //
69308
69168
  // Note dots
69309
69169
  //
@@ -69318,17 +69178,24 @@
69318
69178
  }
69319
69179
  }
69320
69180
  super.doLayout();
69181
+ this.applyRestCollisionOffset();
69321
69182
  if (this.container.beat.isEmpty) {
69322
69183
  this.onTimeX = this.width / 2;
69323
69184
  this.middleX = this.onTimeX;
69185
+ this.stemX = this.middleX;
69324
69186
  }
69325
69187
  else if (this.restGlyph) {
69326
69188
  this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
69327
69189
  this.middleX = this.onTimeX;
69190
+ this.stemX = this.middleX;
69328
69191
  }
69329
69192
  else if (this.noteHeads) {
69330
69193
  this.onTimeX = this.noteHeads.x + this.noteHeads.onTimeX;
69331
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);
69332
69199
  }
69333
69200
  }
69334
69201
  _createBeatDot(line, group) {
@@ -69400,7 +69267,7 @@
69400
69267
  }
69401
69268
  const belowBeatEffects = this.noteHeads.belowBeatEffects;
69402
69269
  const aboveBeatEffects = this.noteHeads.aboveBeatEffects;
69403
- const outsideBeatEffects = this.beamingHelper.direction === BeamDirection.Up
69270
+ const outsideBeatEffects = this.renderer.getBeatDirection(this.container.beat) === BeamDirection.Up
69404
69271
  ? this.noteHeads.belowBeatEffects
69405
69272
  : this.noteHeads.aboveBeatEffects;
69406
69273
  if (n.isStaccato && !belowBeatEffects.has('Staccato')) {
@@ -69836,7 +69703,7 @@
69836
69703
  // if we have a line break we draw only a line until the end
69837
69704
  if (!endNoteRenderer || endNoteRenderer.staff !== startNoteRenderer.staff) {
69838
69705
  const endX = cx + startNoteRenderer.x + startNoteRenderer.width;
69839
- const noteValueToDraw = note.tieDestination.realValue;
69706
+ const noteValueToDraw = note.tieDestination.displayValue;
69840
69707
  startNoteRenderer.accidentalHelper.applyAccidentalForValue(note.beat, noteValueToDraw, false, true);
69841
69708
  const endY = cy +
69842
69709
  startNoteRenderer.y +
@@ -70372,6 +70239,22 @@
70372
70239
  doLayout() {
70373
70240
  this._effectSlur = null;
70374
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
+ }
70375
70258
  super.doLayout();
70376
70259
  if (this._bend) {
70377
70260
  this._bend.renderer = this.renderer;
@@ -70381,7 +70264,7 @@
70381
70264
  }
70382
70265
  getBoundingBoxTop() {
70383
70266
  if (this._bend !== null) {
70384
- return Math.min(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70267
+ return ModelUtils.minBoundingBox(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70385
70268
  }
70386
70269
  else {
70387
70270
  return super.getBoundingBoxTop();
@@ -70389,7 +70272,7 @@
70389
70272
  }
70390
70273
  getBoundingBoxBottom() {
70391
70274
  if (this._bend !== null) {
70392
- return Math.max(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70275
+ return ModelUtils.maxBoundingBox(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70393
70276
  }
70394
70277
  else {
70395
70278
  return super.getBoundingBoxBottom();
@@ -70437,7 +70320,7 @@
70437
70320
  }
70438
70321
  // end effect slur on last beat
70439
70322
  if (!this._effectEndSlur && n.beat.isEffectSlurDestination && n.beat.effectSlurOrigin) {
70440
- const direction = this.onNotes.beamingHelper.direction;
70323
+ const direction = this.renderer.getBeatDirection(n.beat);
70441
70324
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70442
70325
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70443
70326
  const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
@@ -70476,6 +70359,15 @@
70476
70359
  }
70477
70360
  }
70478
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
+ }
70479
70371
  }
70480
70372
 
70481
70373
  /**
@@ -70510,7 +70402,7 @@
70510
70402
  return this.smuflMetrics.oneStaffSpace;
70511
70403
  }
70512
70404
  get heightLineCount() {
70513
- return 5;
70405
+ return Math.max(5, this.bar.staff.standardNotationLineCount);
70514
70406
  }
70515
70407
  get drawnLineCount() {
70516
70408
  return this.bar.staff.standardNotationLineCount;
@@ -70567,7 +70459,9 @@
70567
70459
  slashY -= this.smuflMetrics.stemUp.has(symbol)
70568
70460
  ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
70569
70461
  : 0;
70570
- slashY -= this.smuflMetrics.standardStemLength + scale;
70462
+ if (!beat.isRest) {
70463
+ slashY -= this.smuflMetrics.standardStemLength + scale;
70464
+ }
70571
70465
  }
70572
70466
  return slashY;
70573
70467
  }
@@ -70576,7 +70470,7 @@
70576
70470
  return this.getBeatContainer(beat).onNotes.getNoteY(minNote, direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown);
70577
70471
  }
70578
70472
  let y = this.getScoreY(this.accidentalHelper.getMinSteps(beat));
70579
- if (direction === BeamDirection.Up) {
70473
+ if (direction === BeamDirection.Up && !beat.isRest) {
70580
70474
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
70581
70475
  y -= this.smuflMetrics.standardStemLength * scale;
70582
70476
  }
@@ -70614,14 +70508,14 @@
70614
70508
  getBeamDirection(helper) {
70615
70509
  return helper.direction;
70616
70510
  }
70617
- centerStaffStemY(helper) {
70511
+ centerStaffStemY(direction) {
70618
70512
  const isStandardFive = this.bar.staff.standardNotationLineCount === Staff.DefaultStandardNotationLineCount;
70619
70513
  if (isStandardFive) {
70620
70514
  // center on the middle line for a standard 5-line staff
70621
70515
  return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
70622
70516
  }
70623
70517
  // for other staff line counts, we align the stem either on the top or bottom line
70624
- if (helper.direction === BeamDirection.Up) {
70518
+ if (direction === BeamDirection.Up) {
70625
70519
  return this.getScoreY(this.bar.staff.standardNotationLineCount * 2);
70626
70520
  }
70627
70521
  return this.getScoreY(0);
@@ -70723,7 +70617,7 @@
70723
70617
  createLinePreBeatGlyphs() {
70724
70618
  // Clef
70725
70619
  let hasClef = false;
70726
- if (this.isFirstOfLine ||
70620
+ if (this.isFirstOfStaff ||
70727
70621
  this.bar.clef !== this.bar.previousBar.clef ||
70728
70622
  this.bar.clefOttava !== this.bar.previousBar.clefOttava) {
70729
70623
  // SMUFL: Clefs should be positioned such that the pitch the clef refers to is on the baseline
@@ -70928,6 +70822,25 @@
70928
70822
  */
70929
70823
  class SlashBeatContainerGlyph extends BeatContainerGlyph {
70930
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
+ }
70931
70844
  createTies(n) {
70932
70845
  // create a tie if any effect requires it
70933
70846
  if (!n.isVisible) {
@@ -70944,17 +70857,21 @@
70944
70857
  this.addTie(tie);
70945
70858
  }
70946
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
+ }
70947
70869
  }
70948
70870
 
70949
70871
  /**
70950
70872
  * @internal
70951
70873
  */
70952
70874
  class SlashRestGlyph extends ScoreRestGlyph {
70953
- updateBeamingHelper(cx) {
70954
- if (this.beamingHelper) {
70955
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
70956
- }
70957
- }
70958
70875
  paint(cx, cy, canvas) {
70959
70876
  super.internalPaint(cx, cy, canvas, BeatSubElement.SlashRests);
70960
70877
  }
@@ -71050,19 +70967,6 @@
71050
70967
  }
71051
70968
  return 0;
71052
70969
  }
71053
- updateBeamingHelper() {
71054
- if (this.noteHeads) {
71055
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
71056
- }
71057
- else if (this.deadSlapped) {
71058
- if (this.beamingHelper) {
71059
- 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);
71060
- }
71061
- }
71062
- else if (this.restGlyph) {
71063
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
71064
- }
71065
- }
71066
70970
  doLayout() {
71067
70971
  // create glyphs
71068
70972
  const sr = this.renderer;
@@ -71080,18 +70984,13 @@
71080
70984
  const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat.duration, isGrace, this.container.beat);
71081
70985
  this.noteHeads = noteHeadGlyph;
71082
70986
  noteHeadGlyph.beat = this.container.beat;
71083
- noteHeadGlyph.beamingHelper = this.beamingHelper;
71084
70987
  this.addNormal(noteHeadGlyph);
71085
70988
  }
71086
70989
  else {
71087
70990
  const restGlyph = new SlashRestGlyph(0, glyphY, this.container.beat.duration);
71088
70991
  this.restGlyph = restGlyph;
71089
70992
  restGlyph.beat = this.container.beat;
71090
- restGlyph.beamingHelper = this.beamingHelper;
71091
70993
  this.addNormal(restGlyph);
71092
- if (this.beamingHelper) {
71093
- this.beamingHelper.applyRest(this.container.beat, 0);
71094
- }
71095
70994
  }
71096
70995
  }
71097
70996
  //
@@ -71105,15 +71004,22 @@
71105
71004
  super.doLayout();
71106
71005
  if (this.container.beat.isEmpty) {
71107
71006
  this.onTimeX = this.width / 2;
71007
+ this.stemX = this.onTimeX;
71108
71008
  }
71109
71009
  else if (this.restGlyph) {
71110
71010
  this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71011
+ this.stemX = this.onTimeX;
71111
71012
  }
71112
71013
  else if (this.noteHeads) {
71113
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);
71114
71019
  }
71115
71020
  else if (this.deadSlapped) {
71116
71021
  this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
71022
+ this.stemX = this.onTimeX;
71117
71023
  }
71118
71024
  this.middleX = this.onTimeX;
71119
71025
  }
@@ -71190,7 +71096,9 @@
71190
71096
  const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
71191
71097
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
71192
71098
  slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
71193
- slashY -= this.smuflMetrics.standardStemLength + scale;
71099
+ if (!beat.isRest) {
71100
+ slashY -= this.smuflMetrics.standardStemLength + scale;
71101
+ }
71194
71102
  return slashY;
71195
71103
  }
71196
71104
  getFlagBottomY(beat, _direction) {
@@ -71274,6 +71182,11 @@
71274
71182
  _?.[Symbol.dispose]?.();
71275
71183
  }
71276
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
+ }
71277
71190
  }
71278
71191
 
71279
71192
  /**
@@ -71730,7 +71643,6 @@
71730
71643
  _deadSlapped = null;
71731
71644
  _isGrace;
71732
71645
  beat;
71733
- beamingHelper;
71734
71646
  maxStringNote = null;
71735
71647
  minStringNote = null;
71736
71648
  beatEffects = new Map();
@@ -71879,11 +71791,6 @@
71879
71791
  }
71880
71792
  }
71881
71793
  }
71882
- updateBeamingHelper(cx) {
71883
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71884
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71885
- }
71886
- }
71887
71794
  }
71888
71795
 
71889
71796
  /**
@@ -71891,7 +71798,6 @@
71891
71798
  */
71892
71799
  class TabRestGlyph extends MusicFontGlyph {
71893
71800
  _isVisibleRest;
71894
- beamingHelper;
71895
71801
  constructor(x, y, isVisibleRest, duration) {
71896
71802
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
71897
71803
  this._isVisibleRest = isVisibleRest;
@@ -71899,11 +71805,6 @@
71899
71805
  doLayout() {
71900
71806
  super.doLayout();
71901
71807
  }
71902
- updateBeamingHelper(cx) {
71903
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71904
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71905
- }
71906
- }
71907
71808
  paint(cx, cy, canvas) {
71908
71809
  if (this._isVisibleRest) {
71909
71810
  const _ = ElementStyleHelper.beat(canvas, BeatSubElement.GuitarTabRests, this.beat);
@@ -71960,7 +71861,6 @@
71960
71861
  slashNoteHead.effectElement = BeatSubElement.GuitarTabEffects;
71961
71862
  this.slash = slashNoteHead;
71962
71863
  slashNoteHead.beat = this.container.beat;
71963
- slashNoteHead.beamingHelper = this.beamingHelper;
71964
71864
  this.addNormal(slashNoteHead);
71965
71865
  beatEffects = slashNoteHead.beatEffects;
71966
71866
  }
@@ -71968,7 +71868,6 @@
71968
71868
  const tabNoteNumbers = new TabNoteChordGlyph(0, 0, isGrace);
71969
71869
  this.noteNumbers = tabNoteNumbers;
71970
71870
  tabNoteNumbers.beat = this.container.beat;
71971
- tabNoteNumbers.beamingHelper = this.beamingHelper;
71972
71871
  for (const note of this.container.beat.notes) {
71973
71872
  if (note.isVisible) {
71974
71873
  this._createNoteGlyph(note);
@@ -71990,9 +71889,9 @@
71990
71889
  // Note dots
71991
71890
  //
71992
71891
  if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== exports.TabRhythmMode.Hidden) {
71892
+ const y = tabRenderer.getFlagAndBarPos();
71993
71893
  for (let i = 0; i < this.container.beat.dots; i++) {
71994
- this.addEffect(new AugmentationDotGlyph(0, tabRenderer.lineOffset * tabRenderer.bar.staff.tuning.length +
71995
- tabRenderer.settings.notation.rhythmHeight));
71894
+ this.addEffect(new AugmentationDotGlyph(0, y));
71996
71895
  }
71997
71896
  }
71998
71897
  }
@@ -72002,7 +71901,6 @@
72002
71901
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
72003
71902
  this.restGlyph = restGlyph;
72004
71903
  restGlyph.beat = this.container.beat;
72005
- restGlyph.beamingHelper = this.beamingHelper;
72006
71904
  this.addNormal(restGlyph);
72007
71905
  //
72008
71906
  // Note dots
@@ -72040,21 +71938,11 @@
72040
71938
  this.onTimeX = this.slash.x + this.slash.width / 2;
72041
71939
  }
72042
71940
  this.middleX = this.onTimeX;
71941
+ this.stemX = this.middleX;
72043
71942
  for (const g of centeredEffectGlyphs) {
72044
71943
  g.x = this.onTimeX;
72045
71944
  }
72046
71945
  }
72047
- updateBeamingHelper() {
72048
- if (this.noteNumbers) {
72049
- this.noteNumbers.updateBeamingHelper(this.container.x + this.x);
72050
- }
72051
- else if (this.restGlyph) {
72052
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
72053
- }
72054
- else if (this.slash) {
72055
- this.slash.updateBeamingHelper(this.container.x + this.x);
72056
- }
72057
- }
72058
71946
  _createNoteGlyph(n) {
72059
71947
  const tr = this.renderer;
72060
71948
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
@@ -72065,7 +71953,7 @@
72065
71953
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
72066
71954
  const topY = noteNumberGlyph.y - noteNumberGlyph.height / 2;
72067
71955
  const bottomY = topY + noteNumberGlyph.height;
72068
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
71956
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
72069
71957
  const minString = tr.minString;
72070
71958
  const maxString = tr.maxString;
72071
71959
  if (Number.isNaN(minString) || minString < n.string) {
@@ -72337,7 +72225,7 @@
72337
72225
  }
72338
72226
  createLinePreBeatGlyphs() {
72339
72227
  // Clef
72340
- if (this.isFirstOfLine) {
72228
+ if (this.isFirstOfStaff) {
72341
72229
  const center = (this.bar.staff.tuning.length - 1) / 2;
72342
72230
  this.createStartSpacing();
72343
72231
  this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
@@ -72428,14 +72316,14 @@
72428
72316
  // currently only used for duplets
72429
72317
  return this.getFlagAndBarPos();
72430
72318
  }
72431
- shouldPaintFlag(beat, h) {
72432
- if (!super.shouldPaintFlag(beat, h)) {
72319
+ shouldPaintFlag(beat) {
72320
+ if (!super.shouldPaintFlag(beat)) {
72433
72321
  return false;
72434
72322
  }
72435
72323
  if (beat.graceType !== GraceType.None) {
72436
72324
  return false;
72437
72325
  }
72438
- return this.drawBeamHelperAsFlags(h);
72326
+ return true;
72439
72327
  }
72440
72328
  paintBeamingStem(beat, cy, x, topY, bottomY, canvas) {
72441
72329
  if (bottomY < topY) {