@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.
@@ -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
  *
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.8.0-alpha.1640';
207
- static date = '2025-12-10T02:19:08.776Z';
208
- static commit = '343f59ee6b39b4f3a41636b3c33db34bccf631f9';
206
+ static version = '1.8.0-alpha.1643';
207
+ static date = '2025-12-13T02:07:47.380Z';
208
+ static commit = '6cdd7783a14244e9d7efb9f9b9dbad7a27b11eee';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -4715,6 +4715,24 @@ class ModelUtils {
4715
4715
  static toArticulationId(plain) {
4716
4716
  return plain.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
4717
4717
  }
4718
+ static minBoundingBox(a, b) {
4719
+ if (Number.isNaN(a)) {
4720
+ return b;
4721
+ }
4722
+ else if (Number.isNaN(b)) {
4723
+ return a;
4724
+ }
4725
+ return a < b ? a : b;
4726
+ }
4727
+ static maxBoundingBox(a, b) {
4728
+ if (Number.isNaN(a)) {
4729
+ return b;
4730
+ }
4731
+ else if (Number.isNaN(b)) {
4732
+ return a;
4733
+ }
4734
+ return a > b ? a : b;
4735
+ }
4718
4736
  }
4719
4737
 
4720
4738
  /**
@@ -48297,6 +48315,39 @@ class MidiFileGenerator {
48297
48315
  }
48298
48316
  }
48299
48317
 
48318
+ /**
48319
+ * Lists the different position modes for {@link BarRendererBase.getBeatX}
48320
+ * @internal
48321
+ */
48322
+ var BeatXPosition;
48323
+ (function (BeatXPosition) {
48324
+ /**
48325
+ * Gets the pre-notes position which is located before the accidentals
48326
+ */
48327
+ BeatXPosition[BeatXPosition["PreNotes"] = 0] = "PreNotes";
48328
+ /**
48329
+ * Gets the on-notes position which is located after the accidentals but before the note heads.
48330
+ */
48331
+ BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
48332
+ /**
48333
+ * Gets the middle-notes position which is located after in the exact center of the note heads.
48334
+ */
48335
+ BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
48336
+ /**
48337
+ * Gets position of the stem for this beat
48338
+ */
48339
+ BeatXPosition[BeatXPosition["Stem"] = 3] = "Stem";
48340
+ /**
48341
+ * Get the post-notes position which is located at after the note heads.
48342
+ */
48343
+ BeatXPosition[BeatXPosition["PostNotes"] = 4] = "PostNotes";
48344
+ /**
48345
+ * Get the end-beat position which is located at the end of the beat. This position is almost
48346
+ * equal to the pre-notes position of the next beat.
48347
+ */
48348
+ BeatXPosition[BeatXPosition["EndBeat"] = 5] = "EndBeat";
48349
+ })(BeatXPosition || (BeatXPosition = {}));
48350
+
48300
48351
  /**
48301
48352
  * A glyph is a single symbol which can be added to a GlyphBarRenderer for automated
48302
48353
  * layouting and drawing of stacked symbols.
@@ -48324,198 +48375,6 @@ class Glyph {
48324
48375
  }
48325
48376
  }
48326
48377
 
48327
- /**
48328
- * Effect-Glyphs implementing this public interface get notified
48329
- * as they are expanded over multiple beats.
48330
- * @internal
48331
- */
48332
- class EffectGlyph extends Glyph {
48333
- /**
48334
- * Gets or sets the beat where the glyph belongs to.
48335
- */
48336
- beat = null;
48337
- /**
48338
- * Gets or sets the next glyph of the same type in case
48339
- * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
48340
- */
48341
- nextGlyph = null;
48342
- /**
48343
- * Gets or sets the previous glyph of the same type in case
48344
- * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
48345
- */
48346
- previousGlyph = null;
48347
- constructor(x = 0, y = 0) {
48348
- super(x, y);
48349
- }
48350
- }
48351
-
48352
- /**
48353
- * @internal
48354
- */
48355
- class MusicFontGlyph extends EffectGlyph {
48356
- glyphScale = 0;
48357
- symbol;
48358
- center = false;
48359
- colorOverride;
48360
- offsetX = 0;
48361
- offsetY = 0;
48362
- constructor(x, y, glyphScale, symbol) {
48363
- super(x, y);
48364
- this.glyphScale = glyphScale;
48365
- this.symbol = symbol;
48366
- }
48367
- getBoundingBoxTop() {
48368
- const bBoxTop = this.renderer.smuflMetrics.glyphTop.get(this.symbol);
48369
- return this.y - this.offsetY - bBoxTop;
48370
- }
48371
- doLayout() {
48372
- this.width = this.renderer.smuflMetrics.glyphWidths.get(this.symbol) * this.glyphScale;
48373
- this.height = this.renderer.smuflMetrics.glyphHeights.get(this.symbol) * this.glyphScale;
48374
- }
48375
- paint(cx, cy, canvas) {
48376
- if (this.width === 0 && this.height === 0) {
48377
- return;
48378
- }
48379
- const c = canvas.color;
48380
- if (this.colorOverride) {
48381
- canvas.color = this.colorOverride;
48382
- }
48383
- canvas.fillMusicFontSymbol(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbol, this.center);
48384
- canvas.color = c;
48385
- }
48386
- }
48387
- /**
48388
- * @internal
48389
- */
48390
- class MusicFontTextGlyph extends EffectGlyph {
48391
- glyphScale = 0;
48392
- symbols;
48393
- center = false;
48394
- colorOverride;
48395
- offsetX = 0;
48396
- offsetY = 0;
48397
- constructor(x, y, glyphScale, symbols) {
48398
- super(x, y);
48399
- this.glyphScale = glyphScale;
48400
- this.symbols = symbols;
48401
- }
48402
- getBoundingBoxTop() {
48403
- let bBoxTop = 0;
48404
- for (let i = 0; i < this.symbols.length; i++) {
48405
- const gTop = this.renderer.smuflMetrics.glyphTop.get(this.symbols[i]);
48406
- if (i === 0 || gTop < bBoxTop) {
48407
- bBoxTop = gTop;
48408
- }
48409
- }
48410
- return this.y - this.offsetY - bBoxTop;
48411
- }
48412
- doLayout() {
48413
- this.width = 0;
48414
- this.height = 0;
48415
- for (let i = 0; i < this.symbols.length; i++) {
48416
- const gWidth = this.renderer.smuflMetrics.glyphWidths.get(this.symbols[i]) * this.glyphScale;
48417
- const gHeight = this.renderer.smuflMetrics.glyphHeights.get(this.symbols[i]) * this.glyphScale;
48418
- if (i === 0 || gWidth > this.width) {
48419
- this.width = gWidth;
48420
- }
48421
- if (i === 0 || gHeight > this.height) {
48422
- this.height = gHeight;
48423
- }
48424
- }
48425
- }
48426
- paint(cx, cy, canvas) {
48427
- if (this.width === 0 && this.height === 0) {
48428
- return;
48429
- }
48430
- const c = canvas.color;
48431
- if (this.colorOverride) {
48432
- canvas.color = this.colorOverride;
48433
- }
48434
- canvas.fillMusicFontSymbols(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbols, this.center);
48435
- canvas.color = c;
48436
- }
48437
- }
48438
-
48439
- /**
48440
- * @internal
48441
- */
48442
- class NoteHeadGlyph extends MusicFontGlyph {
48443
- // TODO: SmuFL
48444
- static GraceScale = 0.75;
48445
- centerOnStem = false;
48446
- constructor(x, y, duration, isGrace) {
48447
- super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, NoteHeadGlyph.getSymbol(duration));
48448
- }
48449
- paint(cx, cy, canvas) {
48450
- if (this.centerOnStem) {
48451
- this.center = true;
48452
- }
48453
- super.paint(cx, cy, canvas);
48454
- }
48455
- static getSymbol(duration) {
48456
- switch (duration) {
48457
- case Duration.QuadrupleWhole:
48458
- return MusicFontSymbol.NoteheadDoubleWholeSquare;
48459
- case Duration.DoubleWhole:
48460
- return MusicFontSymbol.NoteheadDoubleWhole;
48461
- case Duration.Whole:
48462
- return MusicFontSymbol.NoteheadWhole;
48463
- case Duration.Half:
48464
- return MusicFontSymbol.NoteheadHalf;
48465
- default:
48466
- return MusicFontSymbol.NoteheadBlack;
48467
- }
48468
- }
48469
- }
48470
-
48471
- /**
48472
- * @internal
48473
- */
48474
- class FlagGlyph extends MusicFontGlyph {
48475
- constructor(x, y, duration, direction, isGrace) {
48476
- super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
48477
- }
48478
- static getSymbol(duration, direction, isGrace) {
48479
- if (isGrace) {
48480
- duration = Duration.Eighth;
48481
- }
48482
- if (direction === BeamDirection.Up) {
48483
- switch (duration) {
48484
- case Duration.Eighth:
48485
- return MusicFontSymbol.Flag8thUp;
48486
- case Duration.Sixteenth:
48487
- return MusicFontSymbol.Flag16thUp;
48488
- case Duration.ThirtySecond:
48489
- return MusicFontSymbol.Flag32ndUp;
48490
- case Duration.SixtyFourth:
48491
- return MusicFontSymbol.Flag64thUp;
48492
- case Duration.OneHundredTwentyEighth:
48493
- return MusicFontSymbol.Flag128thUp;
48494
- case Duration.TwoHundredFiftySixth:
48495
- return MusicFontSymbol.Flag256thUp;
48496
- default:
48497
- return MusicFontSymbol.Flag8thUp;
48498
- }
48499
- }
48500
- switch (duration) {
48501
- case Duration.Eighth:
48502
- return MusicFontSymbol.Flag8thDown;
48503
- case Duration.Sixteenth:
48504
- return MusicFontSymbol.Flag16thDown;
48505
- case Duration.ThirtySecond:
48506
- return MusicFontSymbol.Flag32ndDown;
48507
- case Duration.SixtyFourth:
48508
- return MusicFontSymbol.Flag64thDown;
48509
- case Duration.OneHundredTwentyEighth:
48510
- return MusicFontSymbol.Flag128thDown;
48511
- case Duration.TwoHundredFiftySixth:
48512
- return MusicFontSymbol.Flag128thDown;
48513
- default:
48514
- return MusicFontSymbol.Flag8thDown;
48515
- }
48516
- }
48517
- }
48518
-
48519
48378
  /**
48520
48379
  * @internal
48521
48380
  */
@@ -48542,28 +48401,20 @@ class BeatContainerGlyph extends Glyph {
48542
48401
  this.renderer.registerTie(tie);
48543
48402
  }
48544
48403
  getBoundingBoxTop() {
48545
- return Math.min(this.preNotes.getBoundingBoxTop(), this.onNotes.getBoundingBoxTop());
48404
+ return ModelUtils.minBoundingBox(this.preNotes.getBoundingBoxTop(), this.onNotes.getBoundingBoxTop());
48546
48405
  }
48547
48406
  getBoundingBoxBottom() {
48548
- return Math.max(this.preNotes.getBoundingBoxBottom(), this.onNotes.getBoundingBoxBottom());
48407
+ return ModelUtils.maxBoundingBox(this.preNotes.getBoundingBoxBottom(), this.onNotes.getBoundingBoxBottom());
48549
48408
  }
48550
48409
  drawBeamHelperAsFlags(helper) {
48551
48410
  return helper.hasFlag(false, undefined);
48552
48411
  }
48412
+ get postBeatStretch() {
48413
+ return this.onNotes.computedWidth - this.onNotes.onTimeX;
48414
+ }
48553
48415
  registerLayoutingInfo(layoutings) {
48554
48416
  const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
48555
- let postBeatStretch = this.onNotes.computedWidth - this.onNotes.onTimeX;
48556
- // make space for flag
48557
- const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48558
- if (this.beat.graceType !== GraceType.None) {
48559
- // always use flag size as spacing on grace notes
48560
- postBeatStretch +=
48561
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
48562
- }
48563
- else if (helper && this.drawBeamHelperAsFlags(helper)) {
48564
- postBeatStretch +=
48565
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
48566
- }
48417
+ let postBeatStretch = this.postBeatStretch;
48567
48418
  for (const tie of this._ties) {
48568
48419
  const tg = tie;
48569
48420
  postBeatStretch += tg.width;
@@ -48578,7 +48429,6 @@ class BeatContainerGlyph extends Glyph {
48578
48429
  });
48579
48430
  }
48580
48431
  applyLayoutingInfo(_info) {
48581
- this.onNotes.updateBeamingHelper();
48582
48432
  this.updateWidth();
48583
48433
  }
48584
48434
  doLayout() {
@@ -48590,7 +48440,6 @@ class BeatContainerGlyph extends Glyph {
48590
48440
  this.onNotes.renderer = this.renderer;
48591
48441
  this.onNotes.container = this;
48592
48442
  this.onNotes.doLayout();
48593
- this.onNotes.updateBeamingHelper();
48594
48443
  let i = this.beat.notes.length - 1;
48595
48444
  while (i >= 0) {
48596
48445
  this.createTies(this.beat.notes[i--]);
@@ -48599,15 +48448,6 @@ class BeatContainerGlyph extends Glyph {
48599
48448
  }
48600
48449
  updateWidth() {
48601
48450
  this.minWidth = this.preNotes.width + this.onNotes.width;
48602
- if (!this.beat.isRest) {
48603
- if (this.onNotes.beamingHelper.beats.length === 1) {
48604
- // make space for flag
48605
- if (this.beat.duration >= Duration.Eighth) {
48606
- const symbol = FlagGlyph.getSymbol(this.beat.duration, this.onNotes.beamingHelper.direction, this.beat.graceType !== GraceType.None);
48607
- this.minWidth += this.renderer.smuflMetrics.glyphWidths.get(symbol);
48608
- }
48609
- }
48610
- }
48611
48451
  let tieWidth = 0;
48612
48452
  for (const tie of this._ties) {
48613
48453
  const tg = tie;
@@ -48618,10 +48458,6 @@ class BeatContainerGlyph extends Glyph {
48618
48458
  this.minWidth += tieWidth;
48619
48459
  this.width = this.minWidth;
48620
48460
  }
48621
- scaleToWidth(beatWidth) {
48622
- this.onNotes.updateBeamingHelper();
48623
- this.width = beatWidth;
48624
- }
48625
48461
  createTies(_n) {
48626
48462
  }
48627
48463
  static getGroupId(beat) {
@@ -48631,8 +48467,11 @@ class BeatContainerGlyph extends Glyph {
48631
48467
  // var c = canvas.color;
48632
48468
  // canvas.color = Color.random();
48633
48469
  // canvas.fillRect(cx + this.x, cy + this.y + this.preNotes.getBoundingBoxTop(), this.width, this.renderer.height);
48634
- // canvas.color = Color.random();
48635
48470
  // canvas.fillRect(cx + this.x, cy + this.y + this.onNotes.getBoundingBoxTop(), this.width, this.renderer.height);
48471
+ // canvas.color = Color.random();
48472
+ // const top = this.getBoundingBoxTop();
48473
+ // const bottom = this.getBoundingBoxBottom();
48474
+ // canvas.fillRect(cx + this.x, cy + this.y + top, this.width, bottom-top);
48636
48475
  // canvas.color = c;
48637
48476
  // var c = canvas.color;
48638
48477
  // var ta = canvas.textAlign;
@@ -48711,7 +48550,7 @@ class BeatContainerGlyph extends Glyph {
48711
48550
  }
48712
48551
  let visualEndX = 0;
48713
48552
  if (!this.onNotes.isEmpty) {
48714
- visualEndX = cx + this.x + this.onNotes.x + this.onNotes.width;
48553
+ visualEndX = cx + this.x + this.onNotes.x + this.onNotes.onTimeX + this.postBeatStretch;
48715
48554
  }
48716
48555
  else if (!this.preNotes.isEmpty) {
48717
48556
  visualEndX = cx + this.x + this.preNotes.x + this.preNotes.width;
@@ -48720,12 +48559,6 @@ class BeatContainerGlyph extends Glyph {
48720
48559
  visualEndX = cx + this.x + this.width;
48721
48560
  }
48722
48561
  beatBoundings.visualBounds.w = visualEndX - beatBoundings.visualBounds.x;
48723
- const helper = this.renderer.helpers.getBeamingHelperForBeat(this.beat);
48724
- if ((helper && this.drawBeamHelperAsFlags(helper)) || this.beat.graceType !== GraceType.None) {
48725
- beatBoundings.visualBounds.w +=
48726
- this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) *
48727
- (this.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1);
48728
- }
48729
48562
  beatBoundings.visualBounds.y = barBounds.visualBounds.y;
48730
48563
  beatBoundings.visualBounds.h = barBounds.visualBounds.h;
48731
48564
  beatBoundings.realBounds = new Bounds();
@@ -48740,6 +48573,26 @@ class BeatContainerGlyph extends Glyph {
48740
48573
  this.onNotes.buildBoundingsLookup(beatBoundings, cx + this.x, cy + this.y);
48741
48574
  }
48742
48575
  }
48576
+ getBeatX(requestedPosition, useSharedSizes = false) {
48577
+ switch (requestedPosition) {
48578
+ case BeatXPosition.PreNotes:
48579
+ return this.preNotes.x;
48580
+ case BeatXPosition.OnNotes:
48581
+ return this.onNotes.x;
48582
+ case BeatXPosition.MiddleNotes:
48583
+ return this.onNotes.x + this.onNotes.middleX;
48584
+ case BeatXPosition.Stem:
48585
+ return this.onNotes.x + this.onNotes.stemX;
48586
+ case BeatXPosition.PostNotes:
48587
+ const onNoteSize = useSharedSizes
48588
+ ? (this.renderer.layoutingInfo.getBeatSizes(this.beat)?.onBeatSize ?? this.onNotes.width)
48589
+ : this.onNotes.width;
48590
+ return this.onNotes.x + onNoteSize;
48591
+ case BeatXPosition.EndBeat:
48592
+ return this.width;
48593
+ }
48594
+ return this.preNotes.x;
48595
+ }
48743
48596
  }
48744
48597
 
48745
48598
  /**
@@ -56498,6 +56351,31 @@ var EffectBarGlyphSizing;
56498
56351
  EffectBarGlyphSizing[EffectBarGlyphSizing["FullBar"] = 5] = "FullBar";
56499
56352
  })(EffectBarGlyphSizing || (EffectBarGlyphSizing = {}));
56500
56353
 
56354
+ /**
56355
+ * Effect-Glyphs implementing this public interface get notified
56356
+ * as they are expanded over multiple beats.
56357
+ * @internal
56358
+ */
56359
+ class EffectGlyph extends Glyph {
56360
+ /**
56361
+ * Gets or sets the beat where the glyph belongs to.
56362
+ */
56363
+ beat = null;
56364
+ /**
56365
+ * Gets or sets the next glyph of the same type in case
56366
+ * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
56367
+ */
56368
+ nextGlyph = null;
56369
+ /**
56370
+ * Gets or sets the previous glyph of the same type in case
56371
+ * the effect glyph is expanded when using {@link EffectBarGlyphSizing.groupedOnBeat}.
56372
+ */
56373
+ previousGlyph = null;
56374
+ constructor(x = 0, y = 0) {
56375
+ super(x, y);
56376
+ }
56377
+ }
56378
+
56501
56379
  /**
56502
56380
  * @internal
56503
56381
  */
@@ -56625,39 +56503,6 @@ class AlternateEndingsEffectInfo extends EffectInfo {
56625
56503
  }
56626
56504
  }
56627
56505
 
56628
- /**
56629
- * Lists the different position modes for {@link BarRendererBase.getBeatX}
56630
- * @internal
56631
- */
56632
- var BeatXPosition;
56633
- (function (BeatXPosition) {
56634
- /**
56635
- * Gets the pre-notes position which is located before the accidentals
56636
- */
56637
- BeatXPosition[BeatXPosition["PreNotes"] = 0] = "PreNotes";
56638
- /**
56639
- * Gets the on-notes position which is located after the accidentals but before the note heads.
56640
- */
56641
- BeatXPosition[BeatXPosition["OnNotes"] = 1] = "OnNotes";
56642
- /**
56643
- * Gets the middle-notes position which is located after in the exact center of the note heads.
56644
- */
56645
- BeatXPosition[BeatXPosition["MiddleNotes"] = 2] = "MiddleNotes";
56646
- /**
56647
- * Gets position of the stem for this beat
56648
- */
56649
- BeatXPosition[BeatXPosition["Stem"] = 3] = "Stem";
56650
- /**
56651
- * Get the post-notes position which is located at after the note heads.
56652
- */
56653
- BeatXPosition[BeatXPosition["PostNotes"] = 4] = "PostNotes";
56654
- /**
56655
- * Get the end-beat position which is located at the end of the beat. This position is almost
56656
- * equal to the pre-notes position of the next beat.
56657
- */
56658
- BeatXPosition[BeatXPosition["EndBeat"] = 5] = "EndBeat";
56659
- })(BeatXPosition || (BeatXPosition = {}));
56660
-
56661
56506
  /**
56662
56507
  * @internal
56663
56508
  */
@@ -57256,6 +57101,93 @@ class DirectionsEffectInfo extends EffectInfo {
57256
57101
  }
57257
57102
  }
57258
57103
 
57104
+ /**
57105
+ * @internal
57106
+ */
57107
+ class MusicFontGlyph extends EffectGlyph {
57108
+ glyphScale = 0;
57109
+ symbol;
57110
+ center = false;
57111
+ colorOverride;
57112
+ offsetX = 0;
57113
+ offsetY = 0;
57114
+ constructor(x, y, glyphScale, symbol) {
57115
+ super(x, y);
57116
+ this.glyphScale = glyphScale;
57117
+ this.symbol = symbol;
57118
+ }
57119
+ getBoundingBoxTop() {
57120
+ const bBoxTop = this.renderer.smuflMetrics.glyphTop.get(this.symbol);
57121
+ return this.y - this.offsetY - bBoxTop;
57122
+ }
57123
+ doLayout() {
57124
+ this.width = this.renderer.smuflMetrics.glyphWidths.get(this.symbol) * this.glyphScale;
57125
+ this.height = this.renderer.smuflMetrics.glyphHeights.get(this.symbol) * this.glyphScale;
57126
+ }
57127
+ paint(cx, cy, canvas) {
57128
+ if (this.width === 0 && this.height === 0) {
57129
+ return;
57130
+ }
57131
+ const c = canvas.color;
57132
+ if (this.colorOverride) {
57133
+ canvas.color = this.colorOverride;
57134
+ }
57135
+ canvas.fillMusicFontSymbol(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbol, this.center);
57136
+ canvas.color = c;
57137
+ }
57138
+ }
57139
+ /**
57140
+ * @internal
57141
+ */
57142
+ class MusicFontTextGlyph extends EffectGlyph {
57143
+ glyphScale = 0;
57144
+ symbols;
57145
+ center = false;
57146
+ colorOverride;
57147
+ offsetX = 0;
57148
+ offsetY = 0;
57149
+ constructor(x, y, glyphScale, symbols) {
57150
+ super(x, y);
57151
+ this.glyphScale = glyphScale;
57152
+ this.symbols = symbols;
57153
+ }
57154
+ getBoundingBoxTop() {
57155
+ let bBoxTop = 0;
57156
+ for (let i = 0; i < this.symbols.length; i++) {
57157
+ const gTop = this.renderer.smuflMetrics.glyphTop.get(this.symbols[i]);
57158
+ if (i === 0 || gTop < bBoxTop) {
57159
+ bBoxTop = gTop;
57160
+ }
57161
+ }
57162
+ return this.y - this.offsetY - bBoxTop;
57163
+ }
57164
+ doLayout() {
57165
+ this.width = 0;
57166
+ this.height = 0;
57167
+ for (let i = 0; i < this.symbols.length; i++) {
57168
+ const gWidth = this.renderer.smuflMetrics.glyphWidths.get(this.symbols[i]) * this.glyphScale;
57169
+ const gHeight = this.renderer.smuflMetrics.glyphHeights.get(this.symbols[i]) * this.glyphScale;
57170
+ if (i === 0 || gWidth > this.width) {
57171
+ this.width = gWidth;
57172
+ }
57173
+ if (i === 0 || gHeight > this.height) {
57174
+ this.height = gHeight;
57175
+ }
57176
+ }
57177
+ }
57178
+ paint(cx, cy, canvas) {
57179
+ if (this.width === 0 && this.height === 0) {
57180
+ return;
57181
+ }
57182
+ const c = canvas.color;
57183
+ if (this.colorOverride) {
57184
+ canvas.color = this.colorOverride;
57185
+ }
57186
+ canvas.fillMusicFontSymbols(cx + this.x + this.offsetX, cy + this.y + this.offsetY, this.glyphScale, this.symbols, this.center);
57187
+ canvas.color = c;
57188
+ }
57189
+ }
57190
+
57259
57191
  /**
57260
57192
  * @internal
57261
57193
  */
@@ -57494,17 +57426,6 @@ class FermataEffectInfo extends EffectInfo {
57494
57426
  }
57495
57427
  }
57496
57428
 
57497
- /**
57498
- * This simple glyph allows to put an empty region in to a BarRenderer.
57499
- * @internal
57500
- */
57501
- class SpacingGlyph extends Glyph {
57502
- constructor(x, y, width) {
57503
- super(x, y);
57504
- this.width = width;
57505
- }
57506
- }
57507
-
57508
57429
  /**
57509
57430
  * This glyph allows to group several other glyphs to be
57510
57431
  * drawn at the same x position
@@ -57520,34 +57441,20 @@ class GlyphGroup extends Glyph {
57520
57441
  const glyphs = this.glyphs;
57521
57442
  if (glyphs) {
57522
57443
  for (const g of glyphs) {
57523
- // only count real visual glyphs
57524
- if (g instanceof SpacingGlyph) {
57525
- continue;
57526
- }
57527
- const gTop = g.getBoundingBoxTop();
57528
- if (Number.isNaN(top) || gTop < top) {
57529
- top = gTop;
57530
- }
57444
+ top = ModelUtils.minBoundingBox(top, g.getBoundingBoxTop());
57531
57445
  }
57532
57446
  }
57533
- return Number.isNaN(top) ? this.y : top;
57447
+ return top;
57534
57448
  }
57535
57449
  getBoundingBoxBottom() {
57536
57450
  let bottom = Number.NaN;
57537
57451
  const glyphs = this.glyphs;
57538
57452
  if (glyphs) {
57539
57453
  for (const g of glyphs) {
57540
- // only count real visual glyphs
57541
- if (g instanceof SpacingGlyph) {
57542
- continue;
57543
- }
57544
- const gBottom = g.getBoundingBoxBottom();
57545
- if (Number.isNaN(bottom) || gBottom > bottom) {
57546
- bottom = gBottom;
57547
- }
57454
+ bottom = ModelUtils.maxBoundingBox(bottom, g.getBoundingBoxBottom());
57548
57455
  }
57549
57456
  }
57550
- return Number.isNaN(bottom) ? this.y + this.height : bottom;
57457
+ return bottom;
57551
57458
  }
57552
57459
  doLayout() {
57553
57460
  if (!this.glyphs || this.glyphs.length === 0) {
@@ -57963,6 +57870,38 @@ class FreeTimeEffectInfo extends EffectInfo {
57963
57870
  }
57964
57871
  }
57965
57872
 
57873
+ /**
57874
+ * @internal
57875
+ */
57876
+ class NoteHeadGlyph extends MusicFontGlyph {
57877
+ // TODO: SmuFL
57878
+ static GraceScale = 0.75;
57879
+ centerOnStem = false;
57880
+ constructor(x, y, duration, isGrace) {
57881
+ super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, NoteHeadGlyph.getSymbol(duration));
57882
+ }
57883
+ paint(cx, cy, canvas) {
57884
+ if (this.centerOnStem) {
57885
+ this.center = true;
57886
+ }
57887
+ super.paint(cx, cy, canvas);
57888
+ }
57889
+ static getSymbol(duration) {
57890
+ switch (duration) {
57891
+ case Duration.QuadrupleWhole:
57892
+ return MusicFontSymbol.NoteheadDoubleWholeSquare;
57893
+ case Duration.DoubleWhole:
57894
+ return MusicFontSymbol.NoteheadDoubleWhole;
57895
+ case Duration.Whole:
57896
+ return MusicFontSymbol.NoteheadWhole;
57897
+ case Duration.Half:
57898
+ return MusicFontSymbol.NoteheadHalf;
57899
+ default:
57900
+ return MusicFontSymbol.NoteheadBlack;
57901
+ }
57902
+ }
57903
+ }
57904
+
57966
57905
  /**
57967
57906
  * @internal
57968
57907
  */
@@ -59581,12 +59520,12 @@ class VoiceContainerGlyph extends GlyphGroup {
59581
59520
  // of the next glyph
59582
59521
  if (i > 0) {
59583
59522
  const beatWidth = currentBeatGlyph.x - beatGlyphs[i - 1].x;
59584
- beatGlyphs[i - 1].scaleToWidth(beatWidth);
59523
+ beatGlyphs[i - 1].width = beatWidth;
59585
59524
  }
59586
59525
  // for the last glyph size based on the full width
59587
59526
  if (i === j - 1) {
59588
59527
  const beatWidth = this.width - beatGlyphs[beatGlyphs.length - 1].x;
59589
- currentBeatGlyph.scaleToWidth(beatWidth);
59528
+ currentBeatGlyph.width = beatWidth;
59590
59529
  }
59591
59530
  }
59592
59531
  }
@@ -61286,7 +61225,7 @@ class StaffSystem {
61286
61225
  if (!masterBarBoundsLookup.has(renderer.bar.masterBar.index)) {
61287
61226
  masterBarBounds = new MasterBarBounds();
61288
61227
  masterBarBounds.index = renderer.bar.masterBar.index;
61289
- masterBarBounds.isFirstOfLine = renderer.isFirstOfLine;
61228
+ masterBarBounds.isFirstOfLine = renderer.isFirstOfStaff;
61290
61229
  masterBarBounds.realBounds = new Bounds();
61291
61230
  masterBarBounds.realBounds.x = x + renderer.x;
61292
61231
  masterBarBounds.realBounds.y = realTop;
@@ -61818,11 +61757,9 @@ class BeatGlyphBase extends GlyphGroup {
61818
61757
  * @internal
61819
61758
  */
61820
61759
  class BeatOnNoteGlyphBase extends BeatGlyphBase {
61821
- beamingHelper;
61822
61760
  onTimeX = 0;
61823
61761
  middleX = 0;
61824
- updateBeamingHelper() {
61825
- }
61762
+ stemX = 0;
61826
61763
  buildBoundingsLookup(_beatBounds, _cx, _cy) {
61827
61764
  }
61828
61765
  getNoteX(_note, _requestedPosition) {
@@ -61964,14 +61901,6 @@ class MultiBarRestBeatContainerGlyph extends BeatContainerGlyph {
61964
61901
  }
61965
61902
  }
61966
61903
 
61967
- /**
61968
- * @internal
61969
- */
61970
- class BeatLinePositions {
61971
- staffId = '';
61972
- up = 0;
61973
- down = 0;
61974
- }
61975
61904
  /**
61976
61905
  * @internal
61977
61906
  */
@@ -62002,10 +61931,7 @@ class BeamingHelperDrawInfo {
62002
61931
  */
62003
61932
  class BeamingHelper {
62004
61933
  _staff;
62005
- _beatLineXPositions = new Map();
62006
61934
  _renderer;
62007
- _firstNonRestBeat = null;
62008
- _lastNonRestBeat = null;
62009
61935
  voice = null;
62010
61936
  beats = [];
62011
61937
  shortestDuration = Duration.QuadrupleWhole;
@@ -62015,6 +61941,7 @@ class BeamingHelper {
62015
61941
  */
62016
61942
  hasTuplet = false;
62017
61943
  slashBeats = [];
61944
+ restBeats = [];
62018
61945
  lowestNoteInHelper = null;
62019
61946
  _lowestNoteCompareValueInHelper = -1;
62020
61947
  highestNoteInHelper = null;
@@ -62022,25 +61949,21 @@ class BeamingHelper {
62022
61949
  invertBeamDirection = false;
62023
61950
  preferredBeamDirection = null;
62024
61951
  graceType = GraceType.None;
62025
- minRestSteps = null;
62026
- beatOfMinRestSteps = null;
62027
- maxRestSteps = null;
62028
- beatOfMaxRestSteps = null;
62029
61952
  get isRestBeamHelper() {
62030
61953
  return this.beats.length === 1 && this.beats[0].isRest;
62031
61954
  }
62032
- hasLine(forceFlagOnSingleBeat, beat) {
62033
- return ((forceFlagOnSingleBeat && this._beatHasLine(beat)) ||
62034
- (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasLine(beat)));
61955
+ hasStem(forceFlagOnSingleBeat, beat) {
61956
+ return ((forceFlagOnSingleBeat && this._beatHasStem(beat)) ||
61957
+ (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasStem(beat)));
62035
61958
  }
62036
- _beatHasLine(beat) {
61959
+ _beatHasStem(beat) {
62037
61960
  return beat.duration > Duration.Whole;
62038
61961
  }
62039
61962
  hasFlag(forceFlagOnSingleBeat, beat) {
62040
- return ((forceFlagOnSingleBeat && this._beatHasFlag(beat)) ||
62041
- (!forceFlagOnSingleBeat && this.beats.length === 1 && this._beatHasFlag(this.beats[0])));
61963
+ return ((forceFlagOnSingleBeat && BeamingHelper.beatHasFlag(beat)) ||
61964
+ (!forceFlagOnSingleBeat && this.beats.length === 1 && BeamingHelper.beatHasFlag(this.beats[0])));
62042
61965
  }
62043
- _beatHasFlag(beat) {
61966
+ static beatHasFlag(beat) {
62044
61967
  return (!beat.deadSlapped && !beat.isRest && (beat.duration > Duration.Quarter || beat.graceType !== GraceType.None));
62045
61968
  }
62046
61969
  constructor(staff, renderer) {
@@ -62048,38 +61971,12 @@ class BeamingHelper {
62048
61971
  this._renderer = renderer;
62049
61972
  this.beats = [];
62050
61973
  }
62051
- getBeatLineX(beat, direction) {
62052
- direction = direction ?? this.direction;
62053
- if (this.hasBeatLineX(beat)) {
62054
- if (direction === BeamDirection.Up) {
62055
- return this._beatLineXPositions.get(beat.index).up;
62056
- }
62057
- return this._beatLineXPositions.get(beat.index).down;
62058
- }
62059
- return 0;
62060
- }
62061
- hasBeatLineX(beat) {
62062
- return this._beatLineXPositions.has(beat.index);
62063
- }
62064
- registerBeatLineX(staffId, beat, up, down) {
62065
- const positions = this._getOrCreateBeatPositions(beat);
62066
- positions.staffId = staffId;
62067
- positions.up = up;
62068
- positions.down = down;
61974
+ alignWithBeats() {
62069
61975
  for (const v of this.drawingInfos.values()) {
62070
- if (v.startBeat === beat) {
62071
- v.startX = this.getBeatLineX(beat);
62072
- }
62073
- else if (v.endBeat === beat) {
62074
- v.endX = this.getBeatLineX(beat);
62075
- }
62076
- }
62077
- }
62078
- _getOrCreateBeatPositions(beat) {
62079
- if (!this._beatLineXPositions.has(beat.index)) {
62080
- this._beatLineXPositions.set(beat.index, new BeatLinePositions());
61976
+ v.startX = this._renderer.getBeatX(v.startBeat, BeatXPosition.Stem);
61977
+ v.endX = this._renderer.getBeatX(v.endBeat, BeatXPosition.Stem);
61978
+ this.drawingInfos.clear();
62081
61979
  }
62082
- return this._beatLineXPositions.get(beat.index);
62083
61980
  }
62084
61981
  direction = BeamDirection.Up;
62085
61982
  finish() {
@@ -62145,36 +62042,6 @@ class BeamingHelper {
62145
62042
  }
62146
62043
  return [0, 0];
62147
62044
  }
62148
- /**
62149
- * Registers a rest beat within the accidental helper so the rest
62150
- * symbol is considered properly during beaming.
62151
- * @param beat The rest beat.
62152
- * @param steps The steps on which the rest symbol is placed
62153
- */
62154
- applyRest(beat, steps) {
62155
- // do not accept rests after the last beat which has notes
62156
- if ((this._lastNonRestBeat && beat.index >= this._lastNonRestBeat.index) ||
62157
- (this._firstNonRestBeat && beat.index <= this._firstNonRestBeat.index)) {
62158
- return;
62159
- }
62160
- // correct the line of the glyph to a note which would
62161
- // be placed at the upper / lower end of the glyph.
62162
- let aboveRest = steps;
62163
- let belowRest = steps;
62164
- const offsets = BeamingHelper.computeLineHeightsForRest(beat.duration);
62165
- aboveRest -= offsets[0];
62166
- belowRest += offsets[1];
62167
- const minRestSteps = this.minRestSteps;
62168
- const maxRestSteps = this.maxRestSteps;
62169
- if (minRestSteps === null || minRestSteps > aboveRest) {
62170
- this.minRestSteps = aboveRest;
62171
- this.beatOfMinRestSteps = beat;
62172
- }
62173
- if (maxRestSteps === null || maxRestSteps < belowRest) {
62174
- this.maxRestSteps = belowRest;
62175
- this.beatOfMaxRestSteps = beat;
62176
- }
62177
- }
62178
62045
  _invert(direction) {
62179
62046
  if (!this.invertBeamDirection) {
62180
62047
  return direction;
@@ -62238,14 +62105,13 @@ class BeamingHelper {
62238
62105
  if (this.shortestDuration < beat.duration) {
62239
62106
  this.shortestDuration = beat.duration;
62240
62107
  }
62241
- if (!this._firstNonRestBeat) {
62242
- this._firstNonRestBeat = beat;
62243
- }
62244
- this._lastNonRestBeat = beat;
62245
62108
  }
62246
62109
  else if (this.beats.length === 0) {
62247
62110
  this.beats.push(beat);
62248
62111
  }
62112
+ else {
62113
+ this.restBeats.push(beat);
62114
+ }
62249
62115
  if (beat.slashed) {
62250
62116
  this.slashBeats.push(beat);
62251
62117
  }
@@ -62354,19 +62220,6 @@ class BeamingHelper {
62354
62220
  get beatOfHighestNote() {
62355
62221
  return this.highestNoteInHelper.beat;
62356
62222
  }
62357
- /**
62358
- * Returns whether the the position of the given beat, was registered by the staff of the given ID
62359
- * @param staffId
62360
- * @param beat
62361
- * @returns
62362
- */
62363
- isPositionFrom(staffId, beat) {
62364
- if (!this._beatLineXPositions.has(beat.index)) {
62365
- return true;
62366
- }
62367
- return (this._beatLineXPositions.get(beat.index).staffId === staffId ||
62368
- !this._beatLineXPositions.get(beat.index).staffId);
62369
- }
62370
62223
  drawingInfos = new Map();
62371
62224
  }
62372
62225
 
@@ -62518,8 +62371,8 @@ class BarCollisionHelper {
62518
62371
  */
62519
62372
  class BarHelpers {
62520
62373
  _renderer;
62374
+ _beamHelperLookup = new Map();
62521
62375
  beamHelpers = [];
62522
- beamHelperLookup = [];
62523
62376
  collisionHelper;
62524
62377
  preferredBeamDirection = null;
62525
62378
  constructor(renderer) {
@@ -62534,7 +62387,6 @@ class BarHelpers {
62534
62387
  for (let i = 0, j = bar.voices.length; i < j; i++) {
62535
62388
  const v = bar.voices[i];
62536
62389
  this.beamHelpers.push([]);
62537
- this.beamHelperLookup.push(new Map());
62538
62390
  for (let k = 0, l = v.beats.length; k < l; k++) {
62539
62391
  const b = v.beats[k];
62540
62392
  let helperForBeat;
@@ -62543,6 +62395,9 @@ class BarHelpers {
62543
62395
  }
62544
62396
  else {
62545
62397
  helperForBeat = currentBeamHelper;
62398
+ if (currentGraceBeamHelper) {
62399
+ currentGraceBeamHelper.finish();
62400
+ }
62546
62401
  currentGraceBeamHelper = null;
62547
62402
  }
62548
62403
  // if a new beaming helper was started, we close our tuplet grouping as well
@@ -62563,7 +62418,7 @@ class BarHelpers {
62563
62418
  }
62564
62419
  this.beamHelpers[v.index].push(helperForBeat);
62565
62420
  }
62566
- this.beamHelperLookup[v.index].set(b.index, helperForBeat);
62421
+ this._beamHelperLookup.set(b.id, helperForBeat);
62567
62422
  }
62568
62423
  if (currentBeamHelper) {
62569
62424
  currentBeamHelper.finish();
@@ -62576,7 +62431,7 @@ class BarHelpers {
62576
62431
  }
62577
62432
  }
62578
62433
  getBeamingHelperForBeat(beat) {
62579
- return this.beamHelperLookup[beat.voice.index].get(beat.index);
62434
+ return this._beamHelperLookup.has(beat.id) ? this._beamHelperLookup.get(beat.id) : undefined;
62580
62435
  }
62581
62436
  }
62582
62437
 
@@ -62686,6 +62541,9 @@ class BarRendererBase {
62686
62541
  return this._contentBottomOverflow + this.bottomEffects.height;
62687
62542
  }
62688
62543
  helpers;
62544
+ get collisionHelper() {
62545
+ return this.helpers.collisionHelper;
62546
+ }
62689
62547
  /**
62690
62548
  * Gets or sets whether this renderer is linked to the next one
62691
62549
  * by some glyphs like a vibrato effect
@@ -62745,6 +62603,11 @@ class BarRendererBase {
62745
62603
  for (const container of this._voiceContainers.values()) {
62746
62604
  container.scaleToWidth(containerWidth);
62747
62605
  }
62606
+ for (const v of this.helpers.beamHelpers) {
62607
+ for (const h of v) {
62608
+ h.alignWithBeats();
62609
+ }
62610
+ }
62748
62611
  this._postBeatGlyphs.x = this._preBeatGlyphs.x + this._preBeatGlyphs.width + containerWidth;
62749
62612
  this.width = width;
62750
62613
  this.topEffects.alignGlyphs();
@@ -62773,10 +62636,13 @@ class BarRendererBase {
62773
62636
  get barDisplayWidth() {
62774
62637
  return this.staff.system.staves.length > 1 ? this.bar.masterBar.displayWidth : this.bar.displayWidth;
62775
62638
  }
62776
- wasFirstOfLine = false;
62777
- get isFirstOfLine() {
62639
+ wasFirstOfStaff = false;
62640
+ get isFirstOfStaff() {
62778
62641
  return this.index === 0;
62779
62642
  }
62643
+ get isLastOfStaff() {
62644
+ return this.index === this.staff.barRenderers.length - 1;
62645
+ }
62780
62646
  get isLast() {
62781
62647
  return !this.bar || this.bar.index === this.scoreRenderer.layout.lastBarIndex;
62782
62648
  }
@@ -62991,6 +62857,18 @@ class BarRendererBase {
62991
62857
  }
62992
62858
  }
62993
62859
  }
62860
+ for (const v of this._voiceContainers.values()) {
62861
+ for (const b of v.beatGlyphs) {
62862
+ const topY = b.getBoundingBoxTop();
62863
+ if (topY < 0) {
62864
+ this.registerOverflowTop(topY * -1);
62865
+ }
62866
+ const bottomY = b.getBoundingBoxBottom();
62867
+ if (bottomY > rendererBottom) {
62868
+ this.registerOverflowBottom(bottomY - rendererBottom);
62869
+ }
62870
+ }
62871
+ }
62994
62872
  const beatEffectsMinY = this.beatEffectsMinY;
62995
62873
  if (!Number.isNaN(beatEffectsMinY)) {
62996
62874
  const beatEffectTopOverflow = -beatEffectsMinY;
@@ -63044,7 +62922,6 @@ class BarRendererBase {
63044
62922
  g.renderer = this;
63045
62923
  g.preNotes.renderer = this;
63046
62924
  g.onNotes.renderer = this;
63047
- g.onNotes.beamingHelper = this.helpers.beamHelperLookup[g.beat.voice.index].get(g.beat.index);
63048
62925
  this.getVoiceContainer(g.beat.voice).addGlyph(g);
63049
62926
  }
63050
62927
  getVoiceContainer(voice) {
@@ -63123,7 +63000,7 @@ class BarRendererBase {
63123
63000
  this._postBeatGlyphs.addGlyph(g);
63124
63001
  }
63125
63002
  createPreBeatGlyphs() {
63126
- this.wasFirstOfLine = this.isFirstOfLine;
63003
+ this.wasFirstOfStaff = this.isFirstOfStaff;
63127
63004
  }
63128
63005
  createBeatGlyphs() {
63129
63006
  for (const voice of this.bar.voices) {
@@ -63150,26 +63027,7 @@ class BarRendererBase {
63150
63027
  getBeatX(beat, requestedPosition = BeatXPosition.PreNotes, useSharedSizes = false) {
63151
63028
  const container = this.getBeatContainer(beat);
63152
63029
  if (container) {
63153
- switch (requestedPosition) {
63154
- case BeatXPosition.PreNotes:
63155
- return container.voiceContainer.x + container.x;
63156
- case BeatXPosition.OnNotes:
63157
- return container.voiceContainer.x + container.x + container.onNotes.x;
63158
- case BeatXPosition.MiddleNotes:
63159
- return container.voiceContainer.x + container.x + container.onNotes.x + container.onNotes.middleX;
63160
- case BeatXPosition.Stem:
63161
- const offset = container.onNotes.beamingHelper
63162
- ? container.onNotes.beamingHelper.getBeatLineX(beat)
63163
- : container.onNotes.x + container.onNotes.width / 2;
63164
- return container.voiceContainer.x + offset;
63165
- case BeatXPosition.PostNotes:
63166
- const onNoteSize = useSharedSizes
63167
- ? (this.layoutingInfo.getBeatSizes(beat)?.onBeatSize ?? container.onNotes.width)
63168
- : container.onNotes.width;
63169
- return container.voiceContainer.x + container.x + container.onNotes.x + onNoteSize;
63170
- case BeatXPosition.EndBeat:
63171
- return container.voiceContainer.x + container.x + container.width;
63172
- }
63030
+ return container.voiceContainer.x + container.x + container.getBeatX(requestedPosition, useSharedSizes);
63173
63031
  }
63174
63032
  return 0;
63175
63033
  }
@@ -63204,7 +63062,7 @@ class BarRendererBase {
63204
63062
  this.updateSizes();
63205
63063
  // there are some glyphs which are shown only for renderers at the line start, so we simply recreate them
63206
63064
  // 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
63207
- if ((this.wasFirstOfLine && !this.isFirstOfLine) || (!this.wasFirstOfLine && this.isFirstOfLine)) {
63065
+ if ((this.wasFirstOfStaff && !this.isFirstOfStaff) || (!this.wasFirstOfStaff && this.isFirstOfStaff)) {
63208
63066
  this.recreatePreBeatGlyphs();
63209
63067
  this._postBeatGlyphs.doLayout();
63210
63068
  }
@@ -63241,7 +63099,7 @@ class BarRendererBase {
63241
63099
  completeBeamingHelper(_helper) {
63242
63100
  }
63243
63101
  getBeatDirection(beat) {
63244
- return this.helpers.getBeamingHelperForBeat(beat).direction;
63102
+ return this.helpers.getBeamingHelperForBeat(beat)?.direction ?? BeamDirection.Up;
63245
63103
  }
63246
63104
  }
63247
63105
 
@@ -65608,8 +65466,18 @@ class BarLineGlyph extends LeftToRightLayoutingGlyphGroup {
65608
65466
  }
65609
65467
  const lineRenderer = this.renderer;
65610
65468
  const lineYOffset = lineRenderer.smuflMetrics.staffLineThickness;
65611
- const top = this.y - lineYOffset;
65612
- const bottom = this.y + this.renderer.height;
65469
+ let top = this.y;
65470
+ let bottom = this.y;
65471
+ if (lineRenderer.drawnLineCount < 2 ||
65472
+ (!this._isRight && lineRenderer.isFirstOfStaff) ||
65473
+ (this._isRight && lineRenderer.isLastOfStaff)) {
65474
+ top -= lineYOffset;
65475
+ bottom += lineRenderer.height;
65476
+ }
65477
+ else {
65478
+ top += lineRenderer.getLineY(0);
65479
+ bottom += lineRenderer.getLineY(lineRenderer.drawnLineCount - 1);
65480
+ }
65613
65481
  const h = bottom - top;
65614
65482
  // round up to have pixel-aligned bar lines, x-shift will be used during rendering
65615
65483
  // to avoid shifting again all glyphs
@@ -65695,6 +65563,54 @@ class BarNumberGlyph extends Glyph {
65695
65563
  }
65696
65564
  }
65697
65565
 
65566
+ /**
65567
+ * @internal
65568
+ */
65569
+ class FlagGlyph extends MusicFontGlyph {
65570
+ constructor(x, y, duration, direction, isGrace) {
65571
+ super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
65572
+ }
65573
+ static getSymbol(duration, direction, isGrace) {
65574
+ if (isGrace) {
65575
+ duration = Duration.Eighth;
65576
+ }
65577
+ if (direction === BeamDirection.Up) {
65578
+ switch (duration) {
65579
+ case Duration.Eighth:
65580
+ return MusicFontSymbol.Flag8thUp;
65581
+ case Duration.Sixteenth:
65582
+ return MusicFontSymbol.Flag16thUp;
65583
+ case Duration.ThirtySecond:
65584
+ return MusicFontSymbol.Flag32ndUp;
65585
+ case Duration.SixtyFourth:
65586
+ return MusicFontSymbol.Flag64thUp;
65587
+ case Duration.OneHundredTwentyEighth:
65588
+ return MusicFontSymbol.Flag128thUp;
65589
+ case Duration.TwoHundredFiftySixth:
65590
+ return MusicFontSymbol.Flag256thUp;
65591
+ default:
65592
+ return MusicFontSymbol.Flag8thUp;
65593
+ }
65594
+ }
65595
+ switch (duration) {
65596
+ case Duration.Eighth:
65597
+ return MusicFontSymbol.Flag8thDown;
65598
+ case Duration.Sixteenth:
65599
+ return MusicFontSymbol.Flag16thDown;
65600
+ case Duration.ThirtySecond:
65601
+ return MusicFontSymbol.Flag32ndDown;
65602
+ case Duration.SixtyFourth:
65603
+ return MusicFontSymbol.Flag64thDown;
65604
+ case Duration.OneHundredTwentyEighth:
65605
+ return MusicFontSymbol.Flag128thDown;
65606
+ case Duration.TwoHundredFiftySixth:
65607
+ return MusicFontSymbol.Flag128thDown;
65608
+ default:
65609
+ return MusicFontSymbol.Flag8thDown;
65610
+ }
65611
+ }
65612
+ }
65613
+
65698
65614
  /**
65699
65615
  * @internal
65700
65616
  */
@@ -65730,6 +65646,23 @@ class RepeatCountGlyph extends Glyph {
65730
65646
  }
65731
65647
  }
65732
65648
 
65649
+ /**
65650
+ * This simple glyph allows to put an empty region in to a BarRenderer.
65651
+ * @internal
65652
+ */
65653
+ class SpacingGlyph extends Glyph {
65654
+ constructor(x, y, width) {
65655
+ super(x, y);
65656
+ this.width = width;
65657
+ }
65658
+ getBoundingBoxTop() {
65659
+ return Number.NaN;
65660
+ }
65661
+ getBoundingBoxBottom() {
65662
+ return Number.NaN;
65663
+ }
65664
+ }
65665
+
65733
65666
  /**
65734
65667
  * This is a base class for any bar renderer which renders music notation on a staff
65735
65668
  * with lines like Standard Notation, Guitar Tablatures and Slash Notation.
@@ -65853,7 +65786,7 @@ class LineBarRenderer extends BarRendererBase {
65853
65786
  if (this.hasVoiceContainer(voice)) {
65854
65787
  const container = this.getVoiceContainer(voice);
65855
65788
  for (const tupletGroup of container.tupletGroups) {
65856
- this._paintTupletHelper(cx + this.beatGlyphsStart, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65789
+ this._paintTupletHelper(cx, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
65857
65790
  }
65858
65791
  }
65859
65792
  }
@@ -65927,26 +65860,27 @@ class LineBarRenderer extends BarRendererBase {
65927
65860
  // check if we need to paint simple footer
65928
65861
  const offset = this.tupletOffset;
65929
65862
  const size = this.tupletSize;
65863
+ const shift = offset + size * 0.5;
65930
65864
  const _ = ElementStyleHelper.beat(canvas, beatElement, h.beats[0]);
65931
65865
  try {
65932
65866
  const l = canvas.lineWidth;
65933
65867
  canvas.lineWidth = this.smuflMetrics.tupletBracketThickness;
65934
65868
  if (h.beats.length === 1 || !h.isFull) {
65935
65869
  for (const beat of h.beats) {
65936
- const beamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(beat.index);
65870
+ const beamingHelper = this.helpers.getBeamingHelperForBeat(beat);
65937
65871
  if (!beamingHelper) {
65938
65872
  continue;
65939
65873
  }
65940
65874
  const direction = this.getTupletBeamDirection(beamingHelper);
65941
- const tupletX = beamingHelper.getBeatLineX(beat);
65875
+ const tupletX = this.getBeatX(beat, BeatXPosition.Stem);
65942
65876
  let tupletY = this.calculateBeamYWithDirection(beamingHelper, tupletX, direction);
65943
65877
  if (direction === BeamDirection.Down) {
65944
- tupletY += offset + size;
65878
+ tupletY += shift;
65945
65879
  }
65946
65880
  else {
65947
- tupletY -= offset + size;
65881
+ tupletY -= shift;
65948
65882
  }
65949
- canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY, 1, s, true);
65883
+ canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY + size * 0.5, 1, s, true);
65950
65884
  }
65951
65885
  }
65952
65886
  else {
@@ -65976,12 +65910,12 @@ class LineBarRenderer extends BarRendererBase {
65976
65910
  }
65977
65911
  //
65978
65912
  // Calculate the overall area of the tuplet bracket
65979
- const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes) - this.beatGlyphsStart;
65980
- const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes) - this.beatGlyphsStart;
65913
+ const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes);
65914
+ const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes);
65981
65915
  //
65982
65916
  // calculate the y positions for our bracket
65983
- const firstNonRestBeamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(firstNonRestBeat.index);
65984
- const lastNonRestBeamingHelper = this.helpers.beamHelperLookup[h.voice.index].get(lastNonRestBeat.index);
65917
+ const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
65918
+ const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
65985
65919
  const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
65986
65920
  let startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
65987
65921
  let endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
@@ -65990,7 +65924,6 @@ class LineBarRenderer extends BarRendererBase {
65990
65924
  endY = startY;
65991
65925
  }
65992
65926
  // align line centered in available space
65993
- const shift = offset + size * 0.5;
65994
65927
  if (direction === BeamDirection.Down) {
65995
65928
  startY += shift;
65996
65929
  endY += shift;
@@ -66055,14 +65988,24 @@ class LineBarRenderer extends BarRendererBase {
66055
65988
  paintBeams(cx, cy, canvas, flagsElement, beamsElement) {
66056
65989
  for (const v of this.helpers.beamHelpers) {
66057
65990
  for (const h of v) {
66058
- this._paintBeamHelper(cx + this.beatGlyphsStart, cy, canvas, h, flagsElement, beamsElement);
65991
+ this.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
66059
65992
  }
66060
65993
  }
66061
65994
  }
66062
65995
  drawBeamHelperAsFlags(h) {
66063
65996
  return h.beats.length === 1;
66064
65997
  }
66065
- _paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
65998
+ hasFlag(beat) {
65999
+ if (beat.isRest) {
66000
+ return false;
66001
+ }
66002
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
66003
+ if (helper) {
66004
+ return helper.hasFlag(this.drawBeamHelperAsFlags(helper), beat);
66005
+ }
66006
+ return BeamingHelper.beatHasFlag(beat);
66007
+ }
66008
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
66066
66009
  canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
66067
66010
  if (!h.isRestBeamHelper) {
66068
66011
  if (this.drawBeamHelperAsFlags(h)) {
@@ -66073,7 +66016,7 @@ class LineBarRenderer extends BarRendererBase {
66073
66016
  }
66074
66017
  }
66075
66018
  }
66076
- shouldPaintFlag(beat, h) {
66019
+ shouldPaintFlag(beat) {
66077
66020
  // no flags for bend grace beats
66078
66021
  if (beat.graceType === GraceType.BendGrace) {
66079
66022
  return false;
@@ -66081,10 +66024,6 @@ class LineBarRenderer extends BarRendererBase {
66081
66024
  if (beat.deadSlapped) {
66082
66025
  return false;
66083
66026
  }
66084
- // we don't have an X-position: cannot paint a flag
66085
- if (!h.hasBeatLineX(beat)) {
66086
- return false;
66087
- }
66088
66027
  // no flags for any grace notes on songbook mode
66089
66028
  if (beat.graceType !== GraceType.None && this.settings.notation.notationMode === NotationMode.SongBook) {
66090
66029
  return false;
@@ -66099,14 +66038,14 @@ class LineBarRenderer extends BarRendererBase {
66099
66038
  }
66100
66039
  paintFlag(cx, cy, canvas, h, flagsElement) {
66101
66040
  for (const beat of h.beats) {
66102
- if (!this.shouldPaintFlag(beat, h)) {
66041
+ if (!this.shouldPaintFlag(beat)) {
66103
66042
  continue;
66104
66043
  }
66105
66044
  const isGrace = beat.graceType !== GraceType.None;
66106
66045
  //
66107
66046
  // draw line
66108
66047
  //
66109
- const beatLineX = h.getBeatLineX(beat);
66048
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66110
66049
  const direction = this.getBeamDirection(h);
66111
66050
  const topY = cy + this.y + this.getFlagTopY(beat, direction);
66112
66051
  const bottomY = cy + this.y + this.getFlagBottomY(beat, direction);
@@ -66117,7 +66056,7 @@ class LineBarRenderer extends BarRendererBase {
66117
66056
  else {
66118
66057
  flagY = topY;
66119
66058
  }
66120
- if (!h.hasLine(true, beat)) {
66059
+ if (!h.hasStem(true, beat)) {
66121
66060
  continue;
66122
66061
  }
66123
66062
  this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, topY, bottomY, canvas);
@@ -66203,10 +66142,10 @@ class LineBarRenderer extends BarRendererBase {
66203
66142
  }
66204
66143
  for (let i = 0, j = h.beats.length; i < j; i++) {
66205
66144
  const beat = h.beats[i];
66206
- if (!h.hasBeatLineX(beat) || beat.deadSlapped) {
66145
+ if (beat.deadSlapped) {
66207
66146
  continue;
66208
66147
  }
66209
- const beatLineX = h.getBeatLineX(beat);
66148
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66210
66149
  const y1 = cy + this.y + this.getBarLineStart(beat, direction);
66211
66150
  // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
66212
66151
  // when combining stems and beams on sub-pixel level
@@ -66244,7 +66183,7 @@ class LineBarRenderer extends BarRendererBase {
66244
66183
  barEndY = barY + this.calculateBeamY(h, barEndX);
66245
66184
  LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66246
66185
  // end part
66247
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66186
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66248
66187
  barStartX = barEndX - brokenBarOffset;
66249
66188
  barStartY = barY + this.calculateBeamY(h, barStartX);
66250
66189
  barEndY = barY + this.calculateBeamY(h, barEndX);
@@ -66254,7 +66193,7 @@ class LineBarRenderer extends BarRendererBase {
66254
66193
  if (isFullBarJoin) {
66255
66194
  // full bar?
66256
66195
  barStartX = beatLineX;
66257
- barEndX = h.getBeatLineX(h.beats[i + 1]);
66196
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66258
66197
  }
66259
66198
  else if (i === 0 || !BeamingHelper.isFullBarJoin(h.beats[i - 1], beat, barIndex)) {
66260
66199
  barStartX = beatLineX;
@@ -66289,7 +66228,7 @@ class LineBarRenderer extends BarRendererBase {
66289
66228
  }
66290
66229
  }
66291
66230
  if (h.graceType === GraceType.BeforeBeat) {
66292
- const beatLineX = h.getBeatLineX(h.beats[0]);
66231
+ const beatLineX = this.getBeatX(h.beats[0], BeatXPosition.Stem);
66293
66232
  const flagWidth = this.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
66294
66233
  let slashY = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
66295
66234
  slashY += barSize + barSpacing;
@@ -66316,20 +66255,7 @@ class LineBarRenderer extends BarRendererBase {
66316
66255
  const noteOverflowPadding = this.getLineHeight(0.5);
66317
66256
  for (const v of this.helpers.beamHelpers) {
66318
66257
  for (const h of v) {
66319
- if (h.isRestBeamHelper) {
66320
- if (h.minRestSteps) {
66321
- const topY = this.getLineY(h.maxRestSteps / 2) - noteOverflowPadding;
66322
- if (topY < maxNoteY) {
66323
- maxNoteY = topY;
66324
- }
66325
- }
66326
- if (h.maxRestSteps) {
66327
- const bottomY = this.getLineY(h.maxRestSteps & 2) + noteOverflowPadding;
66328
- if (bottomY < maxNoteY) {
66329
- maxNoteY = bottomY;
66330
- }
66331
- }
66332
- }
66258
+ if (h.isRestBeamHelper) ;
66333
66259
  else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
66334
66260
  if (h.direction === BeamDirection.Up) {
66335
66261
  let topY = this.getFlagTopY(h.beats[0], h.direction);
@@ -66380,17 +66306,6 @@ class LineBarRenderer extends BarRendererBase {
66380
66306
  }
66381
66307
  }
66382
66308
  }
66383
- const beatContainer = this.getBeatContainer(h.beats[0]);
66384
- if (beatContainer) {
66385
- const bBoxTop = beatContainer.getBoundingBoxTop();
66386
- const bBoxBottom = beatContainer.getBoundingBoxBottom();
66387
- if (bBoxBottom > minNoteY) {
66388
- minNoteY = bBoxBottom;
66389
- }
66390
- if (bBoxTop < maxNoteY) {
66391
- maxNoteY = bBoxTop;
66392
- }
66393
- }
66394
66309
  }
66395
66310
  }
66396
66311
  if (maxNoteY < rendererTop) {
@@ -66406,25 +66321,6 @@ class LineBarRenderer extends BarRendererBase {
66406
66321
  }
66407
66322
  const scale = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66408
66323
  const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
66409
- let stemSize = this.smuflMetrics.standardStemLength * scale;
66410
- if (h.tremoloDuration) {
66411
- // for 16th and shorter beats we need more space for all tremolos
66412
- // for 8th beats we need only more space for 32nd tremolos
66413
- // the logic here is not perfect but there is no SMuFL guideline
66414
- // on how tremolos need to extend stems
66415
- const oneBeamSize = (this.smuflMetrics.beamThickness + this.smuflMetrics.beamSpacing) * scale;
66416
- if (h.shortestDuration > Duration.Eighth) {
66417
- if (h.tremoloDuration === Duration.Eighth) {
66418
- stemSize += oneBeamSize;
66419
- }
66420
- else {
66421
- stemSize += oneBeamSize * 1.5;
66422
- }
66423
- }
66424
- else if (h.tremoloDuration === Duration.ThirtySecond) {
66425
- stemSize += oneBeamSize * 1.5;
66426
- }
66427
- }
66428
66324
  const drawingInfo = new BeamingHelperDrawInfo();
66429
66325
  h.drawingInfos.set(direction, drawingInfo);
66430
66326
  // the beaming logic works like this:
@@ -66437,33 +66333,17 @@ class LineBarRenderer extends BarRendererBase {
66437
66333
  const isRest = h.isRestBeamHelper;
66438
66334
  // 1. put direct diagonal line.
66439
66335
  drawingInfo.startBeat = firstBeat;
66440
- drawingInfo.startX = h.getBeatLineX(firstBeat);
66441
- if (isRest) {
66442
- drawingInfo.startY =
66443
- direction === BeamDirection.Up
66444
- ? this.getLineY(h.minRestSteps / 2)
66445
- : this.getLineY(h.maxRestSteps / 2);
66446
- }
66447
- else {
66448
- drawingInfo.startY =
66449
- direction === BeamDirection.Up
66450
- ? this.getFlagTopY(firstBeat, direction)
66451
- : this.getFlagBottomY(firstBeat, direction);
66452
- }
66336
+ drawingInfo.startX = this.getBeatX(firstBeat, BeatXPosition.Stem);
66337
+ drawingInfo.startY =
66338
+ direction === BeamDirection.Up
66339
+ ? this.getFlagTopY(firstBeat, direction)
66340
+ : this.getFlagBottomY(firstBeat, direction);
66453
66341
  drawingInfo.endBeat = lastBeat;
66454
- drawingInfo.endX = h.getBeatLineX(lastBeat);
66455
- if (isRest) {
66456
- drawingInfo.endY =
66457
- direction === BeamDirection.Up
66458
- ? this.getLineY(h.minRestSteps / 2)
66459
- : this.getLineY(h.maxRestSteps / 2);
66460
- }
66461
- else {
66462
- drawingInfo.endY =
66463
- direction === BeamDirection.Up
66464
- ? this.getFlagTopY(lastBeat, direction)
66465
- : this.getFlagBottomY(lastBeat, direction);
66466
- }
66342
+ drawingInfo.endX = this.getBeatX(lastBeat, BeatXPosition.Stem);
66343
+ drawingInfo.endY =
66344
+ direction === BeamDirection.Up
66345
+ ? this.getFlagTopY(lastBeat, direction)
66346
+ : this.getFlagBottomY(lastBeat, direction);
66467
66347
  // 2. ensure max slope
66468
66348
  // we use the min/max notes to place the beam along their real position
66469
66349
  // we only want a maximum of 10 offset for their gradient
@@ -66488,12 +66368,41 @@ class LineBarRenderer extends BarRendererBase {
66488
66368
  drawingInfo.startY - drawingInfo.endY > maxSlope) {
66489
66369
  drawingInfo.startY = drawingInfo.endY + maxSlope;
66490
66370
  }
66491
- // 3. let middle elements shift up/down
66371
+ // 3. adjust beam drawing order
66372
+ // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66373
+ // here we shift accordingly
66374
+ let barDrawingShift = 0;
66375
+ if (barCount > 2 && !isRest) {
66376
+ const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66377
+ const beamThickness = this.smuflMetrics.beamThickness * scale;
66378
+ const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66379
+ if (direction === BeamDirection.Up) {
66380
+ const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66381
+ const barTopY = bottomBarY - totalBarsHeight;
66382
+ const diff = drawingInfo.startY - barTopY;
66383
+ if (diff > 0) {
66384
+ barDrawingShift = diff * -1;
66385
+ drawingInfo.startY -= diff;
66386
+ drawingInfo.endY -= diff;
66387
+ }
66388
+ }
66389
+ else {
66390
+ const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66391
+ const barBottomY = topBarY + totalBarsHeight;
66392
+ const diff = barBottomY - drawingInfo.startY;
66393
+ if (diff > 0) {
66394
+ barDrawingShift = diff;
66395
+ drawingInfo.startY += diff;
66396
+ drawingInfo.endY += diff;
66397
+ }
66398
+ }
66399
+ }
66400
+ // 4. let middle elements shift up/down
66492
66401
  if (h.beats.length > 1) {
66493
66402
  // check if highest note shifts bar up or down
66494
66403
  if (direction === BeamDirection.Up) {
66495
- const yNeededForHighestNote = this.getLineY(this.getMinLineOfBeat(h.beatOfHighestNote)) - stemSize;
66496
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfHighestNote));
66404
+ const yNeededForHighestNote = barDrawingShift + this.getFlagTopY(h.beatOfHighestNote, direction);
66405
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfHighestNote, BeatXPosition.Stem));
66497
66406
  const diff = yGivenByCurrentValues - yNeededForHighestNote;
66498
66407
  if (diff > 0) {
66499
66408
  drawingInfo.startY -= diff;
@@ -66501,8 +66410,8 @@ class LineBarRenderer extends BarRendererBase {
66501
66410
  }
66502
66411
  }
66503
66412
  else {
66504
- const yNeededForLowestNote = this.getLineY(this.getMaxLineOfBeat(h.beatOfLowestNote)) + stemSize;
66505
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfLowestNote));
66413
+ const yNeededForLowestNote = barDrawingShift + this.getFlagBottomY(h.beatOfLowestNote, direction);
66414
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfLowestNote, BeatXPosition.Stem));
66506
66415
  const diff = yNeededForLowestNote - yGivenByCurrentValues;
66507
66416
  if (diff > 0) {
66508
66417
  drawingInfo.startY += diff;
@@ -66510,33 +66419,39 @@ class LineBarRenderer extends BarRendererBase {
66510
66419
  }
66511
66420
  }
66512
66421
  // check if rest shifts bar up or down
66513
- if (h.minRestSteps !== null || h.maxRestSteps !== null) {
66422
+ let barSpacing = 0;
66423
+ if (h.restBeats.length > 0) {
66424
+ // space needed for the bars, rests need to be below them
66514
66425
  const scaleMod = h.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
66515
- let barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66516
- barSpacing += this.smuflMetrics.beamSpacing;
66517
- if (direction === BeamDirection.Up && h.minRestSteps !== null) {
66518
- const yNeededForRest = this.getLineY(h.minRestSteps / 2) - barSpacing;
66519
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMinRestSteps));
66520
- const diff = yGivenByCurrentValues - yNeededForRest;
66521
- if (diff > 0) {
66522
- drawingInfo.startY -= diff;
66523
- drawingInfo.endY -= diff;
66426
+ barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66427
+ }
66428
+ for (const b of h.restBeats) {
66429
+ // rest beats which are "under" the beam
66430
+ if (b.isRest && b.index < h.beats[h.beats.length - 1].index) {
66431
+ if (direction === BeamDirection.Up) {
66432
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxTop() - barSpacing;
66433
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66434
+ const diff = yGivenByCurrentValues - yNeededForRest;
66435
+ if (diff > 0) {
66436
+ drawingInfo.startY -= diff;
66437
+ drawingInfo.endY -= diff;
66438
+ }
66524
66439
  }
66525
- }
66526
- else if (direction === BeamDirection.Down && h.maxRestSteps !== null) {
66527
- const yNeededForRest = this.getLineHeight(h.maxRestSteps / 2) + barSpacing;
66528
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMaxRestSteps));
66529
- const diff = yNeededForRest - yGivenByCurrentValues;
66530
- if (diff > 0) {
66531
- drawingInfo.startY += diff;
66532
- drawingInfo.endY += diff;
66440
+ else if (direction === BeamDirection.Down) {
66441
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxBottom() + barSpacing;
66442
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66443
+ const diff = yNeededForRest - yGivenByCurrentValues;
66444
+ if (diff > 0) {
66445
+ drawingInfo.startY += diff;
66446
+ drawingInfo.endY += diff;
66447
+ }
66533
66448
  }
66534
66449
  }
66535
66450
  }
66536
66451
  // check if slash shifts bar up or down
66537
66452
  if (h.slashBeats.length > 0) {
66538
66453
  for (const b of h.slashBeats) {
66539
- const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(b));
66454
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66540
66455
  const yNeededForSlash = h.direction === BeamDirection.Up
66541
66456
  ? this.getFlagTopY(b, h.direction)
66542
66457
  : this.getFlagBottomY(b, h.direction);
@@ -66548,31 +66463,6 @@ class LineBarRenderer extends BarRendererBase {
66548
66463
  }
66549
66464
  }
66550
66465
  }
66551
- // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66552
- // here we shift accordingly
66553
- if (barCount > 2 && !isRest) {
66554
- const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66555
- const beamThickness = this.smuflMetrics.beamThickness * scale;
66556
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66557
- if (direction === BeamDirection.Up) {
66558
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66559
- const barTopY = bottomBarY - totalBarsHeight;
66560
- const diff = drawingInfo.startY - barTopY;
66561
- if (diff > 0) {
66562
- drawingInfo.startY -= diff;
66563
- drawingInfo.endY -= diff;
66564
- }
66565
- }
66566
- else {
66567
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66568
- const barBottomY = topBarY + totalBarsHeight;
66569
- const diff = barBottomY - drawingInfo.startY;
66570
- if (diff > 0) {
66571
- drawingInfo.startY += diff;
66572
- drawingInfo.endY += diff;
66573
- }
66574
- }
66575
- }
66576
66466
  }
66577
66467
  getMinLineOfBeat(_beat) {
66578
66468
  return 0;
@@ -67093,20 +66983,6 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67093
66983
  }
67094
66984
  return 0;
67095
66985
  }
67096
- updateBeamingHelper() {
67097
- if (this.beamingHelper) {
67098
- let g = null;
67099
- if (this.noteHeads) {
67100
- g = this.noteHeads;
67101
- }
67102
- else if (this.deadSlapped) {
67103
- g = this.deadSlapped;
67104
- }
67105
- if (g) {
67106
- this.beamingHelper.registerBeatLineX('numbered', this.container.beat, this.container.x + this.x + g.x, this.container.x + this.x + g.x + g.width);
67107
- }
67108
- }
67109
- }
67110
66986
  static majorKeySignatureOneValues = [
67111
66987
  // Flats
67112
66988
  59, 66, 61, 68, 63, 58, 65,
@@ -67231,6 +67107,7 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67231
67107
  this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67232
67108
  }
67233
67109
  this.middleX = this.onTimeX;
67110
+ this.stemX = this.middleX;
67234
67111
  }
67235
67112
  }
67236
67113
 
@@ -67643,7 +67520,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67643
67520
  const barSize = this.smuflMetrics.numberedBarRendererBarSize;
67644
67521
  const barCount = ModelUtils.getIndex(beat.duration) - 2;
67645
67522
  const barStart = cy + this.y;
67646
- const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes) - this.beatGlyphsStart;
67523
+ const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes);
67647
67524
  const beamY = this.calculateBeamY(h, beatLineX);
67648
67525
  for (let barIndex = 0; barIndex < barCount; barIndex++) {
67649
67526
  let barStartX = 0;
@@ -67652,11 +67529,11 @@ class NumberedBarRenderer extends LineBarRenderer {
67652
67529
  const barY = barStart + barIndex * barSpacing;
67653
67530
  if (i === h.beats.length - 1) {
67654
67531
  barStartX = beatLineX;
67655
- barEndX = this.getBeatX(beat, BeatXPosition.PostNotes) - this.beatGlyphsStart;
67532
+ barEndX = this.getBeatX(beat, BeatXPosition.PostNotes);
67656
67533
  }
67657
67534
  else {
67658
67535
  barStartX = beatLineX;
67659
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes) - this.beatGlyphsStart;
67536
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes);
67660
67537
  }
67661
67538
  barStartY = barY + beamY;
67662
67539
  canvas.fillRect(cx + this.x + barStartX, barStartY, barEndX - barStartX, barSize);
@@ -67674,7 +67551,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67674
67551
  dotsY = barStart + beamY + barCount * (barSpacing + barSize);
67675
67552
  dotsOffset = dotSpacing;
67676
67553
  }
67677
- const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes) - this.beatGlyphsStart;
67554
+ const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes);
67678
67555
  dotCount = Math.abs(dotCount);
67679
67556
  for (let d = 0; d < dotCount; d++) {
67680
67557
  CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + dotX, dotsY, 1, MusicFontSymbol.AugmentationDot, true);
@@ -67723,7 +67600,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67723
67600
  return this.getLineY(0) - noteHeadHeight / 2;
67724
67601
  }
67725
67602
  createPreBeatGlyphs() {
67726
- this.wasFirstOfLine = this.isFirstOfLine;
67603
+ this.wasFirstOfStaff = this.isFirstOfStaff;
67727
67604
  if (this.index === 0 || (this.bar.masterBar.isRepeatStart && this._isOnlyNumbered)) {
67728
67605
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
67729
67606
  }
@@ -67779,6 +67656,11 @@ class NumberedBarRenderer extends LineBarRenderer {
67779
67656
  }
67780
67657
  paintBeamingStem(_beat, _cy, _x, _topY, _bottomY, _canvas) {
67781
67658
  }
67659
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
67660
+ if (h.voice?.index === 0) {
67661
+ super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
67662
+ }
67663
+ }
67782
67664
  }
67783
67665
 
67784
67666
  /**
@@ -68315,9 +68197,8 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68315
68197
  aboveBeatEffects = new Map();
68316
68198
  belowBeatEffects = new Map();
68317
68199
  beat;
68318
- beamingHelper;
68319
68200
  get direction() {
68320
- return this.beamingHelper.direction;
68201
+ return this.renderer.getBeatDirection(this.beat);
68321
68202
  }
68322
68203
  get scale() {
68323
68204
  return this.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
@@ -68359,7 +68240,7 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68359
68240
  : 0) * scale;
68360
68241
  // stem size according to duration
68361
68242
  pos -= this.renderer.smuflMetrics.standardStemLength * scale;
68362
- const topCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68243
+ const topCenterY = this.renderer.centerStaffStemY(this.direction);
68363
68244
  return Math.min(topCenterY, pos);
68364
68245
  case NoteYPosition.Top:
68365
68246
  pos -= n.height / 2;
@@ -68376,7 +68257,7 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68376
68257
  : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
68377
68258
  // stem size according to duration
68378
68259
  pos += this.renderer.smuflMetrics.standardStemLength * scale;
68379
- const bottomCenterY = this.renderer.centerStaffStemY(this.beamingHelper);
68260
+ const bottomCenterY = this.renderer.centerStaffStemY(this.direction);
68380
68261
  return Math.max(bottomCenterY, pos);
68381
68262
  case NoteYPosition.StemUp:
68382
68263
  pos -=
@@ -68401,11 +68282,6 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68401
68282
  addEffectNoteGlyph(noteGlyph, noteLine) {
68402
68283
  super.add(noteGlyph, noteLine);
68403
68284
  }
68404
- updateBeamingHelper(cx) {
68405
- if (this.beamingHelper) {
68406
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.upLineX, cx + this.x + this.downLineX);
68407
- }
68408
- }
68409
68285
  doLayout() {
68410
68286
  super.doLayout();
68411
68287
  const scoreRenderer = this.renderer;
@@ -68533,7 +68409,6 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
68533
68409
  * @internal
68534
68410
  */
68535
68411
  class ScoreRestGlyph extends MusicFontGlyph {
68536
- beamingHelper;
68537
68412
  constructor(x, y, duration) {
68538
68413
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
68539
68414
  }
@@ -68565,11 +68440,6 @@ class ScoreRestGlyph extends MusicFontGlyph {
68565
68440
  return MusicFontSymbol.None;
68566
68441
  }
68567
68442
  }
68568
- updateBeamingHelper(cx) {
68569
- if (this.beamingHelper) {
68570
- this.beamingHelper.registerBeatLineX('score', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
68571
- }
68572
- }
68573
68443
  paint(cx, cy, canvas) {
68574
68444
  this.internalPaint(cx, cy, canvas, BeatSubElement.StandardNotationRests);
68575
68445
  }
@@ -69061,10 +68931,11 @@ class StringNumberContainerGlyph extends EffectGlyph {
69061
68931
  */
69062
68932
  class SlashNoteHeadGlyph extends MusicFontGlyph {
69063
68933
  beatEffects = new Map();
69064
- beamingHelper;
69065
68934
  noteHeadElement = NoteSubElement.SlashNoteHead;
69066
68935
  effectElement = BeatSubElement.SlashEffects;
69067
68936
  _symbol;
68937
+ upLineX = 0;
68938
+ downLineX = 0;
69068
68939
  constructor(x, y, duration, isGrace, beat) {
69069
68940
  super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, SlashNoteHeadGlyph.getSymbol(duration));
69070
68941
  this._symbol = SlashNoteHeadGlyph.getSymbol(duration);
@@ -69115,6 +68986,15 @@ class SlashNoteHeadGlyph extends MusicFontGlyph {
69115
68986
  if (!Number.isNaN(minEffectY)) {
69116
68987
  this.renderer.registerBeatEffectOverflows(minEffectY, maxEffectY);
69117
68988
  }
68989
+ const symbol = this._symbol;
68990
+ const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
68991
+ ? this.renderer.smuflMetrics.stemUp.get(symbol).x
68992
+ : 0;
68993
+ this.upLineX = stemInfoUp;
68994
+ const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
68995
+ ? this.renderer.smuflMetrics.stemDown.get(symbol).x
68996
+ : 0;
68997
+ this.downLineX = stemInfoDown;
69118
68998
  }
69119
68999
  static getSymbol(duration) {
69120
69000
  switch (duration) {
@@ -69128,25 +69008,13 @@ class SlashNoteHeadGlyph extends MusicFontGlyph {
69128
69008
  return MusicFontSymbol.NoteheadSlashHorizontalEnds;
69129
69009
  }
69130
69010
  }
69131
- updateBeamingHelper(cx) {
69132
- if (this.beamingHelper) {
69133
- const symbol = this._symbol;
69134
- const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
69135
- ? this.renderer.smuflMetrics.stemUp.get(symbol).x
69136
- : 0;
69137
- const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69138
- ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69139
- : 0;
69140
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + stemInfoUp, cx + this.x + stemInfoDown);
69141
- }
69142
- }
69143
69011
  }
69144
69012
 
69145
69013
  /**
69146
69014
  * @internal
69147
69015
  */
69148
69016
  class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69149
- _collisionOffset = -1e3;
69017
+ _collisionOffset = Number.NaN;
69150
69018
  _skipPaint = false;
69151
69019
  _whammy;
69152
69020
  noteHeads = null;
@@ -69197,22 +69065,19 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69197
69065
  getNoteY(note, requestedPosition) {
69198
69066
  return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
69199
69067
  }
69200
- updateBeamingHelper() {
69201
- if (this.noteHeads) {
69202
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
69068
+ applyRestCollisionOffset() {
69069
+ if (!this.restGlyph) {
69070
+ return;
69203
69071
  }
69204
- else if (this.restGlyph) {
69205
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
69206
- if (this.renderer.bar.isMultiVoice && this._collisionOffset === -1e3) {
69207
- this._collisionOffset = this.renderer.helpers.collisionHelper.applyRestCollisionOffset(this.container.beat, this.restGlyph.y, this.renderer.getScoreHeight(1));
69208
- this.y += this._collisionOffset;
69209
- const existingRests = this.renderer.helpers.collisionHelper.restDurationsByDisplayTime;
69210
- if (existingRests.has(this.container.beat.playbackStart) &&
69211
- existingRests.get(this.container.beat.playbackStart).has(this.container.beat.playbackDuration) &&
69212
- existingRests.get(this.container.beat.playbackStart).get(this.container.beat.playbackDuration) !==
69213
- this.container.beat.id) {
69214
- this._skipPaint = true;
69215
- }
69072
+ if (this.renderer.bar.isMultiVoice && Number.isNaN(this._collisionOffset)) {
69073
+ this._collisionOffset = this.renderer.collisionHelper.applyRestCollisionOffset(this.container.beat, this.restGlyph.y, this.renderer.getScoreHeight(1));
69074
+ this.y += this._collisionOffset;
69075
+ const existingRests = this.renderer.collisionHelper.restDurationsByDisplayTime;
69076
+ if (existingRests.has(this.container.beat.playbackStart) &&
69077
+ existingRests.get(this.container.beat.playbackStart).has(this.container.beat.playbackDuration) &&
69078
+ existingRests.get(this.container.beat.playbackStart).get(this.container.beat.playbackDuration) !==
69079
+ this.container.beat.id) {
69080
+ this._skipPaint = true;
69216
69081
  }
69217
69082
  }
69218
69083
  }
@@ -69232,7 +69097,6 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69232
69097
  const noteHeads = new ScoreNoteChordGlyph();
69233
69098
  this.noteHeads = noteHeads;
69234
69099
  noteHeads.beat = this.container.beat;
69235
- noteHeads.beamingHelper = this.beamingHelper;
69236
69100
  const ghost = new GhostNoteContainerGlyph(false);
69237
69101
  ghost.renderer = this.renderer;
69238
69102
  for (const note of this.container.beat.notes) {
@@ -69282,22 +69146,18 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69282
69146
  const restGlyph = new ScoreRestGlyph(0, sr.getScoreY(steps), this.container.beat.duration);
69283
69147
  this.restGlyph = restGlyph;
69284
69148
  restGlyph.beat = this.container.beat;
69285
- restGlyph.beamingHelper = this.beamingHelper;
69286
69149
  this.addNormal(restGlyph);
69287
69150
  if (this.renderer.bar.isMultiVoice) {
69288
69151
  if (this.container.beat.voice.index === 0) {
69289
69152
  const restSizes = BeamingHelper.computeLineHeightsForRest(this.container.beat.duration);
69290
69153
  const restTop = restGlyph.y - sr.getScoreHeight(restSizes[0]);
69291
69154
  const restBottom = restGlyph.y + sr.getScoreHeight(restSizes[1]);
69292
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69155
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, restTop, restBottom);
69293
69156
  }
69294
69157
  else {
69295
- this.renderer.helpers.collisionHelper.registerRest(this.container.beat);
69158
+ this.renderer.collisionHelper.registerRest(this.container.beat);
69296
69159
  }
69297
69160
  }
69298
- if (this.beamingHelper) {
69299
- this.beamingHelper.applyRest(this.container.beat, steps);
69300
- }
69301
69161
  //
69302
69162
  // Note dots
69303
69163
  //
@@ -69312,17 +69172,24 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69312
69172
  }
69313
69173
  }
69314
69174
  super.doLayout();
69175
+ this.applyRestCollisionOffset();
69315
69176
  if (this.container.beat.isEmpty) {
69316
69177
  this.onTimeX = this.width / 2;
69317
69178
  this.middleX = this.onTimeX;
69179
+ this.stemX = this.middleX;
69318
69180
  }
69319
69181
  else if (this.restGlyph) {
69320
69182
  this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
69321
69183
  this.middleX = this.onTimeX;
69184
+ this.stemX = this.middleX;
69322
69185
  }
69323
69186
  else if (this.noteHeads) {
69324
69187
  this.onTimeX = this.noteHeads.x + this.noteHeads.onTimeX;
69325
69188
  this.middleX = this.noteHeads.x + this.noteHeads.width / 2;
69189
+ const direction = this.renderer.getBeatDirection(this.container.beat);
69190
+ this.stemX =
69191
+ this.noteHeads.x +
69192
+ (direction === BeamDirection.Up ? this.noteHeads.upLineX : this.noteHeads.downLineX);
69326
69193
  }
69327
69194
  }
69328
69195
  _createBeatDot(line, group) {
@@ -69394,7 +69261,7 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
69394
69261
  }
69395
69262
  const belowBeatEffects = this.noteHeads.belowBeatEffects;
69396
69263
  const aboveBeatEffects = this.noteHeads.aboveBeatEffects;
69397
- const outsideBeatEffects = this.beamingHelper.direction === BeamDirection.Up
69264
+ const outsideBeatEffects = this.renderer.getBeatDirection(this.container.beat) === BeamDirection.Up
69398
69265
  ? this.noteHeads.belowBeatEffects
69399
69266
  : this.noteHeads.aboveBeatEffects;
69400
69267
  if (n.isStaccato && !belowBeatEffects.has('Staccato')) {
@@ -69830,7 +69697,7 @@ class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
69830
69697
  // if we have a line break we draw only a line until the end
69831
69698
  if (!endNoteRenderer || endNoteRenderer.staff !== startNoteRenderer.staff) {
69832
69699
  const endX = cx + startNoteRenderer.x + startNoteRenderer.width;
69833
- const noteValueToDraw = note.tieDestination.realValue;
69700
+ const noteValueToDraw = note.tieDestination.displayValue;
69834
69701
  startNoteRenderer.accidentalHelper.applyAccidentalForValue(note.beat, noteValueToDraw, false, true);
69835
69702
  const endY = cy +
69836
69703
  startNoteRenderer.y +
@@ -70366,6 +70233,22 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70366
70233
  doLayout() {
70367
70234
  this._effectSlur = null;
70368
70235
  this._effectEndSlur = null;
70236
+ // make space for flag
70237
+ const sr = this.renderer;
70238
+ const beat = this.beat;
70239
+ const isGrace = beat.graceType !== GraceType.None;
70240
+ if (sr.hasFlag(beat)) {
70241
+ const direction = this.renderer.getBeatDirection(beat);
70242
+ const scale = isGrace ? NoteHeadGlyph.GraceScale : 1;
70243
+ const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
70244
+ const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
70245
+ this._flagStretch = flagWidth;
70246
+ }
70247
+ else if (isGrace) {
70248
+ // always use flag size as spacing on grace notes
70249
+ const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
70250
+ this._flagStretch = graceSpacing;
70251
+ }
70369
70252
  super.doLayout();
70370
70253
  if (this._bend) {
70371
70254
  this._bend.renderer = this.renderer;
@@ -70375,7 +70258,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70375
70258
  }
70376
70259
  getBoundingBoxTop() {
70377
70260
  if (this._bend !== null) {
70378
- return Math.min(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70261
+ return ModelUtils.minBoundingBox(this._bend.getBoundingBoxTop(), super.getBoundingBoxTop());
70379
70262
  }
70380
70263
  else {
70381
70264
  return super.getBoundingBoxTop();
@@ -70383,7 +70266,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70383
70266
  }
70384
70267
  getBoundingBoxBottom() {
70385
70268
  if (this._bend !== null) {
70386
- return Math.max(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70269
+ return ModelUtils.maxBoundingBox(this._bend.getBoundingBoxBottom(), super.getBoundingBoxTop());
70387
70270
  }
70388
70271
  else {
70389
70272
  return super.getBoundingBoxBottom();
@@ -70431,7 +70314,7 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70431
70314
  }
70432
70315
  // end effect slur on last beat
70433
70316
  if (!this._effectEndSlur && n.beat.isEffectSlurDestination && n.beat.effectSlurOrigin) {
70434
- const direction = this.onNotes.beamingHelper.direction;
70317
+ const direction = this.renderer.getBeatDirection(n.beat);
70435
70318
  const startNote = direction === BeamDirection.Up ? n.beat.effectSlurOrigin.minNote : n.beat.effectSlurOrigin.maxNote;
70436
70319
  const endNote = direction === BeamDirection.Up ? n.beat.minNote : n.beat.maxNote;
70437
70320
  const effectEndSlur = new ScoreSlurGlyph(`score.slur.effect.${startNote.beat.id}`, startNote, endNote, true);
@@ -70470,6 +70353,15 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
70470
70353
  }
70471
70354
  }
70472
70355
  }
70356
+ _flagStretch = 0;
70357
+ get postBeatStretch() {
70358
+ return super.postBeatStretch + this._flagStretch;
70359
+ }
70360
+ updateWidth() {
70361
+ super.updateWidth();
70362
+ this.width += this._flagStretch;
70363
+ this.minWidth += this._flagStretch;
70364
+ }
70473
70365
  }
70474
70366
 
70475
70367
  /**
@@ -70504,7 +70396,7 @@ class ScoreBarRenderer extends LineBarRenderer {
70504
70396
  return this.smuflMetrics.oneStaffSpace;
70505
70397
  }
70506
70398
  get heightLineCount() {
70507
- return 5;
70399
+ return Math.max(5, this.bar.staff.standardNotationLineCount);
70508
70400
  }
70509
70401
  get drawnLineCount() {
70510
70402
  return this.bar.staff.standardNotationLineCount;
@@ -70561,7 +70453,9 @@ class ScoreBarRenderer extends LineBarRenderer {
70561
70453
  slashY -= this.smuflMetrics.stemUp.has(symbol)
70562
70454
  ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
70563
70455
  : 0;
70564
- slashY -= this.smuflMetrics.standardStemLength + scale;
70456
+ if (!beat.isRest) {
70457
+ slashY -= this.smuflMetrics.standardStemLength + scale;
70458
+ }
70565
70459
  }
70566
70460
  return slashY;
70567
70461
  }
@@ -70570,7 +70464,7 @@ class ScoreBarRenderer extends LineBarRenderer {
70570
70464
  return this.getBeatContainer(beat).onNotes.getNoteY(minNote, direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown);
70571
70465
  }
70572
70466
  let y = this.getScoreY(this.accidentalHelper.getMinSteps(beat));
70573
- if (direction === BeamDirection.Up) {
70467
+ if (direction === BeamDirection.Up && !beat.isRest) {
70574
70468
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
70575
70469
  y -= this.smuflMetrics.standardStemLength * scale;
70576
70470
  }
@@ -70608,14 +70502,14 @@ class ScoreBarRenderer extends LineBarRenderer {
70608
70502
  getBeamDirection(helper) {
70609
70503
  return helper.direction;
70610
70504
  }
70611
- centerStaffStemY(helper) {
70505
+ centerStaffStemY(direction) {
70612
70506
  const isStandardFive = this.bar.staff.standardNotationLineCount === Staff.DefaultStandardNotationLineCount;
70613
70507
  if (isStandardFive) {
70614
70508
  // center on the middle line for a standard 5-line staff
70615
70509
  return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
70616
70510
  }
70617
70511
  // for other staff line counts, we align the stem either on the top or bottom line
70618
- if (helper.direction === BeamDirection.Up) {
70512
+ if (direction === BeamDirection.Up) {
70619
70513
  return this.getScoreY(this.bar.staff.standardNotationLineCount * 2);
70620
70514
  }
70621
70515
  return this.getScoreY(0);
@@ -70717,7 +70611,7 @@ class ScoreBarRenderer extends LineBarRenderer {
70717
70611
  createLinePreBeatGlyphs() {
70718
70612
  // Clef
70719
70613
  let hasClef = false;
70720
- if (this.isFirstOfLine ||
70614
+ if (this.isFirstOfStaff ||
70721
70615
  this.bar.clef !== this.bar.previousBar.clef ||
70722
70616
  this.bar.clefOttava !== this.bar.previousBar.clefOttava) {
70723
70617
  // SMUFL: Clefs should be positioned such that the pitch the clef refers to is on the baseline
@@ -70922,6 +70816,25 @@ class SlashTieGlyph extends NoteTieGlyph {
70922
70816
  */
70923
70817
  class SlashBeatContainerGlyph extends BeatContainerGlyph {
70924
70818
  _tiedNoteTie = null;
70819
+ doLayout() {
70820
+ // make space for flag
70821
+ const sr = this.renderer;
70822
+ const beat = this.beat;
70823
+ const isGrace = beat.graceType !== GraceType.None;
70824
+ if (sr.hasFlag(beat)) {
70825
+ const direction = this.renderer.getBeatDirection(beat);
70826
+ const scale = isGrace ? NoteHeadGlyph.GraceScale : 1;
70827
+ const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
70828
+ const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
70829
+ this._flagStretch = flagWidth;
70830
+ }
70831
+ else if (isGrace) {
70832
+ // always use flag size as spacing on grace notes
70833
+ const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * NoteHeadGlyph.GraceScale;
70834
+ this._flagStretch = graceSpacing;
70835
+ }
70836
+ super.doLayout();
70837
+ }
70925
70838
  createTies(n) {
70926
70839
  // create a tie if any effect requires it
70927
70840
  if (!n.isVisible) {
@@ -70938,17 +70851,21 @@ class SlashBeatContainerGlyph extends BeatContainerGlyph {
70938
70851
  this.addTie(tie);
70939
70852
  }
70940
70853
  }
70854
+ _flagStretch = 0;
70855
+ get postBeatStretch() {
70856
+ return super.postBeatStretch + this._flagStretch;
70857
+ }
70858
+ updateWidth() {
70859
+ super.updateWidth();
70860
+ this.width += this._flagStretch;
70861
+ this.minWidth += this._flagStretch;
70862
+ }
70941
70863
  }
70942
70864
 
70943
70865
  /**
70944
70866
  * @internal
70945
70867
  */
70946
70868
  class SlashRestGlyph extends ScoreRestGlyph {
70947
- updateBeamingHelper(cx) {
70948
- if (this.beamingHelper) {
70949
- this.beamingHelper.registerBeatLineX('slash', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
70950
- }
70951
- }
70952
70869
  paint(cx, cy, canvas) {
70953
70870
  super.internalPaint(cx, cy, canvas, BeatSubElement.SlashRests);
70954
70871
  }
@@ -71044,19 +70961,6 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
71044
70961
  }
71045
70962
  return 0;
71046
70963
  }
71047
- updateBeamingHelper() {
71048
- if (this.noteHeads) {
71049
- this.noteHeads.updateBeamingHelper(this.container.x + this.x);
71050
- }
71051
- else if (this.deadSlapped) {
71052
- if (this.beamingHelper) {
71053
- 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);
71054
- }
71055
- }
71056
- else if (this.restGlyph) {
71057
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
71058
- }
71059
- }
71060
70964
  doLayout() {
71061
70965
  // create glyphs
71062
70966
  const sr = this.renderer;
@@ -71074,18 +70978,13 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
71074
70978
  const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat.duration, isGrace, this.container.beat);
71075
70979
  this.noteHeads = noteHeadGlyph;
71076
70980
  noteHeadGlyph.beat = this.container.beat;
71077
- noteHeadGlyph.beamingHelper = this.beamingHelper;
71078
70981
  this.addNormal(noteHeadGlyph);
71079
70982
  }
71080
70983
  else {
71081
70984
  const restGlyph = new SlashRestGlyph(0, glyphY, this.container.beat.duration);
71082
70985
  this.restGlyph = restGlyph;
71083
70986
  restGlyph.beat = this.container.beat;
71084
- restGlyph.beamingHelper = this.beamingHelper;
71085
70987
  this.addNormal(restGlyph);
71086
- if (this.beamingHelper) {
71087
- this.beamingHelper.applyRest(this.container.beat, 0);
71088
- }
71089
70988
  }
71090
70989
  }
71091
70990
  //
@@ -71099,15 +70998,22 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
71099
70998
  super.doLayout();
71100
70999
  if (this.container.beat.isEmpty) {
71101
71000
  this.onTimeX = this.width / 2;
71001
+ this.stemX = this.onTimeX;
71102
71002
  }
71103
71003
  else if (this.restGlyph) {
71104
71004
  this.onTimeX = this.restGlyph.x + this.restGlyph.width / 2;
71005
+ this.stemX = this.onTimeX;
71105
71006
  }
71106
71007
  else if (this.noteHeads) {
71107
71008
  this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
71009
+ const direction = this.renderer.getBeatDirection(this.container.beat);
71010
+ this.stemX =
71011
+ this.noteHeads.x +
71012
+ (direction === BeamDirection.Up ? this.noteHeads.upLineX : this.noteHeads.downLineX);
71108
71013
  }
71109
71014
  else if (this.deadSlapped) {
71110
71015
  this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
71016
+ this.stemX = this.onTimeX;
71111
71017
  }
71112
71018
  this.middleX = this.onTimeX;
71113
71019
  }
@@ -71184,7 +71090,9 @@ class SlashBarRenderer extends LineBarRenderer {
71184
71090
  const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
71185
71091
  const scale = beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1;
71186
71092
  slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
71187
- slashY -= this.smuflMetrics.standardStemLength + scale;
71093
+ if (!beat.isRest) {
71094
+ slashY -= this.smuflMetrics.standardStemLength + scale;
71095
+ }
71188
71096
  return slashY;
71189
71097
  }
71190
71098
  getFlagBottomY(beat, _direction) {
@@ -71268,6 +71176,11 @@ class SlashBarRenderer extends LineBarRenderer {
71268
71176
  _?.[Symbol.dispose]?.();
71269
71177
  }
71270
71178
  }
71179
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
71180
+ if (h.voice?.index === 0) {
71181
+ super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
71182
+ }
71183
+ }
71271
71184
  }
71272
71185
 
71273
71186
  /**
@@ -71724,7 +71637,6 @@ class TabNoteChordGlyph extends Glyph {
71724
71637
  _deadSlapped = null;
71725
71638
  _isGrace;
71726
71639
  beat;
71727
- beamingHelper;
71728
71640
  maxStringNote = null;
71729
71641
  minStringNote = null;
71730
71642
  beatEffects = new Map();
@@ -71873,11 +71785,6 @@ class TabNoteChordGlyph extends Glyph {
71873
71785
  }
71874
71786
  }
71875
71787
  }
71876
- updateBeamingHelper(cx) {
71877
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71878
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71879
- }
71880
- }
71881
71788
  }
71882
71789
 
71883
71790
  /**
@@ -71885,7 +71792,6 @@ class TabNoteChordGlyph extends Glyph {
71885
71792
  */
71886
71793
  class TabRestGlyph extends MusicFontGlyph {
71887
71794
  _isVisibleRest;
71888
- beamingHelper;
71889
71795
  constructor(x, y, isVisibleRest, duration) {
71890
71796
  super(x, y, 1, ScoreRestGlyph.getSymbol(duration));
71891
71797
  this._isVisibleRest = isVisibleRest;
@@ -71893,11 +71799,6 @@ class TabRestGlyph extends MusicFontGlyph {
71893
71799
  doLayout() {
71894
71800
  super.doLayout();
71895
71801
  }
71896
- updateBeamingHelper(cx) {
71897
- if (this.beamingHelper && this.beamingHelper.isPositionFrom('tab', this.beat)) {
71898
- this.beamingHelper.registerBeatLineX('tab', this.beat, cx + this.x + this.width / 2, cx + this.x + this.width / 2);
71899
- }
71900
- }
71901
71802
  paint(cx, cy, canvas) {
71902
71803
  if (this._isVisibleRest) {
71903
71804
  const _ = ElementStyleHelper.beat(canvas, BeatSubElement.GuitarTabRests, this.beat);
@@ -71954,7 +71855,6 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71954
71855
  slashNoteHead.effectElement = BeatSubElement.GuitarTabEffects;
71955
71856
  this.slash = slashNoteHead;
71956
71857
  slashNoteHead.beat = this.container.beat;
71957
- slashNoteHead.beamingHelper = this.beamingHelper;
71958
71858
  this.addNormal(slashNoteHead);
71959
71859
  beatEffects = slashNoteHead.beatEffects;
71960
71860
  }
@@ -71962,7 +71862,6 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71962
71862
  const tabNoteNumbers = new TabNoteChordGlyph(0, 0, isGrace);
71963
71863
  this.noteNumbers = tabNoteNumbers;
71964
71864
  tabNoteNumbers.beat = this.container.beat;
71965
- tabNoteNumbers.beamingHelper = this.beamingHelper;
71966
71865
  for (const note of this.container.beat.notes) {
71967
71866
  if (note.isVisible) {
71968
71867
  this._createNoteGlyph(note);
@@ -71984,9 +71883,9 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71984
71883
  // Note dots
71985
71884
  //
71986
71885
  if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== TabRhythmMode.Hidden) {
71886
+ const y = tabRenderer.getFlagAndBarPos();
71987
71887
  for (let i = 0; i < this.container.beat.dots; i++) {
71988
- this.addEffect(new AugmentationDotGlyph(0, tabRenderer.lineOffset * tabRenderer.bar.staff.tuning.length +
71989
- tabRenderer.settings.notation.rhythmHeight));
71888
+ this.addEffect(new AugmentationDotGlyph(0, y));
71990
71889
  }
71991
71890
  }
71992
71891
  }
@@ -71996,7 +71895,6 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
71996
71895
  const restGlyph = new TabRestGlyph(0, y, tabRenderer.showRests, this.container.beat.duration);
71997
71896
  this.restGlyph = restGlyph;
71998
71897
  restGlyph.beat = this.container.beat;
71999
- restGlyph.beamingHelper = this.beamingHelper;
72000
71898
  this.addNormal(restGlyph);
72001
71899
  //
72002
71900
  // Note dots
@@ -72034,21 +71932,11 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
72034
71932
  this.onTimeX = this.slash.x + this.slash.width / 2;
72035
71933
  }
72036
71934
  this.middleX = this.onTimeX;
71935
+ this.stemX = this.middleX;
72037
71936
  for (const g of centeredEffectGlyphs) {
72038
71937
  g.x = this.onTimeX;
72039
71938
  }
72040
71939
  }
72041
- updateBeamingHelper() {
72042
- if (this.noteNumbers) {
72043
- this.noteNumbers.updateBeamingHelper(this.container.x + this.x);
72044
- }
72045
- else if (this.restGlyph) {
72046
- this.restGlyph.updateBeamingHelper(this.container.x + this.x);
72047
- }
72048
- else if (this.slash) {
72049
- this.slash.updateBeamingHelper(this.container.x + this.x);
72050
- }
72051
- }
72052
71940
  _createNoteGlyph(n) {
72053
71941
  const tr = this.renderer;
72054
71942
  const noteNumberGlyph = new NoteNumberGlyph(0, 0, n);
@@ -72059,7 +71947,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
72059
71947
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
72060
71948
  const topY = noteNumberGlyph.y - noteNumberGlyph.height / 2;
72061
71949
  const bottomY = topY + noteNumberGlyph.height;
72062
- this.renderer.helpers.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
71950
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
72063
71951
  const minString = tr.minString;
72064
71952
  const maxString = tr.maxString;
72065
71953
  if (Number.isNaN(minString) || minString < n.string) {
@@ -72331,7 +72219,7 @@ class TabBarRenderer extends LineBarRenderer {
72331
72219
  }
72332
72220
  createLinePreBeatGlyphs() {
72333
72221
  // Clef
72334
- if (this.isFirstOfLine) {
72222
+ if (this.isFirstOfStaff) {
72335
72223
  const center = (this.bar.staff.tuning.length - 1) / 2;
72336
72224
  this.createStartSpacing();
72337
72225
  this.addPreBeatGlyph(new TabClefGlyph(0, this.getLineY(center)));
@@ -72422,14 +72310,14 @@ class TabBarRenderer extends LineBarRenderer {
72422
72310
  // currently only used for duplets
72423
72311
  return this.getFlagAndBarPos();
72424
72312
  }
72425
- shouldPaintFlag(beat, h) {
72426
- if (!super.shouldPaintFlag(beat, h)) {
72313
+ shouldPaintFlag(beat) {
72314
+ if (!super.shouldPaintFlag(beat)) {
72427
72315
  return false;
72428
72316
  }
72429
72317
  if (beat.graceType !== GraceType.None) {
72430
72318
  return false;
72431
72319
  }
72432
- return this.drawBeamHelperAsFlags(h);
72320
+ return true;
72433
72321
  }
72434
72322
  paintBeamingStem(beat, cy, x, topY, bottomY, canvas) {
72435
72323
  if (bottomY < topY) {