@coderline/alphatab 1.8.0-alpha.1646 → 1.8.0-alpha.1647

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.1646 (develop, build 1646)
2
+ * alphaTab v1.8.0-alpha.1647 (develop, build 1647)
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.1646';
207
- static date = '2025-12-16T02:20:58.271Z';
208
- static commit = 'b7dacb7f9d4f3f9e6d83e9fc2f52c4d63f378a49';
206
+ static version = '1.8.0-alpha.1647';
207
+ static date = '2025-12-17T02:10:13.730Z';
208
+ static commit = 'a1bd11d4a3eef22409f1f49a5dd172ace16108cc';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -1311,6 +1311,10 @@ var NotationElement;
1311
1311
  * The whammy bar line effect shown above the tab staff
1312
1312
  */
1313
1313
  NotationElement[NotationElement["EffectWhammyBarLine"] = 50] = "EffectWhammyBarLine";
1314
+ /**
1315
+ * The key signature for numbered notation staff.
1316
+ */
1317
+ NotationElement[NotationElement["EffectNumberedNotationKeySignature"] = 51] = "EffectNumberedNotationKeySignature";
1314
1318
  })(NotationElement || (NotationElement = {}));
1315
1319
  /**
1316
1320
  * The notation settings control how various music notation elements are shown and behaving
@@ -39715,7 +39719,7 @@ class EngravingSettings {
39715
39719
  //
39716
39720
  // custom alphatab sizes
39717
39721
  this.numberedBarRendererBarSize = this.staffLineThickness * 2;
39718
- this.numberedBarRendererBarSpacing = this.beamSpacing + this.numberedBarRendererBarSize;
39722
+ this.numberedBarRendererBarSpacing = this.beamSpacing;
39719
39723
  this.preNoteEffectPadding = 0.4 * this.oneStaffSpace;
39720
39724
  this.postNoteEffectPadding = 0.2 * this.oneStaffSpace;
39721
39725
  this.lineRangedGlyphDashGap = 0.5 * this.oneStaffSpace;
@@ -39784,7 +39788,7 @@ class EngravingSettings {
39784
39788
  */
39785
39789
  numberedBarRendererBarSpacing = 0;
39786
39790
  /**
39787
- * The size of the dashed drawn in numbered notation to indicate the durations.
39791
+ * The padding minimum between the duration dashes.
39788
39792
  */
39789
39793
  numberedDashGlyphPadding = 0;
39790
39794
  /**
@@ -48565,10 +48569,13 @@ class BeatContainerGlyphBase extends Glyph {
48565
48569
  */
48566
48570
  class BeatContainerGlyph extends BeatContainerGlyphBase {
48567
48571
  _ties = [];
48572
+ _tieWidth = 0;
48568
48573
  beat;
48569
48574
  preNotes;
48570
48575
  onNotes;
48571
- minWidth = 0;
48576
+ get beatId() {
48577
+ return this.beat.id;
48578
+ }
48572
48579
  get isLastOfVoice() {
48573
48580
  return this.beat.isLastOfVoice;
48574
48581
  }
@@ -48629,7 +48636,7 @@ class BeatContainerGlyph extends BeatContainerGlyphBase {
48629
48636
  return helper.hasFlag(false, undefined);
48630
48637
  }
48631
48638
  get postBeatStretch() {
48632
- return this.onNotes.computedWidth - this.onNotes.onTimeX;
48639
+ return (this.onNotes.computedWidth + this._tieWidth) - this.onNotes.onTimeX;
48633
48640
  }
48634
48641
  registerLayoutingInfo(layoutings) {
48635
48642
  const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
@@ -48659,16 +48666,19 @@ class BeatContainerGlyph extends BeatContainerGlyphBase {
48659
48666
  this.onNotes.renderer = this.renderer;
48660
48667
  this.onNotes.container = this;
48661
48668
  this.onNotes.doLayout();
48669
+ this.createBeatTies();
48670
+ this.updateWidth();
48671
+ }
48672
+ createBeatTies() {
48662
48673
  let i = this.beat.notes.length - 1;
48663
48674
  while (i >= 0) {
48664
48675
  this.createTies(this.beat.notes[i--]);
48665
48676
  }
48666
- this.updateWidth();
48667
48677
  }
48668
48678
  doMultiVoiceLayout() {
48669
48679
  }
48670
48680
  updateWidth() {
48671
- this.minWidth = this.preNotes.width + this.onNotes.width;
48681
+ let width = this.preNotes.width + this.onNotes.width;
48672
48682
  let tieWidth = 0;
48673
48683
  for (const tie of this._ties) {
48674
48684
  const tg = tie;
@@ -48676,8 +48686,9 @@ class BeatContainerGlyph extends BeatContainerGlyphBase {
48676
48686
  tieWidth = tg.width;
48677
48687
  }
48678
48688
  }
48679
- this.minWidth += tieWidth;
48680
- this.width = this.minWidth;
48689
+ this._tieWidth = tieWidth;
48690
+ width += tieWidth;
48691
+ this.width = width;
48681
48692
  }
48682
48693
  createTies(_n) {
48683
48694
  }
@@ -58583,6 +58594,244 @@ class NoteOrnamentEffectInfo extends EffectInfo {
58583
58594
  }
58584
58595
  }
58585
58596
 
58597
+ /**
58598
+ * @internal
58599
+ */
58600
+ class AccidentalGlyph extends MusicFontGlyph {
58601
+ constructor(x, y, accidentalType, scale) {
58602
+ super(x, y, scale, AccidentalGlyph.getMusicSymbol(accidentalType));
58603
+ }
58604
+ static getMusicSymbol(accidentalType) {
58605
+ switch (accidentalType) {
58606
+ case AccidentalType.Natural:
58607
+ return MusicFontSymbol.AccidentalNatural;
58608
+ case AccidentalType.Sharp:
58609
+ return MusicFontSymbol.AccidentalSharp;
58610
+ case AccidentalType.Flat:
58611
+ return MusicFontSymbol.AccidentalFlat;
58612
+ case AccidentalType.NaturalQuarterNoteUp:
58613
+ return MusicFontSymbol.AccidentalQuarterToneSharpNaturalArrowUp;
58614
+ case AccidentalType.SharpQuarterNoteUp:
58615
+ return MusicFontSymbol.AccidentalThreeQuarterTonesSharpArrowUp;
58616
+ case AccidentalType.FlatQuarterNoteUp:
58617
+ return MusicFontSymbol.AccidentalQuarterToneFlatArrowUp;
58618
+ case AccidentalType.DoubleSharp:
58619
+ return MusicFontSymbol.AccidentalDoubleSharp;
58620
+ case AccidentalType.DoubleFlat:
58621
+ return MusicFontSymbol.AccidentalDoubleFlat;
58622
+ }
58623
+ return MusicFontSymbol.None;
58624
+ }
58625
+ }
58626
+
58627
+ /**
58628
+ * @internal
58629
+ */
58630
+ class NumberedKeySignatureGlyph extends EffectGlyph {
58631
+ _keySignature;
58632
+ _keySignatureType;
58633
+ _text = '';
58634
+ _accidental = AccidentalType.None;
58635
+ _accidentalOffset = 0;
58636
+ _padding = 0;
58637
+ constructor(x, y, keySignature, keySignatureType) {
58638
+ super(x, y);
58639
+ this._keySignature = keySignature;
58640
+ this._keySignatureType = keySignatureType;
58641
+ }
58642
+ doLayout() {
58643
+ super.doLayout();
58644
+ const text = '1 = ';
58645
+ let text2 = '';
58646
+ let accidental = AccidentalType.None;
58647
+ switch (this._keySignatureType) {
58648
+ case KeySignatureType.Major:
58649
+ switch (this._keySignature) {
58650
+ case KeySignature.Cb:
58651
+ text2 = ' C';
58652
+ accidental = AccidentalType.Flat;
58653
+ break;
58654
+ case KeySignature.Gb:
58655
+ text2 = ' G';
58656
+ accidental = AccidentalType.Flat;
58657
+ break;
58658
+ case KeySignature.Db:
58659
+ text2 = ' D';
58660
+ accidental = AccidentalType.Flat;
58661
+ break;
58662
+ case KeySignature.Ab:
58663
+ text2 = ' A';
58664
+ accidental = AccidentalType.Flat;
58665
+ break;
58666
+ case KeySignature.Eb:
58667
+ text2 = ' E';
58668
+ accidental = AccidentalType.Flat;
58669
+ break;
58670
+ case KeySignature.Bb:
58671
+ text2 = ' B';
58672
+ accidental = AccidentalType.Flat;
58673
+ break;
58674
+ case KeySignature.F:
58675
+ text2 = 'F';
58676
+ break;
58677
+ case KeySignature.C:
58678
+ text2 = 'C';
58679
+ accidental = AccidentalType.None;
58680
+ break;
58681
+ case KeySignature.G:
58682
+ text2 = 'G';
58683
+ accidental = AccidentalType.None;
58684
+ break;
58685
+ case KeySignature.D:
58686
+ text2 = 'D';
58687
+ accidental = AccidentalType.None;
58688
+ break;
58689
+ case KeySignature.A:
58690
+ text2 = 'A';
58691
+ accidental = AccidentalType.None;
58692
+ break;
58693
+ case KeySignature.E:
58694
+ text2 = 'E';
58695
+ accidental = AccidentalType.None;
58696
+ break;
58697
+ case KeySignature.B:
58698
+ text2 = 'B';
58699
+ accidental = AccidentalType.None;
58700
+ break;
58701
+ case KeySignature.FSharp:
58702
+ text2 = ' F';
58703
+ accidental = AccidentalType.Sharp;
58704
+ break;
58705
+ case KeySignature.CSharp:
58706
+ text2 = ' C';
58707
+ accidental = AccidentalType.Sharp;
58708
+ break;
58709
+ }
58710
+ break;
58711
+ case KeySignatureType.Minor:
58712
+ switch (this._keySignature) {
58713
+ case KeySignature.Cb:
58714
+ text2 = ' a';
58715
+ accidental = AccidentalType.Flat;
58716
+ break;
58717
+ case KeySignature.Gb:
58718
+ text2 = ' e';
58719
+ accidental = AccidentalType.Flat;
58720
+ break;
58721
+ case KeySignature.Db:
58722
+ text2 = ' b';
58723
+ accidental = AccidentalType.Flat;
58724
+ break;
58725
+ case KeySignature.Ab:
58726
+ text2 = 'f';
58727
+ accidental = AccidentalType.None;
58728
+ break;
58729
+ case KeySignature.Eb:
58730
+ text2 = 'c';
58731
+ accidental = AccidentalType.None;
58732
+ break;
58733
+ case KeySignature.Bb:
58734
+ text2 = 'g';
58735
+ accidental = AccidentalType.None;
58736
+ break;
58737
+ case KeySignature.F:
58738
+ text2 = 'd';
58739
+ break;
58740
+ case KeySignature.C:
58741
+ text2 = 'a';
58742
+ accidental = AccidentalType.None;
58743
+ break;
58744
+ case KeySignature.G:
58745
+ text2 = 'e';
58746
+ accidental = AccidentalType.None;
58747
+ break;
58748
+ case KeySignature.D:
58749
+ text2 = 'b';
58750
+ accidental = AccidentalType.None;
58751
+ break;
58752
+ case KeySignature.A:
58753
+ text2 = ' f';
58754
+ accidental = AccidentalType.Sharp;
58755
+ break;
58756
+ case KeySignature.E:
58757
+ text2 = ' c';
58758
+ accidental = AccidentalType.Sharp;
58759
+ break;
58760
+ case KeySignature.B:
58761
+ text2 = ' g';
58762
+ accidental = AccidentalType.Sharp;
58763
+ break;
58764
+ case KeySignature.FSharp:
58765
+ text2 = ' d';
58766
+ accidental = AccidentalType.Sharp;
58767
+ break;
58768
+ case KeySignature.CSharp:
58769
+ text2 = ' a';
58770
+ accidental = AccidentalType.Sharp;
58771
+ break;
58772
+ }
58773
+ break;
58774
+ }
58775
+ this._text = text + text2;
58776
+ this._accidental = accidental;
58777
+ const c = this.renderer.scoreRenderer.canvas;
58778
+ const settings = this.renderer.settings;
58779
+ const res = settings.display.resources;
58780
+ c.font = res.numberedNotationFont;
58781
+ this._accidentalOffset = c.measureText(text).width;
58782
+ const fullSize = c.measureText(text + text2);
58783
+ this._padding =
58784
+ this.renderer.index === 0 ? settings.display.firstStaffPaddingLeft : settings.display.staffPaddingLeft;
58785
+ this.width = this._padding + fullSize.width;
58786
+ this.height = fullSize.height;
58787
+ }
58788
+ paint(cx, cy, canvas) {
58789
+ const _ = ElementStyleHelper.bar(canvas, BarSubElement.NumberedKeySignature, this.renderer.bar);
58790
+ try {
58791
+ const res = this.renderer.resources;
58792
+ canvas.font = res.numberedNotationFont;
58793
+ canvas.textBaseline = TextBaseline.Alphabetic;
58794
+ canvas.fillText(this._text, cx + this.x + this._padding, cy + this.y + this.height);
58795
+ if (this._accidental !== AccidentalType.None) {
58796
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + this._padding + this._accidentalOffset, cy + this.y + this.height, 1, AccidentalGlyph.getMusicSymbol(this._accidental), false);
58797
+ }
58798
+ }
58799
+ finally {
58800
+ _?.[Symbol.dispose]?.();
58801
+ }
58802
+ }
58803
+ }
58804
+
58805
+ /**
58806
+ * @internal
58807
+ */
58808
+ class NumberedBarKeySignatureEffectInfo extends EffectInfo {
58809
+ get notationElement() {
58810
+ return NotationElement.EffectNumberedNotationKeySignature;
58811
+ }
58812
+ get hideOnMultiTrack() {
58813
+ return false;
58814
+ }
58815
+ get canShareBand() {
58816
+ return false;
58817
+ }
58818
+ get sizingMode() {
58819
+ return EffectBarGlyphSizing.FullBar;
58820
+ }
58821
+ shouldCreateGlyph(_settings, beat) {
58822
+ const bar = beat.voice.bar;
58823
+ return (beat.index === 0 &&
58824
+ beat.voice.index === 0 &&
58825
+ (!bar.previousBar || bar.keySignature !== bar.previousBar.keySignature));
58826
+ }
58827
+ createNewGlyph(renderer, _beat) {
58828
+ return new NumberedKeySignatureGlyph(0, 0, renderer.bar.keySignature, renderer.bar.keySignatureType);
58829
+ }
58830
+ canExpand(_from, _to) {
58831
+ return false;
58832
+ }
58833
+ }
58834
+
58586
58835
  /**
58587
58836
  * @internal
58588
58837
  */
@@ -59335,6 +59584,7 @@ class LeftToRightLayoutingGlyphGroup extends GlyphGroup {
59335
59584
  class MultiVoiceContainerGlyph extends Glyph {
59336
59585
  static KeySizeBeat = 'Beat';
59337
59586
  voiceDrawOrder;
59587
+ _beatGlyphLookup = new Map();
59338
59588
  beatGlyphs = new Map();
59339
59589
  tupletGroups = new Map();
59340
59590
  constructor() {
@@ -59467,6 +59717,10 @@ class MultiVoiceContainerGlyph extends Glyph {
59467
59717
  beatGlyphs.length === 0 ? 0 : beatGlyphs[beatGlyphs.length - 1].x + beatGlyphs[beatGlyphs.length - 1].width;
59468
59718
  bg.renderer = this.renderer;
59469
59719
  beatGlyphs.push(bg);
59720
+ const id = bg.beatId;
59721
+ if (id >= 0) {
59722
+ this._beatGlyphLookup.set(id, bg);
59723
+ }
59470
59724
  const newWidth = bg.x + bg.width;
59471
59725
  if (newWidth > this.width) {
59472
59726
  this.width = newWidth;
@@ -59512,11 +59766,10 @@ class MultiVoiceContainerGlyph extends Glyph {
59512
59766
  return Number.NaN;
59513
59767
  }
59514
59768
  getBeatContainer(beat) {
59515
- if (!this.beatGlyphs.has(beat.voice.index)) {
59769
+ if (!this._beatGlyphLookup.has(beat.id)) {
59516
59770
  return undefined;
59517
59771
  }
59518
- const beats = this.beatGlyphs.get(beat.voice.index);
59519
- return beat.index < beats.length ? beats[beat.index] : undefined;
59772
+ return this._beatGlyphLookup.get(beat.id);
59520
59773
  }
59521
59774
  buildBoundingsLookup(barBounds, cx, cy) {
59522
59775
  for (const [index, c] of this.beatGlyphs) {
@@ -62139,6 +62392,9 @@ class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
62139
62392
  get absoluteDisplayStart() {
62140
62393
  return this.renderer.bar.masterBar.start;
62141
62394
  }
62395
+ get beatId() {
62396
+ return -1;
62397
+ }
62142
62398
  get onTimeX() {
62143
62399
  return 0;
62144
62400
  }
@@ -62210,7 +62466,6 @@ class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
62210
62466
  case BeatXPosition.OnNotes:
62211
62467
  case BeatXPosition.MiddleNotes:
62212
62468
  case BeatXPosition.Stem:
62213
- return g.x + g.width / 2;
62214
62469
  case BeatXPosition.PostNotes:
62215
62470
  return g.x + g.width;
62216
62471
  case BeatXPosition.EndBeat:
@@ -63302,13 +63557,13 @@ class BarRendererBase {
63302
63557
  getBeatX(beat, requestedPosition = BeatXPosition.PreNotes, useSharedSizes = false) {
63303
63558
  return this.beatGlyphsStart + this.voiceContainer.getBeatX(beat, requestedPosition, useSharedSizes);
63304
63559
  }
63305
- getRatioPositionX(ticks) {
63560
+ getRatioPositionX(ratio) {
63306
63561
  const firstOnNoteX = this.bar.isEmpty
63307
63562
  ? this.beatGlyphsStart
63308
63563
  : this.getBeatX(this.bar.voices[0].beats[0], BeatXPosition.MiddleNotes);
63309
63564
  const x = firstOnNoteX;
63310
63565
  const w = this.postBeatGlyphsStart - firstOnNoteX;
63311
- return x + w * ticks;
63566
+ return x + w * ratio;
63312
63567
  }
63313
63568
  getNoteX(note, requestedPosition) {
63314
63569
  return this.beatGlyphsStart + this.voiceContainer.getNoteX(note, requestedPosition);
@@ -63995,6 +64250,7 @@ class TabWhammyBarGlyph extends EffectGlyph {
63995
64250
  this.originalTopOffset = topY;
63996
64251
  this.originalBottomOffset = bottomY;
63997
64252
  this.height = topY + bottomY;
64253
+ this.width = 0;
63998
64254
  }
63999
64255
  _getOffset(value) {
64000
64256
  if (value === 0) {
@@ -65821,6 +66077,12 @@ class BarNumberGlyph extends Glyph {
65821
66077
  this._number = `${num} `;
65822
66078
  }
65823
66079
  doLayout() {
66080
+ // TODO: activate this and update paddings accordingly.
66081
+ // if (!this.renderer.staff!.isFirstInSystem) {
66082
+ // this.width = 0;
66083
+ // this.height = 0;
66084
+ // return;
66085
+ // }
65824
66086
  this.renderer.scoreRenderer.canvas.font = this.renderer.resources.barNumberFont;
65825
66087
  const size = this.renderer.scoreRenderer.canvas.measureText(this._number);
65826
66088
  this.width = size.width;
@@ -65836,6 +66098,7 @@ class BarNumberGlyph extends Glyph {
65836
66098
  const res = this.renderer.resources;
65837
66099
  const baseline = canvas.textBaseline;
65838
66100
  canvas.font = res.barNumberFont;
66101
+ canvas.textBaseline = TextBaseline.Top;
65839
66102
  canvas.fillText(this._number, cx + this.x, cy + this.y);
65840
66103
  canvas.textBaseline = baseline;
65841
66104
  }
@@ -65848,47 +66111,87 @@ class BarNumberGlyph extends Glyph {
65848
66111
  /**
65849
66112
  * @internal
65850
66113
  */
65851
- class FlagGlyph extends MusicFontGlyph {
65852
- constructor(x, y, duration, direction, isGrace) {
65853
- super(x, y, isGrace ? EngravingSettings.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
66114
+ class NumberedTieGlyph extends NoteTieGlyph {
66115
+ shouldDrawBendSlur() {
66116
+ return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66117
+ !!this.startNote.bendOrigin &&
66118
+ this.startNote.isTieOrigin);
65854
66119
  }
65855
- static getSymbol(duration, direction, isGrace) {
65856
- if (isGrace) {
65857
- duration = Duration.Eighth;
66120
+ calculateTieDirection() {
66121
+ return BeamDirection.Up;
66122
+ }
66123
+ }
66124
+
66125
+ /**
66126
+ * @internal
66127
+ */
66128
+ class AccidentalColumnInfo {
66129
+ x = 0;
66130
+ y = -3e3;
66131
+ width = 0;
66132
+ }
66133
+ /**
66134
+ * @internal
66135
+ */
66136
+ class AccidentalGroupGlyph extends GlyphGroup {
66137
+ constructor() {
66138
+ super(0, 0);
66139
+ }
66140
+ doLayout() {
66141
+ if (!this.glyphs || this.glyphs.length === 0) {
66142
+ this.width = 0;
66143
+ return;
65858
66144
  }
65859
- if (direction === BeamDirection.Up) {
65860
- switch (duration) {
65861
- case Duration.Eighth:
65862
- return MusicFontSymbol.Flag8thUp;
65863
- case Duration.Sixteenth:
65864
- return MusicFontSymbol.Flag16thUp;
65865
- case Duration.ThirtySecond:
65866
- return MusicFontSymbol.Flag32ndUp;
65867
- case Duration.SixtyFourth:
65868
- return MusicFontSymbol.Flag64thUp;
65869
- case Duration.OneHundredTwentyEighth:
65870
- return MusicFontSymbol.Flag128thUp;
65871
- case Duration.TwoHundredFiftySixth:
65872
- return MusicFontSymbol.Flag256thUp;
65873
- default:
65874
- return MusicFontSymbol.Flag8thUp;
66145
+ //
66146
+ // Determine Columns for accidentals
66147
+ //
66148
+ this.glyphs.sort((a, b) => {
66149
+ if (a.y < b.y) {
66150
+ return -1;
66151
+ }
66152
+ if (a.y > b.y) {
66153
+ return 1;
66154
+ }
66155
+ return 0;
66156
+ });
66157
+ // defines the reserved y position of the columns
66158
+ const columns = [];
66159
+ columns.push(new AccidentalColumnInfo());
66160
+ for (let i = 0, j = this.glyphs.length; i < j; i++) {
66161
+ const g = this.glyphs[i];
66162
+ g.renderer = this.renderer;
66163
+ g.doLayout();
66164
+ // find column where glyph fits into
66165
+ // as long the glyph does not fit into the current column
66166
+ let gColumn = 0;
66167
+ while (columns[gColumn].y > g.y) {
66168
+ // move to next column
66169
+ gColumn++;
66170
+ // and create the new column if needed
66171
+ if (gColumn === columns.length) {
66172
+ columns.push(new AccidentalColumnInfo());
66173
+ }
66174
+ }
66175
+ // temporary save column as X
66176
+ g.x = gColumn;
66177
+ columns[gColumn].y = g.y + g.height;
66178
+ if (columns[gColumn].width < g.width) {
66179
+ columns[gColumn].width = g.width;
65875
66180
  }
65876
66181
  }
65877
- switch (duration) {
65878
- case Duration.Eighth:
65879
- return MusicFontSymbol.Flag8thDown;
65880
- case Duration.Sixteenth:
65881
- return MusicFontSymbol.Flag16thDown;
65882
- case Duration.ThirtySecond:
65883
- return MusicFontSymbol.Flag32ndDown;
65884
- case Duration.SixtyFourth:
65885
- return MusicFontSymbol.Flag64thDown;
65886
- case Duration.OneHundredTwentyEighth:
65887
- return MusicFontSymbol.Flag128thDown;
65888
- case Duration.TwoHundredFiftySixth:
65889
- return MusicFontSymbol.Flag128thDown;
65890
- default:
65891
- return MusicFontSymbol.Flag8thDown;
66182
+ //
66183
+ // Place accidentals in columns
66184
+ //
66185
+ this.width = 0;
66186
+ const padding = this.renderer.smuflMetrics.accidentalPadding;
66187
+ for (const column of columns) {
66188
+ this.width += column.width + padding;
66189
+ column.x = this.width;
66190
+ }
66191
+ for (let i = 0, j = this.glyphs.length; i < j; i++) {
66192
+ const g = this.glyphs[i];
66193
+ const column = columns[g.x];
66194
+ g.x = this.width - column.x;
65892
66195
  }
65893
66196
  }
65894
66197
  }
@@ -65896,31 +66199,76 @@ class FlagGlyph extends MusicFontGlyph {
65896
66199
  /**
65897
66200
  * @internal
65898
66201
  */
65899
- class RepeatCountGlyph extends Glyph {
65900
- _count = 0;
65901
- constructor(x, y, count) {
65902
- super(x, y);
65903
- this._count = 0;
65904
- this._count = count;
66202
+ class AugmentationDotGlyph extends MusicFontGlyph {
66203
+ constructor(x, y) {
66204
+ super(x, y, 1, MusicFontSymbol.AugmentationDot);
65905
66205
  }
65906
66206
  doLayout() {
65907
- this.renderer.scoreRenderer.canvas.font = this.renderer.resources.barNumberFont;
65908
- const size = this.renderer.scoreRenderer.canvas.measureText(`x${this._count}`);
65909
- this.width = 0; // do not account width
65910
- this.height = size.height;
65911
- this.y -= size.height;
66207
+ super.doLayout();
66208
+ this.offsetX = this.width / 2;
66209
+ this.width *= 1.5;
66210
+ }
66211
+ }
66212
+
66213
+ /**
66214
+ * @internal
66215
+ */
66216
+ class BeatGlyphBase extends GlyphGroup {
66217
+ _effectGlyphs = [];
66218
+ _normalGlyphs = [];
66219
+ container;
66220
+ computedWidth = 0;
66221
+ constructor() {
66222
+ super(0, 0);
66223
+ }
66224
+ doLayout() {
66225
+ // left to right layout
66226
+ let w = 0;
66227
+ if (this.glyphs) {
66228
+ for (let i = 0, j = this.glyphs.length; i < j; i++) {
66229
+ const g = this.glyphs[i];
66230
+ g.x = w;
66231
+ g.renderer = this.renderer;
66232
+ g.doLayout();
66233
+ w += g.width;
66234
+ }
66235
+ }
66236
+ this.width = w;
66237
+ this.computedWidth = w;
66238
+ }
66239
+ noteLoop(action) {
66240
+ for (let i = this.container.beat.notes.length - 1; i >= 0; i--) {
66241
+ action(this.container.beat.notes[i]);
66242
+ }
66243
+ }
66244
+ addEffect(g) {
66245
+ super.addGlyph(g);
66246
+ this._effectGlyphs.push(g);
66247
+ }
66248
+ addNormal(g) {
66249
+ super.addGlyph(g);
66250
+ this._normalGlyphs.push(g);
66251
+ }
66252
+ get effectElement() {
66253
+ return undefined;
65912
66254
  }
65913
66255
  paint(cx, cy, canvas) {
65914
- const _ = ElementStyleHelper.bar(canvas, this.renderer.repeatsBarSubElement, this.renderer.bar);
66256
+ this._paintEffects(cx, cy, canvas);
66257
+ this._paintNormal(cx, cy, canvas);
66258
+ }
66259
+ _paintNormal(cx, cy, canvas) {
66260
+ for (const g of this._normalGlyphs) {
66261
+ g.paint(cx + this.x, cy + this.y, canvas);
66262
+ }
66263
+ }
66264
+ _paintEffects(cx, cy, canvas) {
66265
+ const _ = this.effectElement
66266
+ ? ElementStyleHelper.beat(canvas, this.effectElement, this.container.beat)
66267
+ : undefined;
65915
66268
  try {
65916
- const res = this.renderer.resources;
65917
- const oldAlign = canvas.textAlign;
65918
- canvas.font = res.barNumberFont;
65919
- canvas.textAlign = TextAlign.Right;
65920
- const s = `x${this._count}`;
65921
- const w = canvas.measureText(s).width / 1.5;
65922
- canvas.fillText(s, cx + this.x - w, cy + this.y);
65923
- canvas.textAlign = oldAlign;
66269
+ for (const g of this._effectGlyphs) {
66270
+ g.paint(cx + this.x, cy + this.y, canvas);
66271
+ }
65924
66272
  }
65925
66273
  finally {
65926
66274
  _?.[Symbol.dispose]?.();
@@ -65929,1847 +66277,1789 @@ class RepeatCountGlyph extends Glyph {
65929
66277
  }
65930
66278
 
65931
66279
  /**
65932
- * This simple glyph allows to put an empty region in to a BarRenderer.
65933
66280
  * @internal
65934
66281
  */
65935
- class SpacingGlyph extends Glyph {
65936
- constructor(x, y, width) {
65937
- super(x, y);
65938
- this.width = width;
66282
+ class BeatOnNoteGlyphBase extends BeatGlyphBase {
66283
+ onTimeX = 0;
66284
+ middleX = 0;
66285
+ stemX = 0;
66286
+ }
66287
+
66288
+ /**
66289
+ * @internal
66290
+ */
66291
+ class DeadSlappedBeatGlyph extends Glyph {
66292
+ _topY = 0;
66293
+ constructor() {
66294
+ super(0, 0);
65939
66295
  }
65940
66296
  getBoundingBoxTop() {
65941
- return Number.NaN;
66297
+ return this._topY;
65942
66298
  }
65943
66299
  getBoundingBoxBottom() {
65944
- return Number.NaN;
66300
+ return this._topY + this.height;
66301
+ }
66302
+ doLayout() {
66303
+ this.width = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.NoteheadSlashWhiteHalf);
66304
+ const renderer = this.renderer;
66305
+ const crossHeight = renderer.getLineHeight(renderer.heightLineCount - 1);
66306
+ const staffTop = renderer.getLineY(0);
66307
+ const staffHeight = renderer.drawnLineCount > 0 ? renderer.getLineHeight(renderer.drawnLineCount - 1) : 0;
66308
+ const topY = staffTop + staffHeight / 2 - crossHeight / 2;
66309
+ this.height = crossHeight;
66310
+ this._topY = topY;
66311
+ }
66312
+ paint(cx, cy, canvas) {
66313
+ const crossHeight = this.height;
66314
+ const topY = this._topY;
66315
+ const lw = canvas.lineWidth;
66316
+ canvas.lineWidth = this.renderer.smuflMetrics.deadSlappedLineWidth;
66317
+ canvas.moveTo(cx + this.x, cy + topY);
66318
+ canvas.lineTo(cx + this.x + this.width, cy + topY + crossHeight);
66319
+ canvas.moveTo(cx + this.x, cy + topY + crossHeight);
66320
+ canvas.lineTo(cx + this.x + this.width, cy + topY);
66321
+ canvas.stroke();
66322
+ canvas.lineWidth = lw;
65945
66323
  }
65946
66324
  }
65947
66325
 
65948
66326
  /**
65949
- * This is a base class for any bar renderer which renders music notation on a staff
65950
- * with lines like Standard Notation, Guitar Tablatures and Slash Notation.
65951
- *
65952
- * This base class takes care of the typical bits like drawing lines,
65953
- * allowing note positioning and creating glyphs like repeats, bar numbers etc..
65954
66327
  * @internal
65955
66328
  */
65956
- class LineBarRenderer extends BarRendererBase {
65957
- firstLineY = 0;
65958
- _startSpacing = false;
65959
- tupletSize = 0;
65960
- get lineOffset() {
65961
- return this.lineSpacing;
65962
- }
65963
- get tupletOffset() {
65964
- return this.smuflMetrics.oneStaffSpace * 0.5;
65965
- }
65966
- get topGlyphOverflow() {
65967
- return 0;
65968
- }
65969
- get bottomGlyphOverflow() {
65970
- return 0;
65971
- }
65972
- initLineBasedSizes() {
65973
- this.height = this.lineOffset * (this.heightLineCount - 1);
66329
+ class NumberedNoteHeadGlyph extends Glyph {
66330
+ _isGrace;
66331
+ _beat;
66332
+ _number;
66333
+ _octaveDots;
66334
+ _octaveDotsY = 0;
66335
+ _octaveDotHeight = 0;
66336
+ constructor(x, y, number, isGrace, beat, octaveDots) {
66337
+ super(x, y);
66338
+ this._isGrace = isGrace;
66339
+ this._number = number;
66340
+ this._beat = beat;
66341
+ this._octaveDots = octaveDots;
65974
66342
  }
65975
- updateSizes() {
65976
- this.initLineBasedSizes();
65977
- this.adjustSizes();
65978
- this.updateFirstLineY();
65979
- super.updateSizes();
66343
+ getBoundingBoxTop() {
66344
+ let y = -this.height / 2;
66345
+ if (this._octaveDots > 0 && this._octaveDotsY < y) {
66346
+ y = this._octaveDotsY;
66347
+ }
66348
+ return this.y + y;
65980
66349
  }
65981
- adjustSizes() {
66350
+ getBoundingBoxBottom() {
66351
+ let y = this.height / 2;
66352
+ const dotsBottom = this._octaveDotsY + Math.abs(this._octaveDots) * this._octaveDotHeight * 2;
66353
+ if (this._octaveDots < 0 && y < dotsBottom) {
66354
+ y = dotsBottom;
66355
+ }
66356
+ return this.y + y;
65982
66357
  }
65983
- updateFirstLineY() {
65984
- const fullLineHeight = this.lineOffset * (this.heightLineCount - 1);
65985
- const actualLineHeight = (this.drawnLineCount - 1) * this.lineOffset;
65986
- const lineYOffset = this.smuflMetrics.staffLineThickness / 2;
65987
- this.firstLineY = (((fullLineHeight - actualLineHeight) / 2) | 0) - lineYOffset;
65988
- }
65989
- doLayout() {
65990
- this.initLineBasedSizes();
65991
- this.updateFirstLineY();
65992
- this.tupletSize = this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tuplet0);
65993
- super.doLayout();
65994
- }
65995
- getLineY(line) {
65996
- return this.firstLineY + this.getLineHeight(line);
65997
- }
65998
- getLineHeight(line) {
65999
- return this.lineOffset * line;
66000
- }
66001
- paintContent(cx, cy, canvas) {
66002
- super.paintContent(cx, cy, canvas);
66003
- this.paintBeams(cx, cy, canvas, this.flagsSubElement, this.beamsSubElement);
66004
- this.paintTuplets(cx, cy, canvas, this.tupletSubElement);
66005
- }
66006
- paintBackground(cx, cy, canvas) {
66007
- super.paintBackground(cx, cy, canvas);
66008
- // canvas.color = Color.random(100);
66009
- // canvas.fillRect(cx + this.x, cy + this.y, this.width, this.height);
66010
- //
66011
- // draw string lines
66012
- //
66013
- this.paintStaffLines(cx, cy, canvas);
66014
- this.paintSimileMark(cx, cy, canvas);
66015
- }
66016
- paintStaffLines(cx, cy, canvas) {
66017
- const _ = ElementStyleHelper.bar(canvas, this.staffLineBarSubElement, this.bar, true);
66358
+ paint(cx, cy, canvas) {
66359
+ const _ = this._beat.isRest
66360
+ ? ElementStyleHelper.beat(canvas, BeatSubElement.NumberedRests, this._beat)
66361
+ : this._beat.notes.length > 0
66362
+ ? ElementStyleHelper.note(canvas, NoteSubElement.NumberedNumber, this._beat.notes[0])
66363
+ : undefined;
66018
66364
  try {
66019
- // collect tab note position for spaces
66020
- const spaces = [];
66021
- for (let i = 0, j = this.drawnLineCount; i < j; i++) {
66022
- spaces.push([]);
66023
- }
66024
- // on multibar rest glyphs we don't have spaces as they are empty
66025
- if (!this.additionalMultiRestBars) {
66026
- this.collectSpaces(spaces);
66027
- }
66028
- // if we have multiple voices we need to sort by X-position, otherwise have a wild mix in the list
66029
- // but painting relies on ascending X-position
66030
- for (const line of spaces) {
66031
- line.sort((a, b) => {
66032
- return a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0;
66033
- });
66034
- }
66035
- // during system fitting it can happen that we have fraction widths
66036
- // but to have lines until the full end-pixel we round up.
66037
- // this way we avoid holes,
66038
- const lineWidth = this.width;
66039
- // we want the lines to be exactly virtually aligned with the respective Y-position
66040
- // for note heads to align correctly
66041
- const lineYOffset = this.smuflMetrics.staffLineThickness / 2;
66042
- for (let i = 0; i < this.drawnLineCount; i++) {
66043
- const lineY = this.getLineY(i) - lineYOffset;
66044
- let lineX = 0;
66045
- for (const line of spaces[i]) {
66046
- canvas.fillRect(cx + this.x + lineX, cy + this.y + lineY, line[0] - lineX, this.smuflMetrics.staffLineThickness);
66047
- lineX = line[0] + line[1];
66048
- }
66049
- canvas.fillRect(cx + this.x + lineX, cy + this.y + lineY, lineWidth - lineX, this.smuflMetrics.staffLineThickness);
66365
+ const res = this.renderer.resources;
66366
+ canvas.font = this._isGrace ? res.numberedNotationGraceFont : res.numberedNotationFont;
66367
+ const baseline = canvas.textBaseline;
66368
+ canvas.textBaseline = TextBaseline.Middle;
66369
+ canvas.textAlign = TextAlign.Left;
66370
+ canvas.fillText(this._number.toString(), cx + this.x, cy + this.y);
66371
+ canvas.textBaseline = baseline;
66372
+ const dotCount = Math.abs(this._octaveDots);
66373
+ let dotsY = this._octaveDotsY + res.engravingSettings.glyphTop.get(MusicFontSymbol.AugmentationDot);
66374
+ for (let d = 0; d < dotCount; d++) {
66375
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + this.width / 2, cy + this.y + dotsY, 1, MusicFontSymbol.AugmentationDot, true);
66376
+ dotsY += this._octaveDotHeight * 2;
66050
66377
  }
66051
66378
  }
66052
66379
  finally {
66053
66380
  _?.[Symbol.dispose]?.();
66054
66381
  }
66055
66382
  }
66056
- collectSpaces(_spaces) {
66383
+ doLayout() {
66384
+ const res = this.renderer.resources;
66385
+ const font = this._isGrace ? res.numberedNotationGraceFont : res.numberedNotationFont;
66386
+ const c = this.renderer.scoreRenderer.canvas;
66387
+ c.font = font;
66388
+ const size = c.measureText(`${this._number}`);
66389
+ this.height = size.height;
66390
+ this.width = size.width;
66391
+ const dotCount = this._octaveDots;
66392
+ const dotHeight = res.engravingSettings.glyphHeights.get(MusicFontSymbol.AugmentationDot);
66393
+ const allDotsHeight = Math.abs(dotCount) * dotHeight * 2;
66394
+ if (dotCount > 0) {
66395
+ this._octaveDotsY =
66396
+ -(this.height / 2) -
66397
+ allDotsHeight -
66398
+ res.engravingSettings.glyphTop.get(MusicFontSymbol.AugmentationDot);
66399
+ }
66400
+ else if (dotCount < 0) {
66401
+ this._octaveDotsY =
66402
+ this.height / 2 +
66403
+ // one for the padding
66404
+ dotHeight +
66405
+ // align the dots
66406
+ res.engravingSettings.glyphTop.get(MusicFontSymbol.AugmentationDot);
66407
+ }
66408
+ this._octaveDotHeight = dotHeight;
66057
66409
  }
66058
- createStartSpacing() {
66059
- if (this._startSpacing) {
66410
+ }
66411
+
66412
+ /**
66413
+ * This simple glyph allows to put an empty region in to a BarRenderer.
66414
+ * @internal
66415
+ */
66416
+ class SpacingGlyph extends Glyph {
66417
+ constructor(x, y, width) {
66418
+ super(x, y);
66419
+ this.width = width;
66420
+ }
66421
+ getBoundingBoxTop() {
66422
+ return Number.NaN;
66423
+ }
66424
+ getBoundingBoxBottom() {
66425
+ return Number.NaN;
66426
+ }
66427
+ }
66428
+
66429
+ /**
66430
+ * @internal
66431
+ */
66432
+ class NumberedBeatPreNotesGlyph extends BeatGlyphBase {
66433
+ isNaturalizeAccidental = false;
66434
+ accidental = AccidentalType.None;
66435
+ skipLayout = false;
66436
+ get effectElement() {
66437
+ return BeatSubElement.NumberedEffects;
66438
+ }
66439
+ doLayout() {
66440
+ if (this.skipLayout) {
66060
66441
  return;
66061
66442
  }
66062
- const padding = this.index === 0 ? this.settings.display.firstStaffPaddingLeft : this.settings.display.staffPaddingLeft;
66063
- this.addPreBeatGlyph(new SpacingGlyph(0, 0, padding));
66064
- this._startSpacing = true;
66065
- }
66066
- paintTuplets(cx, cy, canvas, beatElement, bracketsAsArcs = false) {
66067
- for (const v of this.voiceContainer.voiceDrawOrder) {
66068
- if (this.voiceContainer.tupletGroups.has(v)) {
66069
- const voice = this.voiceContainer.tupletGroups.get(v);
66070
- for (const tupletGroup of voice) {
66071
- this._paintTupletHelper(cx, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
66443
+ if (!this.container.beat.isRest && !this.container.beat.isEmpty) {
66444
+ const accidentals = new AccidentalGroupGlyph();
66445
+ accidentals.renderer = this.renderer;
66446
+ if (this.container.beat.notes.length > 0) {
66447
+ const note = this.container.beat.notes[0];
66448
+ // Notes
66449
+ // - Compared to standard notation accidentals:
66450
+ // - Flat keysigs: When there is a naturalize symbol (against key signature, not naturalizing same line) we have a # in Numbered notation
66451
+ // - Flat keysigs: When there is a flat symbol standard notation we also have a flat in Numbered notation
66452
+ // - C keysig: A sharp on standard notation is a sharp on numbered notation
66453
+ // - # keysigs: When there is a # symbol on standard notation we also a sharp in numbered notation
66454
+ // - # keysigs: When there is a naturalize symbol (against key signature, not naturalizing same line) we have a flat in Numbered notation
66455
+ // Or generally:
66456
+ // - numbered notation has the same accidentals as standard notation if applied
66457
+ // - when the standard notation naturalizes the accidental from the key signature, the numbered notation has the reversed accidental
66458
+ const accidentalMode = note ? note.accidentalMode : NoteAccidentalMode.Default;
66459
+ const noteValue = AccidentalHelper.getNoteValue(note);
66460
+ let accidentalToSet = ModelUtils.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
66461
+ if (accidentalToSet === AccidentalType.Natural) {
66462
+ const ks = this.renderer.bar.keySignature;
66463
+ const ksi = ks + 7;
66464
+ const naturalizeAccidentalForKeySignature = ksi < 7 ? AccidentalType.Sharp : AccidentalType.Flat;
66465
+ accidentalToSet = naturalizeAccidentalForKeySignature;
66466
+ this.isNaturalizeAccidental = true;
66467
+ }
66468
+ // do we need an accidental on the note?
66469
+ if (accidentalToSet !== AccidentalType.None) {
66470
+ this.accidental = accidentalToSet;
66471
+ const sr = this.renderer;
66472
+ const color = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.NumberedAccidentals, note);
66473
+ const g = new AccidentalGlyph(0, sr.getLineY(0), accidentalToSet, note.beat.graceType !== GraceType.None
66474
+ ? EngravingSettings.GraceScale * EngravingSettings.GraceScale
66475
+ : EngravingSettings.GraceScale);
66476
+ g.colorOverride = color;
66477
+ g.renderer = this.renderer;
66478
+ accidentals.addGlyph(g);
66479
+ this.addNormal(accidentals);
66480
+ this.addNormal(new SpacingGlyph(0, 0, this.renderer.smuflMetrics.preNoteEffectPadding));
66072
66481
  }
66073
66482
  }
66074
66483
  }
66484
+ super.doLayout();
66075
66485
  }
66076
- getTupletBeamDirection(helper) {
66077
- return this.getBeamDirection(helper);
66486
+ }
66487
+ /**
66488
+ * @internal
66489
+ */
66490
+ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
66491
+ noteHeads = null;
66492
+ deadSlapped = null;
66493
+ get effectElement() {
66494
+ return BeatSubElement.NumberedEffects;
66078
66495
  }
66079
- _paintTupletHelper(cx, cy, canvas, h, beatElement, bracketsAsArcs) {
66080
- const res = this.resources;
66081
- const oldAlign = canvas.textAlign;
66082
- const oldBaseLine = canvas.textBaseline;
66083
- canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
66084
- canvas.textAlign = TextAlign.Center;
66085
- canvas.textBaseline = TextBaseline.Middle;
66086
- let s;
66087
- const num = h.beats[0].tupletNumerator;
66088
- const den = h.beats[0].tupletDenominator;
66089
- // list as in Guitar Pro 7. for certain tuplets only the numerator is shown
66090
- if (num === 2 && den === 3) {
66091
- s = [MusicFontSymbol.Tuplet2];
66092
- }
66093
- else if (num === 3 && den === 2) {
66094
- s = [MusicFontSymbol.Tuplet3];
66095
- }
66096
- else if (num === 4 && den === 6) {
66097
- s = [MusicFontSymbol.Tuplet4];
66098
- }
66099
- else if (num === 5 && den === 4) {
66100
- s = [MusicFontSymbol.Tuplet5];
66101
- }
66102
- else if (num === 6 && den === 4) {
66103
- s = [MusicFontSymbol.Tuplet6];
66104
- }
66105
- else if (num === 7 && den === 4) {
66106
- s = [MusicFontSymbol.Tuplet7];
66496
+ getNoteX(_note, requestedPosition) {
66497
+ let g = null;
66498
+ if (this.noteHeads) {
66499
+ g = this.noteHeads;
66107
66500
  }
66108
- else if (num === 9 && den === 8) {
66109
- s = [MusicFontSymbol.Tuplet9];
66501
+ else if (this.deadSlapped) {
66502
+ g = this.deadSlapped;
66110
66503
  }
66111
- else if (num === 10 && den === 8) {
66112
- s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet0];
66504
+ if (g) {
66505
+ let pos = g.x;
66506
+ switch (requestedPosition) {
66507
+ case NoteXPosition.Left:
66508
+ break;
66509
+ case NoteXPosition.Center:
66510
+ pos += g.width / 2;
66511
+ break;
66512
+ case NoteXPosition.Right:
66513
+ pos += g.width;
66514
+ break;
66515
+ }
66516
+ return pos;
66113
66517
  }
66114
- else if (num === 11 && den === 8) {
66115
- s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet1];
66518
+ return 0;
66519
+ }
66520
+ buildBoundingsLookup(beatBounds, cx, cy) {
66521
+ if (this.noteHeads && this.container.beat.notes.length > 0) {
66522
+ const noteBounds = new NoteBounds();
66523
+ noteBounds.note = this.container.beat.notes[0];
66524
+ noteBounds.noteHeadBounds = new Bounds();
66525
+ noteBounds.noteHeadBounds.x = cx + this.x + this.noteHeads.x;
66526
+ noteBounds.noteHeadBounds.y = cy + this.y + this.noteHeads.y - this.noteHeads.height / 2;
66527
+ noteBounds.noteHeadBounds.w = this.width;
66528
+ noteBounds.noteHeadBounds.h = this.height;
66529
+ beatBounds.addNote(noteBounds);
66116
66530
  }
66117
- else if (num === 12 && den === 8) {
66118
- s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet2];
66531
+ }
66532
+ getLowestNoteY() {
66533
+ return this._internalGetNoteY(NoteYPosition.Center);
66534
+ }
66535
+ getHighestNoteY() {
66536
+ return this._internalGetNoteY(NoteYPosition.Center);
66537
+ }
66538
+ getNoteY(_note, requestedPosition) {
66539
+ return this._internalGetNoteY(requestedPosition);
66540
+ }
66541
+ getRestY(requestedPosition) {
66542
+ return this._internalGetNoteY(requestedPosition);
66543
+ }
66544
+ _internalGetNoteY(requestedPosition) {
66545
+ let g = null;
66546
+ if (this.noteHeads) {
66547
+ g = this.noteHeads;
66119
66548
  }
66120
- else if (num === 13 && den === 8) {
66121
- s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet3];
66549
+ else if (this.deadSlapped) {
66550
+ g = this.deadSlapped;
66122
66551
  }
66123
- else {
66124
- s = [];
66125
- const zero = MusicFontSymbol.Tuplet0;
66126
- if (num > 10) {
66127
- s.push((zero + Math.floor(num / 10)));
66128
- s.push((zero + (num - 10)));
66129
- }
66130
- else {
66131
- s.push((zero + num));
66132
- }
66133
- s.push(MusicFontSymbol.TupletColon);
66134
- if (den > 10) {
66135
- s.push((zero + Math.floor(den / 10)));
66136
- s.push((zero + (den - 10)));
66137
- }
66138
- else {
66139
- s.push((zero + den));
66552
+ if (g) {
66553
+ let pos = this.y + g.y;
66554
+ switch (requestedPosition) {
66555
+ case NoteYPosition.Top:
66556
+ case NoteYPosition.TopWithStem:
66557
+ pos -= g.height / 2;
66558
+ break;
66559
+ case NoteYPosition.Center:
66560
+ break;
66561
+ case NoteYPosition.Bottom:
66562
+ case NoteYPosition.BottomWithStem:
66563
+ pos += g.height / 2;
66564
+ break;
66565
+ case NoteYPosition.StemUp:
66566
+ case NoteYPosition.StemDown:
66567
+ break;
66140
66568
  }
66569
+ return pos;
66141
66570
  }
66142
- // check if we need to paint simple footer
66143
- const offset = this.tupletOffset;
66144
- const size = this.tupletSize;
66145
- const shift = offset + size * 0.5;
66146
- const _ = ElementStyleHelper.beat(canvas, beatElement, h.beats[0]);
66147
- try {
66148
- const l = canvas.lineWidth;
66149
- canvas.lineWidth = this.smuflMetrics.tupletBracketThickness;
66150
- if (h.beats.length === 1 || !h.isFull) {
66151
- for (const beat of h.beats) {
66152
- const beamingHelper = this.helpers.getBeamingHelperForBeat(beat);
66153
- if (!beamingHelper) {
66154
- continue;
66155
- }
66156
- const direction = this.getTupletBeamDirection(beamingHelper);
66157
- const tupletX = this.getBeatX(beat, BeatXPosition.Stem);
66158
- let tupletY = this.calculateBeamYWithDirection(beamingHelper, tupletX, direction);
66159
- if (direction === BeamDirection.Down) {
66160
- tupletY += shift;
66571
+ return 0;
66572
+ }
66573
+ static majorKeySignatureOneValues = [
66574
+ // Flats
66575
+ 59, 66, 61, 68, 63, 58, 65,
66576
+ // natural
66577
+ 60,
66578
+ // sharps (where the value is true, a flat accidental is required for the notes)
66579
+ 67, 62, 69, 64, 71, 66, 61
66580
+ ];
66581
+ static minorKeySignatureOneValues = [
66582
+ // Flats
66583
+ 71, 66, 73, 68, 63, 70, 65,
66584
+ // natural
66585
+ 72,
66586
+ // sharps (where the value is true, a flat accidental is required for the notes)
66587
+ 67, 74, 69, 64, 71, 66, 73
66588
+ ];
66589
+ doLayout() {
66590
+ // create glyphs
66591
+ const sr = this.renderer;
66592
+ if (sr.shortestDuration < this.container.beat.duration) {
66593
+ sr.shortestDuration = this.container.beat.duration;
66594
+ }
66595
+ let octaveDots = 0;
66596
+ if (!this.container.beat.isEmpty) {
66597
+ const glyphY = sr.getLineY(0);
66598
+ let numberWithinOctave = '0';
66599
+ if (this.container.beat.notes.length > 0) {
66600
+ const note = this.container.beat.notes[0];
66601
+ const kst = this.renderer.bar.keySignatureType;
66602
+ const ks = this.renderer.bar.keySignature;
66603
+ const ksi = ks + 7;
66604
+ const oneNoteValues = kst === KeySignatureType.Minor
66605
+ ? NumberedBeatGlyph.minorKeySignatureOneValues
66606
+ : NumberedBeatGlyph.majorKeySignatureOneValues;
66607
+ const oneNoteValue = oneNoteValues[ksi];
66608
+ if (note.isDead) {
66609
+ numberWithinOctave = 'X';
66610
+ }
66611
+ else {
66612
+ const noteValue = note.displayValue - oneNoteValue;
66613
+ const index = noteValue < 0 ? ((noteValue % 12) + 12) % 12 : noteValue % 12;
66614
+ octaveDots = noteValue < 0 ? ((Math.abs(noteValue) + 12) / 12) | 0 : (noteValue / 12) | 0;
66615
+ if (noteValue < 0) {
66616
+ octaveDots *= -1;
66161
66617
  }
66162
- else {
66163
- tupletY -= shift;
66618
+ const stepList = ModelUtils.keySignatureIsSharp(ks) || ModelUtils.keySignatureIsNatural(ks)
66619
+ ? AccidentalHelper.flatNoteSteps
66620
+ : AccidentalHelper.sharpNoteSteps;
66621
+ let steps = stepList[index] + 1;
66622
+ const hasAccidental = ModelUtils.accidentalNotes[index];
66623
+ if (hasAccidental &&
66624
+ !this.container.preNotes.isNaturalizeAccidental) {
66625
+ if (ksi < 7) {
66626
+ steps++;
66627
+ }
66628
+ else {
66629
+ steps--;
66630
+ }
66164
66631
  }
66165
- canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY + size * 0.5, 1, s, true);
66632
+ numberWithinOctave = steps.toString();
66166
66633
  }
66167
66634
  }
66635
+ if (this.container.beat.deadSlapped) {
66636
+ const deadSlapped = new DeadSlappedBeatGlyph();
66637
+ deadSlapped.renderer = this.renderer;
66638
+ deadSlapped.doLayout();
66639
+ this.deadSlapped = deadSlapped;
66640
+ this.addEffect(deadSlapped);
66641
+ }
66168
66642
  else {
66169
- const firstBeat = h.beats[0];
66170
- const lastBeat = h.beats[h.beats.length - 1];
66171
- let firstNonRestBeat = null;
66172
- let lastNonRestBeat = null;
66173
- for (let i = 0; i < h.beats.length; i++) {
66174
- if (!h.beats[i].isRest) {
66175
- firstNonRestBeat = h.beats[i];
66176
- break;
66177
- }
66178
- }
66179
- for (let i = h.beats.length - 1; i >= 0; i--) {
66180
- if (!h.beats[i].isRest) {
66181
- lastNonRestBeat = h.beats[i];
66182
- break;
66183
- }
66184
- }
66185
- let isRestOnly = false;
66186
- if (!firstNonRestBeat) {
66187
- firstNonRestBeat = firstBeat;
66188
- isRestOnly = true;
66189
- }
66190
- if (!lastNonRestBeat) {
66191
- lastNonRestBeat = lastBeat;
66192
- }
66193
- //
66194
- // Calculate the overall area of the tuplet bracket
66195
- const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes);
66196
- const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes);
66197
- //
66198
- // calculate the y positions for our bracket
66199
- const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
66200
- const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
66201
- const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
66202
- let startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
66203
- let endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
66204
- if (isRestOnly) {
66205
- startY = Math.max(startY, endY);
66206
- endY = startY;
66207
- }
66208
- // align line centered in available space
66209
- if (direction === BeamDirection.Down) {
66210
- startY += shift;
66211
- endY += shift;
66212
- }
66213
- else {
66214
- startY -= shift;
66215
- endY -= shift;
66216
- }
66217
- //
66218
- // Calculate how many space the text will need
66219
- const sw = s.reduce((acc, sym) => acc + res.engravingSettings.glyphWidths.get(sym), 0);
66220
- const sp = res.engravingSettings.oneStaffSpace * 0.5;
66221
- //
66222
- // Calculate the offsets where to break the bracket
66223
- const middleX = (startX + endX) / 2;
66224
- const offset1X = middleX - sw / 2 - sp;
66225
- const offset2X = middleX + sw / 2 + sp;
66226
- const k = (endY - startY) / (endX - startX);
66227
- const d = startY - k * startX;
66228
- const offset1Y = k * offset1X + d;
66229
- const middleY = k * middleX + d;
66230
- const offset2Y = k * offset2X + d;
66231
- const angleStartY = direction === BeamDirection.Down ? startY - size * 0.5 : startY + size * 0.5;
66232
- const angleEndY = direction === BeamDirection.Down ? endY - size * 0.5 : endY + size * 0.5;
66233
- //
66234
- // draw the bracket
66235
- const pixelAlignment = canvas.lineWidth % 2 === 0 ? 0 : 0.5;
66236
- cx += pixelAlignment;
66237
- cy += pixelAlignment;
66238
- if (offset1X > startX) {
66239
- canvas.beginPath();
66240
- canvas.moveTo(cx + this.x + startX, cy + this.y + angleStartY);
66241
- if (bracketsAsArcs) {
66242
- canvas.quadraticCurveTo(cx + this.x + (offset1X + startX) / 2, cy + this.y + offset1Y, cx + this.x + offset1X, cy + this.y + offset1Y);
66243
- }
66244
- else {
66245
- canvas.lineTo(cx + this.x + startX, cy + this.y + startY);
66246
- canvas.lineTo(cx + this.x + offset1X, cy + this.y + offset1Y);
66247
- }
66248
- canvas.moveTo(cx + this.x + offset2X, cy + this.y + offset2Y);
66249
- if (bracketsAsArcs) {
66250
- canvas.quadraticCurveTo(cx + this.x + (endX + offset2X) / 2, cy + this.y + offset2Y, cx + this.x + endX, cy + this.y + angleEndY);
66251
- }
66252
- else {
66253
- canvas.lineTo(cx + this.x + endX, cy + this.y + endY);
66254
- canvas.lineTo(cx + this.x + endX, cy + this.y + angleEndY);
66255
- }
66256
- canvas.stroke();
66643
+ const isGrace = this.container.beat.graceType !== GraceType.None;
66644
+ const noteHeadGlyph = new NumberedNoteHeadGlyph(0, glyphY, numberWithinOctave, isGrace, this.container.beat, octaveDots);
66645
+ this.noteHeads = noteHeadGlyph;
66646
+ this.addNormal(noteHeadGlyph);
66647
+ }
66648
+ //
66649
+ // Note dots
66650
+ if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
66651
+ for (let i = 0; i < this.container.beat.dots; i++) {
66652
+ const dot = new AugmentationDotGlyph(0, glyphY);
66653
+ dot.renderer = this.renderer;
66654
+ this.addEffect(dot);
66257
66655
  }
66258
- //
66259
- // Draw the string
66260
- canvas.fillMusicFontSymbols(cx + this.x + middleX, cy + this.y + middleY + size * 0.5, 1, s, true);
66261
66656
  }
66262
- canvas.textAlign = oldAlign;
66263
- canvas.textBaseline = oldBaseLine;
66264
- canvas.lineWidth = l;
66265
66657
  }
66266
- finally {
66267
- _?.[Symbol.dispose]?.();
66658
+ super.doLayout();
66659
+ if (this.container.beat.isEmpty) {
66660
+ this.onTimeX = this.width / 2;
66268
66661
  }
66269
- }
66270
- paintBeams(cx, cy, canvas, flagsElement, beamsElement) {
66271
- for (const v of this.voiceContainer.voiceDrawOrder) {
66272
- for (const h of this.helpers.beamHelpers[v]) {
66273
- this.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
66274
- }
66662
+ else if (this.noteHeads) {
66663
+ this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
66275
66664
  }
66276
- }
66277
- drawBeamHelperAsFlags(h) {
66278
- return h.beats.length === 1;
66279
- }
66280
- hasFlag(beat) {
66281
- if (beat.isRest) {
66282
- return false;
66665
+ else if (this.deadSlapped) {
66666
+ this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
66283
66667
  }
66284
- const helper = this.helpers.getBeamingHelperForBeat(beat);
66285
- if (helper) {
66286
- return helper.hasFlag(this.drawBeamHelperAsFlags(helper), beat);
66287
- }
66288
- return BeamingHelper.beatHasFlag(beat);
66668
+ this.middleX = this.onTimeX;
66669
+ this.stemX = this.middleX;
66289
66670
  }
66290
- hasStem(beat) {
66291
- if (beat.isRest) {
66292
- return false;
66293
- }
66294
- const helper = this.helpers.getBeamingHelperForBeat(beat);
66295
- if (helper) {
66296
- return helper.hasStem(this.drawBeamHelperAsFlags(helper), beat);
66671
+ }
66672
+
66673
+ /**
66674
+ * @internal
66675
+ */
66676
+ class TabTieGlyph extends NoteTieGlyph {
66677
+ calculateTieDirection() {
66678
+ if (this.isLeftHandTap) {
66679
+ return BeamDirection.Up;
66297
66680
  }
66298
- return BeamingHelper.beatHasStem(beat);
66681
+ return TabTieGlyph.getBeamDirectionForNote(this.startNote);
66299
66682
  }
66300
- paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
66301
- canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
66302
- if (!h.isRestBeamHelper) {
66303
- if (this.drawBeamHelperAsFlags(h)) {
66304
- this.paintFlag(cx, cy, canvas, h, flagsElement);
66305
- }
66306
- else {
66307
- this.paintBar(cx, cy, canvas, h, beamsElement);
66308
- }
66309
- }
66683
+ static getBeamDirectionForNote(note) {
66684
+ return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
66310
66685
  }
66311
- shouldPaintFlag(beat) {
66312
- // no flags for bend grace beats
66313
- if (beat.graceType === GraceType.BendGrace) {
66686
+ }
66687
+
66688
+ /**
66689
+ * @internal
66690
+ */
66691
+ class TabSlurGlyph extends TabTieGlyph {
66692
+ _forSlide;
66693
+ constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
66694
+ super(slurEffectId, startNote, endNote, forEnd);
66695
+ this._forSlide = forSlide;
66696
+ }
66697
+ getTieHeight(startX, _startY, endX, _endY) {
66698
+ return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
66699
+ }
66700
+ tryExpand(startNote, endNote, forSlide, forEnd) {
66701
+ // same type required
66702
+ if (this._forSlide !== forSlide) {
66314
66703
  return false;
66315
66704
  }
66316
- if (beat.deadSlapped) {
66705
+ // same start and endbeat
66706
+ if (this.startNote.beat.id !== startNote.beat.id) {
66317
66707
  return false;
66318
66708
  }
66319
- // no flags for any grace notes on songbook mode
66320
- if (beat.graceType !== GraceType.None && this.settings.notation.notationMode === NotationMode.SongBook) {
66709
+ if (this.endNote.beat.id !== endNote.beat.id) {
66321
66710
  return false;
66322
66711
  }
66323
- // only flags for durations with stems
66324
- if (beat.duration === Duration.Whole ||
66325
- beat.duration === Duration.DoubleWhole ||
66326
- beat.duration === Duration.QuadrupleWhole) {
66712
+ const isForEnd = this.renderer === this.lookupEndBeatRenderer();
66713
+ if (isForEnd !== forEnd) {
66327
66714
  return false;
66328
66715
  }
66329
- return true;
66330
- }
66331
- paintFlag(cx, cy, canvas, h, flagsElement) {
66332
- for (const beat of h.beats) {
66333
- if (!this.shouldPaintFlag(beat)) {
66334
- continue;
66335
- }
66336
- const isGrace = beat.graceType !== GraceType.None;
66337
- //
66338
- // draw line
66339
- //
66340
- const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66341
- const direction = this.getBeamDirection(h);
66342
- const topY = cy + this.y + this.getFlagTopY(beat, direction);
66343
- const bottomY = cy + this.y + this.getFlagBottomY(beat, direction);
66344
- let flagY = 0;
66345
- if (direction === BeamDirection.Down) {
66346
- flagY = bottomY;
66347
- }
66348
- else {
66349
- flagY = topY;
66350
- }
66351
- if (!h.hasStem(true, beat)) {
66352
- continue;
66353
- }
66354
- this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, topY, bottomY, canvas);
66355
- const _ = ElementStyleHelper.beat(canvas, flagsElement, beat);
66356
- try {
66357
- let flagWidth = 0;
66358
- //
66359
- // Draw flag
66360
- //
66361
- if (h.hasFlag(true, beat)) {
66362
- const glyph = new FlagGlyph(cx + this.x + beatLineX, flagY, beat.duration, direction, isGrace);
66363
- glyph.renderer = this;
66364
- glyph.doLayout();
66365
- glyph.paint(0, 0, canvas);
66366
- flagWidth = glyph.width / 2;
66716
+ // same draw direction
66717
+ if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
66718
+ return false;
66719
+ }
66720
+ // if we can expand, expand in correct direction
66721
+ switch (this.tieDirection) {
66722
+ case BeamDirection.Up:
66723
+ if (startNote.realValue > this.startNote.realValue) {
66724
+ this.startNote = startNote;
66367
66725
  }
66368
- if (beat.graceType === GraceType.BeforeBeat) {
66369
- if (direction === BeamDirection.Down) {
66370
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, (topY + bottomY - this.smuflMetrics.glyphHeights.get(MusicFontSymbol.GraceNoteSlashStemDown)) /
66371
- 2, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemDown, true);
66372
- }
66373
- else {
66374
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, (topY + bottomY + this.smuflMetrics.glyphHeights.get(MusicFontSymbol.GraceNoteSlashStemUp)) /
66375
- 2, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemUp, true);
66376
- }
66726
+ if (endNote.realValue > this.endNote.realValue) {
66727
+ this.endNote = endNote;
66377
66728
  }
66378
- }
66379
- finally {
66380
- _?.[Symbol.dispose]?.();
66381
- }
66382
- }
66383
- }
66384
- getFlagStemSize(duration, forceMinStem = false) {
66385
- let size = 0;
66386
- switch (duration) {
66387
- case Duration.QuadrupleWhole:
66388
- case Duration.Half:
66389
- case Duration.Quarter:
66390
- case Duration.Eighth:
66391
- case Duration.Sixteenth:
66392
- case Duration.ThirtySecond:
66393
- case Duration.SixtyFourth:
66394
- case Duration.OneHundredTwentyEighth:
66395
- case Duration.TwoHundredFiftySixth:
66396
- size = this.smuflMetrics.standardStemLength + this.smuflMetrics.stemFlagOffsets.get(duration);
66397
66729
  break;
66398
- default:
66399
- size = forceMinStem ? this.smuflMetrics.standardStemLength : 0;
66730
+ case BeamDirection.Down:
66731
+ if (startNote.realValue < this.startNote.realValue) {
66732
+ this.startNote = startNote;
66733
+ }
66734
+ if (endNote.realValue < this.endNote.realValue) {
66735
+ this.endNote = endNote;
66736
+ }
66400
66737
  break;
66401
66738
  }
66402
- return size;
66403
- }
66404
- recreatePreBeatGlyphs() {
66405
- this._startSpacing = false;
66406
- super.recreatePreBeatGlyphs();
66407
- }
66408
- calculateBeamY(h, x) {
66409
- return this.calculateBeamYWithDirection(h, x, this.getBeamDirection(h));
66410
- }
66411
- createPreBeatGlyphs() {
66412
- super.createPreBeatGlyphs();
66413
- this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
66414
- this.createLinePreBeatGlyphs();
66415
- this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
66739
+ return true;
66416
66740
  }
66417
- createPostBeatGlyphs() {
66418
- super.createPostBeatGlyphs();
66419
- const lastBar = this.lastBar;
66420
- this.addPostBeatGlyph(new BarLineGlyph(true, this.bar.staff.track.score.stylesheet.extendBarLines));
66421
- if (lastBar.masterBar.isRepeatEnd && lastBar.masterBar.repeatCount > 2) {
66422
- this.addPostBeatGlyph(new RepeatCountGlyph(0, this.getLineHeight(-0.5), this.bar.masterBar.repeatCount));
66423
- }
66741
+ }
66742
+
66743
+ /**
66744
+ * @internal
66745
+ */
66746
+ class NumberedSlurGlyph extends TabSlurGlyph {
66747
+ calculateTieDirection() {
66748
+ return BeamDirection.Up;
66424
66749
  }
66425
- paintBar(cx, cy, canvas, h, beamsElement) {
66426
- const direction = this.getBeamDirection(h);
66427
- const isGrace = h.graceType !== GraceType.None;
66428
- const scaleMod = isGrace ? EngravingSettings.GraceScale : 1;
66429
- let barSpacing = (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66430
- let barSize = this.smuflMetrics.beamThickness * scaleMod;
66431
- if (direction === BeamDirection.Down) {
66432
- barSpacing = -barSpacing;
66433
- barSize = -barSize;
66434
- }
66435
- for (let i = 0, j = h.beats.length; i < j; i++) {
66436
- const beat = h.beats[i];
66437
- if (beat.deadSlapped) {
66438
- continue;
66439
- }
66440
- const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
66441
- const y1 = cy + this.y + this.getBarLineStart(beat, direction);
66442
- // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
66443
- // when combining stems and beams on sub-pixel level
66444
- const y2 = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
66445
- if (y1 < y2) {
66446
- this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, y1, y2, canvas);
66447
- }
66448
- else {
66449
- this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, y2, y1, canvas);
66450
- }
66451
- const _ = ElementStyleHelper.beat(canvas, beamsElement, beat);
66452
- try {
66453
- const brokenBarOffset = this.smuflMetrics.brokenBeamWidth * scaleMod;
66454
- const barCount = ModelUtils.getIndex(beat.duration) - 2;
66455
- const barStart = cy + this.y;
66456
- for (let barIndex = 0; barIndex < barCount; barIndex++) {
66457
- let barStartX = 0;
66458
- let barEndX = 0;
66459
- let barStartY = 0;
66460
- let barEndY = 0;
66461
- const barY = barStart + barIndex * barSpacing;
66462
- //
66463
- // Bar to Next?
66464
- //
66465
- if (i < h.beats.length - 1) {
66466
- const isFullBarJoin = BeamingHelper.isFullBarJoin(beat, h.beats[i + 1], barIndex);
66467
- // force two broken bars on secondary (last) beam?
66468
- if (barIndex === barCount - 1 &&
66469
- isFullBarJoin &&
66470
- beat.beamingMode === BeatBeamingMode.ForceSplitOnSecondaryToNext) {
66471
- // start part
66472
- barStartX = beatLineX;
66473
- barEndX = barStartX + brokenBarOffset;
66474
- barStartY = barY + this.calculateBeamY(h, barStartX);
66475
- barEndY = barY + this.calculateBeamY(h, barEndX);
66476
- LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66477
- // end part
66478
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66479
- barStartX = barEndX - brokenBarOffset;
66480
- barStartY = barY + this.calculateBeamY(h, barStartX);
66481
- barEndY = barY + this.calculateBeamY(h, barEndX);
66482
- LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66483
- }
66484
- else {
66485
- if (isFullBarJoin) {
66486
- // full bar?
66487
- barStartX = beatLineX;
66488
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
66489
- }
66490
- else if (i === 0 || !BeamingHelper.isFullBarJoin(h.beats[i - 1], beat, barIndex)) {
66491
- barStartX = beatLineX;
66492
- barEndX = barStartX + brokenBarOffset;
66493
- }
66494
- else {
66495
- continue;
66496
- }
66497
- barStartY = barY + this.calculateBeamY(h, barStartX);
66498
- barEndY = barY + this.calculateBeamY(h, barEndX);
66499
- // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
66500
- // when combining stems and beams on sub-pixel level
66501
- if (barIndex === 0) {
66502
- barStartY = barStartY | 0;
66503
- barEndY = barEndY | 0;
66504
- }
66505
- LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66506
- }
66507
- }
66508
- else if (i > 0 && !BeamingHelper.isFullBarJoin(beat, h.beats[i - 1], barIndex)) {
66509
- barStartX = beatLineX - brokenBarOffset;
66510
- barEndX = beatLineX;
66511
- barEndX = beatLineX;
66512
- barStartY = barY + this.calculateBeamY(h, barStartX);
66513
- barEndY = barY + this.calculateBeamY(h, barEndX);
66514
- LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
66515
- }
66516
- }
66517
- }
66518
- finally {
66519
- _?.[Symbol.dispose]?.();
66520
- }
66750
+ }
66751
+
66752
+ /**
66753
+ * @internal
66754
+ */
66755
+ class NumberedBeatContainerGlyph extends BeatContainerGlyph {
66756
+ _slurs = new Map();
66757
+ _effectSlurs = [];
66758
+ _dashes;
66759
+ hasAdditionalNumbers = false;
66760
+ *iterateAdditionalNumbers() {
66761
+ const dashes = this._dashes;
66762
+ if (!dashes) {
66763
+ return;
66521
66764
  }
66522
- if (h.graceType === GraceType.BeforeBeat) {
66523
- const beatLineX = this.getBeatX(h.beats[0], BeatXPosition.Stem);
66524
- const flagWidth = this.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
66525
- let slashY = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
66526
- slashY += barSize + barSpacing;
66527
- if (direction === BeamDirection.Down) {
66528
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, slashY, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemDown, true);
66529
- }
66530
- else {
66531
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, slashY, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemUp, true);
66765
+ for (const d of dashes) {
66766
+ if (d instanceof NumberedNoteBeatContainerGlyphBase) {
66767
+ yield d;
66532
66768
  }
66533
66769
  }
66534
66770
  }
66535
- static paintSingleBar(canvas, x1, y1, x2, y2, size) {
66536
- canvas.beginPath();
66537
- canvas.moveTo(x1, y1);
66538
- canvas.lineTo(x2, y2);
66539
- canvas.lineTo(x2, y2 + size);
66540
- canvas.lineTo(x1, y1 + size);
66541
- canvas.closePath();
66542
- canvas.fill();
66771
+ constructor(beat) {
66772
+ super(beat);
66773
+ this.preNotes = new NumberedBeatPreNotesGlyph();
66774
+ this.onNotes = new NumberedBeatGlyph();
66543
66775
  }
66544
- calculateBeamingOverflows(rendererTop, rendererBottom) {
66545
- let maxNoteY = 0;
66546
- let minNoteY = 0;
66547
- const noteOverflowPadding = this.getLineHeight(0.5);
66548
- for (const v of this.helpers.beamHelpers) {
66549
- for (const h of v) {
66550
- if (h.isRestBeamHelper) ;
66551
- else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
66552
- if (h.direction === BeamDirection.Up) {
66553
- let topY = this.getFlagTopY(h.beats[0], h.direction);
66554
- if (h.hasTuplet) {
66555
- topY -= this.tupletSize + this.tupletOffset;
66556
- }
66557
- if (topY < maxNoteY) {
66558
- maxNoteY = topY;
66559
- }
66560
- }
66561
- else {
66562
- let bottomY = this.getFlagBottomY(h.beats[0], h.direction);
66563
- if (h.hasTuplet) {
66564
- bottomY += this.tupletSize + this.tupletOffset;
66565
- }
66566
- if (bottomY > minNoteY) {
66567
- minNoteY = bottomY;
66568
- }
66569
- }
66570
- }
66571
- else {
66572
- this.ensureBeamDrawingInfo(h, h.direction);
66573
- const drawingInfo = h.drawingInfos.get(h.direction);
66574
- if (h.direction === BeamDirection.Up) {
66575
- let topY = Math.min(drawingInfo.startY, drawingInfo.endY);
66576
- if (h.hasTuplet) {
66577
- topY -= this.tupletSize + this.tupletOffset;
66578
- }
66579
- if (topY < maxNoteY) {
66580
- maxNoteY = topY;
66581
- }
66582
- const bottomY = this.getBarLineStart(h.beatOfLowestNote, h.direction) + noteOverflowPadding;
66583
- if (bottomY > minNoteY) {
66584
- minNoteY = bottomY;
66585
- }
66586
- }
66587
- else {
66588
- let bottomY = Math.max(drawingInfo.startY, drawingInfo.endY);
66589
- if (h.hasTuplet) {
66590
- bottomY += this.tupletSize + this.tupletOffset;
66591
- }
66592
- if (bottomY > minNoteY) {
66593
- minNoteY = bottomY;
66594
- }
66595
- const topY = this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
66596
- if (topY < maxNoteY) {
66597
- maxNoteY = topY;
66598
- }
66599
- }
66600
- }
66601
- }
66776
+ addDash(dash) {
66777
+ let dashes = this._dashes;
66778
+ if (!dashes) {
66779
+ dashes = [];
66780
+ this._dashes = dashes;
66602
66781
  }
66603
- if (maxNoteY < rendererTop) {
66604
- this.registerOverflowTop(Math.abs(maxNoteY));
66782
+ dashes.push(dash);
66783
+ }
66784
+ addNotes(dash) {
66785
+ let dashes = this._dashes;
66786
+ if (!dashes) {
66787
+ dashes = [];
66788
+ this._dashes = dashes;
66605
66789
  }
66606
- if (minNoteY > rendererBottom) {
66607
- this.registerOverflowBottom(Math.abs(minNoteY) - rendererBottom);
66790
+ dashes.push(dash);
66791
+ this.hasAdditionalNumbers = true;
66792
+ }
66793
+ doLayout() {
66794
+ this._slurs.clear();
66795
+ this._effectSlurs = [];
66796
+ super.doLayout();
66797
+ }
66798
+ buildBoundingsLookup(barBounds, cx, cy) {
66799
+ super.buildBoundingsLookup(barBounds, cx, cy);
66800
+ // extend bounds to include dashes
66801
+ const dashes = this._dashes;
66802
+ if (dashes) {
66803
+ const beatBounds = barBounds.beats[barBounds.beats.length - 1];
66804
+ const lastDash = dashes[dashes.length - 1];
66805
+ const visualEndX = lastDash.x + lastDash.contentWidth;
66806
+ beatBounds.visualBounds.w = visualEndX - beatBounds.visualBounds.x;
66807
+ const realEnd = lastDash.x + lastDash.width;
66808
+ beatBounds.realBounds.w = realEnd - beatBounds.realBounds.x;
66608
66809
  }
66609
66810
  }
66610
- ensureBeamDrawingInfo(h, direction) {
66611
- if (h.drawingInfos.has(direction)) {
66811
+ createTies(n) {
66812
+ // create a tie if any effect requires it
66813
+ if (!n.isVisible) {
66612
66814
  return;
66613
66815
  }
66614
- const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
66615
- const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
66616
- const drawingInfo = new BeamingHelperDrawInfo();
66617
- h.drawingInfos.set(direction, drawingInfo);
66618
- // the beaming logic works like this:
66619
- // 1. we take the first and last note, add the stem, and put a diagnal line between them.
66620
- // 2. the height of the diagonal line must not exceed a max height,
66621
- // - if this is the case, the line on the more distant note just gets longer
66622
- // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
66623
- const firstBeat = h.beats[0];
66624
- const lastBeat = h.beats[h.beats.length - 1];
66625
- const isRest = h.isRestBeamHelper;
66626
- // 1. put direct diagonal line.
66627
- drawingInfo.startBeat = firstBeat;
66628
- drawingInfo.startX = this.getBeatX(firstBeat, BeatXPosition.Stem);
66629
- drawingInfo.startY =
66630
- direction === BeamDirection.Up
66631
- ? this.getFlagTopY(firstBeat, direction)
66632
- : this.getFlagBottomY(firstBeat, direction);
66633
- drawingInfo.endBeat = lastBeat;
66634
- drawingInfo.endX = this.getBeatX(lastBeat, BeatXPosition.Stem);
66635
- drawingInfo.endY =
66636
- direction === BeamDirection.Up
66637
- ? this.getFlagTopY(lastBeat, direction)
66638
- : this.getFlagBottomY(lastBeat, direction);
66639
- // 2. ensure max slope
66640
- // we use the min/max notes to place the beam along their real position
66641
- // we only want a maximum of 10 offset for their gradient
66642
- const maxSlope = this.smuflMetrics.oneStaffSpace;
66643
- if (direction === BeamDirection.Down &&
66644
- drawingInfo.startY > drawingInfo.endY &&
66645
- drawingInfo.startY - drawingInfo.endY > maxSlope) {
66646
- drawingInfo.endY = drawingInfo.startY - maxSlope;
66647
- }
66648
- if (direction === BeamDirection.Down &&
66649
- drawingInfo.endY > drawingInfo.startY &&
66650
- drawingInfo.endY - drawingInfo.startY > maxSlope) {
66651
- drawingInfo.startY = drawingInfo.endY - maxSlope;
66816
+ if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
66817
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
66818
+ this.addTie(tie);
66819
+ this._slurs.set(tie.slurEffectId, tie);
66652
66820
  }
66653
- if (direction === BeamDirection.Up &&
66654
- drawingInfo.startY < drawingInfo.endY &&
66655
- drawingInfo.endY - drawingInfo.startY > maxSlope) {
66656
- drawingInfo.endY = drawingInfo.startY + maxSlope;
66821
+ if (n.isTieDestination) {
66822
+ const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
66823
+ this.addTie(tie);
66657
66824
  }
66658
- if (direction === BeamDirection.Up &&
66659
- drawingInfo.endY < drawingInfo.startY &&
66660
- drawingInfo.startY - drawingInfo.endY > maxSlope) {
66661
- drawingInfo.startY = drawingInfo.endY + maxSlope;
66825
+ if (n.isLeftHandTapped &&
66826
+ !n.isHammerPullDestination &&
66827
+ !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
66828
+ const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
66829
+ this.addTie(tapSlur);
66830
+ this._slurs.set(tapSlur.slurEffectId, tapSlur);
66662
66831
  }
66663
- // 3. adjust beam drawing order
66664
- // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
66665
- // here we shift accordingly
66666
- let barDrawingShift = 0;
66667
- if (barCount > 2 && !isRest) {
66668
- const beamSpacing = this.smuflMetrics.beamSpacing * scale;
66669
- const beamThickness = this.smuflMetrics.beamThickness * scale;
66670
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
66671
- if (direction === BeamDirection.Up) {
66672
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
66673
- const barTopY = bottomBarY - totalBarsHeight;
66674
- const diff = drawingInfo.startY - barTopY;
66675
- if (diff > 0) {
66676
- barDrawingShift = diff * -1;
66677
- drawingInfo.startY -= diff;
66678
- drawingInfo.endY -= diff;
66832
+ // start effect slur on first beat
66833
+ if (n.isEffectSlurOrigin && n.effectSlurDestination) {
66834
+ let expanded = false;
66835
+ for (const slur of this._effectSlurs) {
66836
+ if (slur.tryExpand(n, n.effectSlurDestination, false, false)) {
66837
+ expanded = true;
66838
+ break;
66679
66839
  }
66680
66840
  }
66681
- else {
66682
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
66683
- const barBottomY = topBarY + totalBarsHeight;
66684
- const diff = barBottomY - drawingInfo.startY;
66685
- if (diff > 0) {
66686
- barDrawingShift = diff;
66687
- drawingInfo.startY += diff;
66688
- drawingInfo.endY += diff;
66689
- }
66841
+ if (!expanded) {
66842
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
66843
+ this._effectSlurs.push(effectSlur);
66844
+ this.addTie(effectSlur);
66845
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66846
+ this._slurs.set('numbered.slur.effect', effectSlur);
66690
66847
  }
66691
66848
  }
66692
- // 4. let middle elements shift up/down
66693
- if (h.beats.length > 1) {
66694
- // check if highest note shifts bar up or down
66695
- if (direction === BeamDirection.Up) {
66696
- const yNeededForHighestNote = barDrawingShift + this.getFlagTopY(h.beatOfHighestNote, direction);
66697
- const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfHighestNote, BeatXPosition.Stem));
66698
- const diff = yGivenByCurrentValues - yNeededForHighestNote;
66699
- if (diff > 0) {
66700
- drawingInfo.startY -= diff;
66701
- drawingInfo.endY -= diff;
66702
- }
66703
- }
66704
- else {
66705
- const yNeededForLowestNote = barDrawingShift + this.getFlagBottomY(h.beatOfLowestNote, direction);
66706
- const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfLowestNote, BeatXPosition.Stem));
66707
- const diff = yNeededForLowestNote - yGivenByCurrentValues;
66708
- if (diff > 0) {
66709
- drawingInfo.startY += diff;
66710
- drawingInfo.endY += diff;
66849
+ // end effect slur on last beat
66850
+ if (n.isEffectSlurDestination && n.effectSlurOrigin) {
66851
+ let expanded = false;
66852
+ for (const slur of this._effectSlurs) {
66853
+ if (slur.tryExpand(n.effectSlurOrigin, n, false, true)) {
66854
+ expanded = true;
66855
+ break;
66711
66856
  }
66712
66857
  }
66713
- // check if rest shifts bar up or down
66714
- let barSpacing = 0;
66715
- if (h.restBeats.length > 0) {
66716
- // space needed for the bars, rests need to be below them
66717
- const scaleMod = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
66718
- barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
66719
- }
66720
- for (const b of h.restBeats) {
66721
- // rest beats which are "under" the beam
66722
- if (b.isRest && b.index < h.beats[h.beats.length - 1].index) {
66723
- if (direction === BeamDirection.Up) {
66724
- const yNeededForRest = this.getBeatContainer(b).getBoundingBoxTop() - barSpacing;
66725
- const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66726
- const diff = yGivenByCurrentValues - yNeededForRest;
66727
- if (diff > 0) {
66728
- drawingInfo.startY -= diff;
66729
- drawingInfo.endY -= diff;
66730
- }
66731
- }
66732
- else if (direction === BeamDirection.Down) {
66733
- const yNeededForRest = this.getBeatContainer(b).getBoundingBoxBottom() + barSpacing;
66734
- const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66735
- const diff = yNeededForRest - yGivenByCurrentValues;
66736
- if (diff > 0) {
66737
- drawingInfo.startY += diff;
66738
- drawingInfo.endY += diff;
66739
- }
66740
- }
66741
- }
66742
- }
66743
- // check if slash shifts bar up or down
66744
- if (h.slashBeats.length > 0) {
66745
- for (const b of h.slashBeats) {
66746
- const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
66747
- const yNeededForSlash = h.direction === BeamDirection.Up
66748
- ? this.getFlagTopY(b, h.direction)
66749
- : this.getFlagBottomY(b, h.direction);
66750
- const diff = yNeededForSlash - yGivenByCurrentValues;
66751
- if (diff > 0) {
66752
- drawingInfo.startY += diff;
66753
- drawingInfo.endY += diff;
66754
- }
66755
- }
66858
+ if (!expanded) {
66859
+ const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
66860
+ this._effectSlurs.push(effectSlur);
66861
+ this.addTie(effectSlur);
66862
+ this._slurs.set(effectSlur.slurEffectId, effectSlur);
66863
+ this._slurs.set('numbered.slur.effect', effectSlur);
66756
66864
  }
66757
66865
  }
66758
66866
  }
66759
- getMinLineOfBeat(_beat) {
66760
- return 0;
66761
- }
66762
- getMaxLineOfBeat(_beat) {
66763
- return 0;
66764
- }
66765
- }
66766
-
66767
- /**
66768
- * @internal
66769
- */
66770
- class NumberedTieGlyph extends NoteTieGlyph {
66771
- shouldDrawBendSlur() {
66772
- return (this.renderer.settings.notation.extendBendArrowsOnTiedNotes &&
66773
- !!this.startNote.bendOrigin &&
66774
- this.startNote.isTieOrigin);
66775
- }
66776
- calculateTieDirection() {
66777
- return BeamDirection.Up;
66778
- }
66779
66867
  }
66780
66868
 
66781
66869
  /**
66782
66870
  * @internal
66783
66871
  */
66784
- class AccidentalGlyph extends MusicFontGlyph {
66785
- constructor(x, y, accidentalType, scale) {
66786
- super(x, y, scale, AccidentalGlyph.getMusicSymbol(accidentalType));
66872
+ class NumberedNoteBeatContainerGlyphBase extends NumberedBeatContainerGlyph {
66873
+ _absoluteDisplayStart;
66874
+ _displayDuration;
66875
+ constructor(beat, absoluteDisplayStart, displayDuration) {
66876
+ super(beat);
66877
+ this._absoluteDisplayStart = absoluteDisplayStart;
66878
+ this._displayDuration = displayDuration;
66879
+ this.preNotes.skipLayout = true;
66880
+ this.barCount = NumberedNoteBeatContainerGlyphBase._ticksToBarCount(displayDuration);
66787
66881
  }
66788
- static getMusicSymbol(accidentalType) {
66789
- switch (accidentalType) {
66790
- case AccidentalType.Natural:
66791
- return MusicFontSymbol.AccidentalNatural;
66792
- case AccidentalType.Sharp:
66793
- return MusicFontSymbol.AccidentalSharp;
66794
- case AccidentalType.Flat:
66795
- return MusicFontSymbol.AccidentalFlat;
66796
- case AccidentalType.NaturalQuarterNoteUp:
66797
- return MusicFontSymbol.AccidentalQuarterToneSharpNaturalArrowUp;
66798
- case AccidentalType.SharpQuarterNoteUp:
66799
- return MusicFontSymbol.AccidentalThreeQuarterTonesSharpArrowUp;
66800
- case AccidentalType.FlatQuarterNoteUp:
66801
- return MusicFontSymbol.AccidentalQuarterToneFlatArrowUp;
66802
- case AccidentalType.DoubleSharp:
66803
- return MusicFontSymbol.AccidentalDoubleSharp;
66804
- case AccidentalType.DoubleFlat:
66805
- return MusicFontSymbol.AccidentalDoubleFlat;
66882
+ static _ticksToBarCount(displayDuration) {
66883
+ // we know that displayDuration < MidiUtils.QuarterTime, otherwise this glyph is not created
66884
+ if (displayDuration >= MidiUtils.toTicks(Duration.Eighth)) {
66885
+ return 1;
66806
66886
  }
66807
- return MusicFontSymbol.None;
66808
- }
66809
- }
66810
-
66811
- /**
66812
- * @internal
66813
- */
66814
- class AccidentalColumnInfo {
66815
- x = 0;
66816
- y = -3e3;
66817
- width = 0;
66818
- }
66819
- /**
66820
- * @internal
66821
- */
66822
- class AccidentalGroupGlyph extends GlyphGroup {
66823
- constructor() {
66824
- super(0, 0);
66825
- }
66826
- doLayout() {
66827
- if (!this.glyphs || this.glyphs.length === 0) {
66828
- this.width = 0;
66829
- return;
66887
+ else if (displayDuration >= MidiUtils.toTicks(Duration.Sixteenth)) {
66888
+ return 2;
66830
66889
  }
66831
- //
66832
- // Determine Columns for accidentals
66833
- //
66834
- this.glyphs.sort((a, b) => {
66835
- if (a.y < b.y) {
66836
- return -1;
66837
- }
66838
- if (a.y > b.y) {
66839
- return 1;
66840
- }
66841
- return 0;
66842
- });
66843
- // defines the reserved y position of the columns
66844
- const columns = [];
66845
- columns.push(new AccidentalColumnInfo());
66846
- for (let i = 0, j = this.glyphs.length; i < j; i++) {
66847
- const g = this.glyphs[i];
66848
- g.renderer = this.renderer;
66849
- g.doLayout();
66850
- // find column where glyph fits into
66851
- // as long the glyph does not fit into the current column
66852
- let gColumn = 0;
66853
- while (columns[gColumn].y > g.y) {
66854
- // move to next column
66855
- gColumn++;
66856
- // and create the new column if needed
66857
- if (gColumn === columns.length) {
66858
- columns.push(new AccidentalColumnInfo());
66859
- }
66860
- }
66861
- // temporary save column as X
66862
- g.x = gColumn;
66863
- columns[gColumn].y = g.y + g.height;
66864
- if (columns[gColumn].width < g.width) {
66865
- columns[gColumn].width = g.width;
66866
- }
66890
+ else if (displayDuration >= MidiUtils.toTicks(Duration.ThirtySecond)) {
66891
+ return 3;
66867
66892
  }
66868
- //
66869
- // Place accidentals in columns
66870
- //
66871
- this.width = 0;
66872
- const padding = this.renderer.smuflMetrics.accidentalPadding;
66873
- for (const column of columns) {
66874
- this.width += column.width + padding;
66875
- column.x = this.width;
66893
+ else if (displayDuration >= MidiUtils.toTicks(Duration.SixtyFourth)) {
66894
+ return 4;
66876
66895
  }
66877
- for (let i = 0, j = this.glyphs.length; i < j; i++) {
66878
- const g = this.glyphs[i];
66879
- const column = columns[g.x];
66880
- g.x = this.width - column.x;
66896
+ else if (displayDuration >= MidiUtils.toTicks(Duration.OneHundredTwentyEighth)) {
66897
+ return 5;
66881
66898
  }
66899
+ else if (displayDuration >= MidiUtils.toTicks(Duration.TwoHundredFiftySixth)) {
66900
+ return 6;
66901
+ }
66902
+ return 0;
66882
66903
  }
66883
- }
66884
-
66885
- /**
66886
- * @internal
66887
- */
66888
- class AugmentationDotGlyph extends MusicFontGlyph {
66889
- constructor(x, y) {
66890
- super(x, y, 1, MusicFontSymbol.AugmentationDot);
66904
+ barCount;
66905
+ get beatId() {
66906
+ return -1;
66891
66907
  }
66892
- doLayout() {
66893
- super.doLayout();
66894
- this.offsetX = this.width / 2;
66895
- this.width *= 1.5;
66908
+ get contentWidth() {
66909
+ return this.onNotes.width;
66896
66910
  }
66897
- }
66898
-
66899
- /**
66900
- * @internal
66901
- */
66902
- class BeatGlyphBase extends GlyphGroup {
66903
- _effectGlyphs = [];
66904
- _normalGlyphs = [];
66905
- container;
66906
- computedWidth = 0;
66907
- constructor() {
66908
- super(0, 0);
66911
+ get absoluteDisplayStart() {
66912
+ return this._absoluteDisplayStart;
66909
66913
  }
66910
- doLayout() {
66911
- // left to right layout
66912
- let w = 0;
66913
- if (this.glyphs) {
66914
- for (let i = 0, j = this.glyphs.length; i < j; i++) {
66915
- const g = this.glyphs[i];
66916
- g.x = w;
66917
- g.renderer = this.renderer;
66918
- g.doLayout();
66919
- w += g.width;
66920
- }
66921
- }
66922
- this.width = w;
66923
- this.computedWidth = w;
66914
+ get displayDuration() {
66915
+ return this._displayDuration;
66924
66916
  }
66925
- noteLoop(action) {
66926
- for (let i = this.container.beat.notes.length - 1; i >= 0; i--) {
66927
- action(this.container.beat.notes[i]);
66928
- }
66917
+ get graceType() {
66918
+ return GraceType.None;
66929
66919
  }
66930
- addEffect(g) {
66931
- super.addGlyph(g);
66932
- this._effectGlyphs.push(g);
66920
+ get graceIndex() {
66921
+ return 0;
66933
66922
  }
66934
- addNormal(g) {
66935
- super.addGlyph(g);
66936
- this._normalGlyphs.push(g);
66923
+ get graceGroup() {
66924
+ return null;
66937
66925
  }
66938
- get effectElement() {
66939
- return undefined;
66926
+ get isFirstOfTupletGroup() {
66927
+ return false;
66940
66928
  }
66941
- paint(cx, cy, canvas) {
66942
- this._paintEffects(cx, cy, canvas);
66943
- this._paintNormal(cx, cy, canvas);
66929
+ get tupletGroup() {
66930
+ return null;
66944
66931
  }
66945
- _paintNormal(cx, cy, canvas) {
66946
- for (const g of this._normalGlyphs) {
66947
- g.paint(cx + this.x, cy + this.y, canvas);
66948
- }
66932
+ get isLastOfVoice() {
66933
+ return false;
66949
66934
  }
66950
- _paintEffects(cx, cy, canvas) {
66951
- const _ = this.effectElement
66952
- ? ElementStyleHelper.beat(canvas, this.effectElement, this.container.beat)
66953
- : undefined;
66954
- try {
66955
- for (const g of this._effectGlyphs) {
66956
- g.paint(cx + this.x, cy + this.y, canvas);
66957
- }
66958
- }
66959
- finally {
66960
- _?.[Symbol.dispose]?.();
66961
- }
66935
+ buildBoundingsLookup(_barBounds, _cx, _cy) {
66962
66936
  }
66963
66937
  }
66964
-
66965
- /**
66966
- * @internal
66967
- */
66968
- class BeatOnNoteGlyphBase extends BeatGlyphBase {
66969
- onTimeX = 0;
66970
- middleX = 0;
66971
- stemX = 0;
66972
- }
66973
-
66974
66938
  /**
66975
66939
  * @internal
66976
66940
  */
66977
- class DeadSlappedBeatGlyph extends Glyph {
66978
- constructor() {
66941
+ class NumberedDashBeatContainerGlyph extends BeatContainerGlyphBase {
66942
+ _absoluteDisplayStart;
66943
+ _voiceIndex;
66944
+ constructor(voiceIndex, absoluteDisplayStart) {
66979
66945
  super(0, 0);
66946
+ this._absoluteDisplayStart = absoluteDisplayStart;
66947
+ this._voiceIndex = voiceIndex;
66980
66948
  }
66981
- doLayout() {
66982
- this.width = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.NoteheadSlashWhiteHalf);
66949
+ get beatId() {
66950
+ return -1;
66951
+ }
66952
+ get contentWidth() {
66953
+ return this.renderer.smuflMetrics.numberedDashGlyphWidth;
66954
+ }
66955
+ get absoluteDisplayStart() {
66956
+ return this._absoluteDisplayStart;
66957
+ }
66958
+ get displayDuration() {
66959
+ return MidiUtils.QuarterTime;
66960
+ }
66961
+ get onTimeX() {
66962
+ return this.renderer.smuflMetrics.numberedDashGlyphWidth / 2;
66963
+ }
66964
+ get graceType() {
66965
+ return GraceType.None;
66966
+ }
66967
+ get graceIndex() {
66968
+ return 0;
66969
+ }
66970
+ get graceGroup() {
66971
+ return null;
66972
+ }
66973
+ get voiceIndex() {
66974
+ return this._voiceIndex;
66975
+ }
66976
+ get isFirstOfTupletGroup() {
66977
+ return false;
66978
+ }
66979
+ get tupletGroup() {
66980
+ return null;
66981
+ }
66982
+ get isLastOfVoice() {
66983
+ return false;
66984
+ }
66985
+ getNoteY(_note, _requestedPosition) {
66986
+ return 0;
66987
+ }
66988
+ doMultiVoiceLayout() {
66989
+ }
66990
+ getRestY(_requestedPosition) {
66991
+ return 0;
66992
+ }
66993
+ getNoteX(_note, _requestedPosition) {
66994
+ return 0;
66995
+ }
66996
+ getBeatX(_requestedPosition, _useSharedSizes) {
66997
+ return 0;
66998
+ }
66999
+ registerLayoutingInfo(layoutings) {
67000
+ const width = this.renderer.smuflMetrics.numberedDashGlyphWidth;
67001
+ layoutings.addBeatSpring(this, width / 2, width / 2);
67002
+ }
67003
+ applyLayoutingInfo(_info) {
67004
+ }
67005
+ buildBoundingsLookup(_barBounds, _cx, _cy) {
66983
67006
  }
66984
67007
  paint(cx, cy, canvas) {
66985
67008
  const renderer = this.renderer;
66986
- const crossHeight = renderer.getLineHeight(renderer.heightLineCount - 1);
66987
- const staffTop = renderer.getLineY(0);
66988
- const staffHeight = renderer.getLineHeight(renderer.drawnLineCount - 1);
66989
- // center X on staff
66990
- const centerY = staffTop + staffHeight / 2 - crossHeight / 2;
66991
- const lw = canvas.lineWidth;
66992
- canvas.lineWidth = this.renderer.smuflMetrics.deadSlappedLineWidth;
66993
- canvas.moveTo(cx + this.x, cy + centerY);
66994
- canvas.lineTo(cx + this.x + this.width, cy + centerY + crossHeight);
66995
- canvas.moveTo(cx + this.x, cy + centerY + crossHeight);
66996
- canvas.lineTo(cx + this.x + this.width, cy + centerY);
66997
- canvas.stroke();
66998
- canvas.lineWidth = lw;
67009
+ const dashWidth = renderer.smuflMetrics.numberedDashGlyphWidth;
67010
+ const dashHeight = renderer.smuflMetrics.numberedBarRendererBarSize;
67011
+ const dashY = Math.ceil(cy + renderer.getLineY(0) - dashHeight);
67012
+ canvas.fillRect(cx + this.x, dashY, dashWidth, dashHeight);
66999
67013
  }
67000
67014
  }
67001
67015
 
67002
67016
  /**
67003
67017
  * @internal
67004
67018
  */
67005
- class NumberedDashGlyph extends Glyph {
67006
- _beat;
67007
- constructor(x, y, beat) {
67008
- super(x, y);
67009
- this._beat = beat;
67019
+ class GhostParenthesisGlyph extends Glyph {
67020
+ _isOpen;
67021
+ colorOverride;
67022
+ constructor(isOpen) {
67023
+ super(0, 0);
67024
+ this._isOpen = isOpen;
67010
67025
  }
67011
67026
  doLayout() {
67027
+ super.doLayout();
67012
67028
  this.width =
67013
- this.renderer.smuflMetrics.numberedDashGlyphWidth + this.renderer.smuflMetrics.numberedDashGlyphPadding;
67014
- this.height = this.renderer.smuflMetrics.numberedBarRendererBarSize;
67029
+ this.renderer.smuflMetrics.ghostParenthesisWidth + this.renderer.smuflMetrics.ghostParenthesisPadding;
67015
67030
  }
67016
67031
  paint(cx, cy, canvas) {
67017
- const _ = ElementStyleHelper.beat(canvas, BeatSubElement.NumberedDuration, this._beat);
67018
- try {
67019
- const padding = this.renderer.smuflMetrics.numberedDashGlyphPadding;
67020
- canvas.fillRect(cx + this.x, Math.ceil(cy + this.y - this.height), this.width - padding, this.height);
67032
+ const c = canvas.color;
67033
+ if (this.colorOverride) {
67034
+ canvas.color = this.colorOverride;
67021
67035
  }
67022
- finally {
67023
- _?.[Symbol.dispose]?.();
67036
+ if (this._isOpen) {
67037
+ TieGlyph.paintTie(canvas, 1, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisWidth, cy + this.y + this.height, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisWidth, cy + this.y, false, this.renderer.smuflMetrics.ghostParenthesisWidth / 2, this.renderer.smuflMetrics.tieMidpointThickness);
67024
67038
  }
67039
+ else {
67040
+ TieGlyph.paintTie(canvas, 1, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisPadding, cy + this.y, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisPadding, cy + this.y + this.height, false, this.renderer.smuflMetrics.ghostParenthesisWidth / 2, this.renderer.smuflMetrics.tieMidpointThickness);
67041
+ }
67042
+ canvas.color = c;
67025
67043
  }
67026
67044
  }
67027
67045
 
67028
67046
  /**
67029
67047
  * @internal
67030
67048
  */
67031
- class NumberedNoteHeadGlyph extends Glyph {
67032
- _isGrace;
67033
- _beat;
67034
- _number;
67035
- constructor(x, y, number, isGrace, beat) {
67049
+ class TimeSignatureGlyph extends GlyphGroup {
67050
+ _numerator = 0;
67051
+ _denominator = 0;
67052
+ _isCommon;
67053
+ _isFreeTime;
67054
+ barSubElement = BarSubElement.StandardNotationTimeSignature;
67055
+ constructor(x, y, numerator, denominator, isCommon, isFreeTime) {
67036
67056
  super(x, y);
67037
- this._isGrace = isGrace;
67038
- this._number = number;
67039
- this._beat = beat;
67057
+ this._numerator = numerator;
67058
+ this._denominator = denominator;
67059
+ this._isCommon = isCommon;
67060
+ this._isFreeTime = isFreeTime;
67040
67061
  }
67041
67062
  paint(cx, cy, canvas) {
67042
- const _ = this._beat.isRest
67043
- ? ElementStyleHelper.beat(canvas, BeatSubElement.NumberedRests, this._beat)
67044
- : this._beat.notes.length > 0
67045
- ? ElementStyleHelper.note(canvas, NoteSubElement.NumberedNumber, this._beat.notes[0])
67046
- : undefined;
67063
+ const _ = ElementStyleHelper.bar(canvas, this.barSubElement, this.renderer.bar);
67047
67064
  try {
67048
- const res = this.renderer.resources;
67049
- canvas.font = this._isGrace ? res.numberedNotationGraceFont : res.numberedNotationFont;
67050
- canvas.textBaseline = TextBaseline.Middle;
67051
- canvas.textAlign = TextAlign.Left;
67052
- canvas.fillText(this._number.toString(), cx + this.x, cy + this.y);
67065
+ super.paint(cx, cy, canvas);
67053
67066
  }
67054
67067
  finally {
67055
67068
  _?.[Symbol.dispose]?.();
67056
67069
  }
67057
67070
  }
67058
67071
  doLayout() {
67059
- const res = this.renderer.resources;
67060
- const font = this._isGrace ? res.numberedNotationGraceFont : res.numberedNotationFont;
67061
- const c = this.renderer.scoreRenderer.canvas;
67062
- c.font = font;
67063
- const size = c.measureText(`${this._number}`);
67064
- this.height = size.height;
67065
- this.width = size.width;
67072
+ if (this._isCommon && this._numerator === 2 && this._denominator === 2) {
67073
+ const common = new MusicFontGlyph(0, 0, this.commonScale, MusicFontSymbol.TimeSigCutCommon);
67074
+ this.addGlyph(common);
67075
+ super.doLayout();
67076
+ }
67077
+ else if (this._isCommon && this._numerator === 4 && this._denominator === 4) {
67078
+ const common = new MusicFontGlyph(0, 0, this.commonScale, MusicFontSymbol.TimeSigCommon);
67079
+ this.addGlyph(common);
67080
+ super.doLayout();
67081
+ }
67082
+ else {
67083
+ const numerator = new NumberGlyph(0, 0, this._numerator, TextBaseline.Top, this.numberScale);
67084
+ const denominator = new NumberGlyph(0, 0, this._denominator, TextBaseline.Bottom, this.numberScale);
67085
+ this.addGlyph(numerator);
67086
+ this.addGlyph(denominator);
67087
+ super.doLayout();
67088
+ const glyphSpace = this.width;
67089
+ numerator.x = (glyphSpace - numerator.width) / 2;
67090
+ denominator.x = (glyphSpace - denominator.width) / 2;
67091
+ this.width = Math.max(numerator.x + numerator.width, denominator.x + denominator.width);
67092
+ }
67093
+ if (this._isFreeTime) {
67094
+ const numberHeight = this.renderer.smuflMetrics.oneStaffSpace * 2;
67095
+ const openParenthesis = new GhostParenthesisGlyph(true);
67096
+ openParenthesis.renderer = this.renderer;
67097
+ openParenthesis.y = -numberHeight;
67098
+ openParenthesis.height = numberHeight * 2;
67099
+ openParenthesis.doLayout();
67100
+ for (const g of this.glyphs) {
67101
+ g.x += openParenthesis.width;
67102
+ }
67103
+ this.width += openParenthesis.width;
67104
+ this.addGlyph(openParenthesis);
67105
+ const closeParenthesis = new GhostParenthesisGlyph(false);
67106
+ closeParenthesis.renderer = this.renderer;
67107
+ closeParenthesis.x = this.width;
67108
+ closeParenthesis.y = -numberHeight;
67109
+ closeParenthesis.height = numberHeight * 2;
67110
+ closeParenthesis.doLayout();
67111
+ this.addGlyph(closeParenthesis);
67112
+ this.width += closeParenthesis.width;
67113
+ }
67066
67114
  }
67067
67115
  }
67068
67116
 
67069
67117
  /**
67070
67118
  * @internal
67071
67119
  */
67072
- class NumberedBeatPreNotesGlyph extends BeatGlyphBase {
67073
- isNaturalizeAccidental = false;
67074
- accidental = AccidentalType.None;
67075
- get effectElement() {
67076
- return BeatSubElement.NumberedEffects;
67120
+ class ScoreTimeSignatureGlyph extends TimeSignatureGlyph {
67121
+ get commonScale() {
67122
+ return 1;
67077
67123
  }
67078
- doLayout() {
67079
- if (!this.container.beat.isRest && !this.container.beat.isEmpty) {
67080
- const accidentals = new AccidentalGroupGlyph();
67081
- accidentals.renderer = this.renderer;
67082
- if (this.container.beat.notes.length > 0) {
67083
- const note = this.container.beat.notes[0];
67084
- // Notes
67085
- // - Compared to standard notation accidentals:
67086
- // - Flat keysigs: When there is a naturalize symbol (against key signature, not naturalizing same line) we have a # in Numbered notation
67087
- // - Flat keysigs: When there is a flat symbol standard notation we also have a flat in Numbered notation
67088
- // - C keysig: A sharp on standard notation is a sharp on numbered notation
67089
- // - # keysigs: When there is a # symbol on standard notation we also a sharp in numbered notation
67090
- // - # keysigs: When there is a naturalize symbol (against key signature, not naturalizing same line) we have a flat in Numbered notation
67091
- // Or generally:
67092
- // - numbered notation has the same accidentals as standard notation if applied
67093
- // - when the standard notation naturalizes the accidental from the key signature, the numbered notation has the reversed accidental
67094
- const accidentalMode = note ? note.accidentalMode : NoteAccidentalMode.Default;
67095
- const noteValue = AccidentalHelper.getNoteValue(note);
67096
- let accidentalToSet = ModelUtils.computeAccidental(this.renderer.bar.keySignature, accidentalMode, noteValue, note.hasQuarterToneOffset);
67097
- if (accidentalToSet === AccidentalType.Natural) {
67098
- const ks = this.renderer.bar.keySignature;
67099
- const ksi = ks + 7;
67100
- const naturalizeAccidentalForKeySignature = ksi < 7 ? AccidentalType.Sharp : AccidentalType.Flat;
67101
- accidentalToSet = naturalizeAccidentalForKeySignature;
67102
- this.isNaturalizeAccidental = true;
67103
- }
67104
- // do we need an accidental on the note?
67105
- if (accidentalToSet !== AccidentalType.None) {
67106
- this.accidental = accidentalToSet;
67107
- const sr = this.renderer;
67108
- const color = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.NumberedAccidentals, note);
67109
- const g = new AccidentalGlyph(0, sr.getLineY(0), accidentalToSet, note.beat.graceType !== GraceType.None
67110
- ? EngravingSettings.GraceScale * EngravingSettings.GraceScale
67111
- : EngravingSettings.GraceScale);
67112
- g.colorOverride = color;
67113
- g.renderer = this.renderer;
67114
- accidentals.addGlyph(g);
67115
- this.addNormal(accidentals);
67116
- this.addNormal(new SpacingGlyph(0, 0, this.renderer.smuflMetrics.preNoteEffectPadding));
67117
- }
67118
- }
67119
- }
67120
- super.doLayout();
67124
+ get numberScale() {
67125
+ return 1;
67121
67126
  }
67122
67127
  }
67128
+
67123
67129
  /**
67124
67130
  * @internal
67125
67131
  */
67126
- class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67127
- noteHeads = null;
67128
- deadSlapped = null;
67129
- octaveDots = 0;
67130
- get effectElement() {
67131
- return BeatSubElement.NumberedEffects;
67132
+ class FlagGlyph extends MusicFontGlyph {
67133
+ constructor(x, y, duration, direction, isGrace) {
67134
+ super(x, y, isGrace ? EngravingSettings.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
67132
67135
  }
67133
- getNoteX(_note, requestedPosition) {
67134
- let g = null;
67135
- if (this.noteHeads) {
67136
- g = this.noteHeads;
67137
- }
67138
- else if (this.deadSlapped) {
67139
- g = this.deadSlapped;
67136
+ static getSymbol(duration, direction, isGrace) {
67137
+ if (isGrace) {
67138
+ duration = Duration.Eighth;
67140
67139
  }
67141
- if (g) {
67142
- let pos = g.x;
67143
- switch (requestedPosition) {
67144
- case NoteXPosition.Left:
67145
- break;
67146
- case NoteXPosition.Center:
67147
- pos += g.width / 2;
67148
- break;
67149
- case NoteXPosition.Right:
67150
- pos += g.width;
67151
- break;
67140
+ if (direction === BeamDirection.Up) {
67141
+ switch (duration) {
67142
+ case Duration.Eighth:
67143
+ return MusicFontSymbol.Flag8thUp;
67144
+ case Duration.Sixteenth:
67145
+ return MusicFontSymbol.Flag16thUp;
67146
+ case Duration.ThirtySecond:
67147
+ return MusicFontSymbol.Flag32ndUp;
67148
+ case Duration.SixtyFourth:
67149
+ return MusicFontSymbol.Flag64thUp;
67150
+ case Duration.OneHundredTwentyEighth:
67151
+ return MusicFontSymbol.Flag128thUp;
67152
+ case Duration.TwoHundredFiftySixth:
67153
+ return MusicFontSymbol.Flag256thUp;
67154
+ default:
67155
+ return MusicFontSymbol.Flag8thUp;
67152
67156
  }
67153
- return pos;
67154
- }
67155
- return 0;
67156
- }
67157
- buildBoundingsLookup(beatBounds, cx, cy) {
67158
- if (this.noteHeads && this.container.beat.notes.length > 0) {
67159
- const noteBounds = new NoteBounds();
67160
- noteBounds.note = this.container.beat.notes[0];
67161
- noteBounds.noteHeadBounds = new Bounds();
67162
- noteBounds.noteHeadBounds.x = cx + this.x + this.noteHeads.x;
67163
- noteBounds.noteHeadBounds.y = cy + this.y + this.noteHeads.y - this.noteHeads.height / 2;
67164
- noteBounds.noteHeadBounds.w = this.width;
67165
- noteBounds.noteHeadBounds.h = this.height;
67166
- beatBounds.addNote(noteBounds);
67157
+ }
67158
+ switch (duration) {
67159
+ case Duration.Eighth:
67160
+ return MusicFontSymbol.Flag8thDown;
67161
+ case Duration.Sixteenth:
67162
+ return MusicFontSymbol.Flag16thDown;
67163
+ case Duration.ThirtySecond:
67164
+ return MusicFontSymbol.Flag32ndDown;
67165
+ case Duration.SixtyFourth:
67166
+ return MusicFontSymbol.Flag64thDown;
67167
+ case Duration.OneHundredTwentyEighth:
67168
+ return MusicFontSymbol.Flag128thDown;
67169
+ case Duration.TwoHundredFiftySixth:
67170
+ return MusicFontSymbol.Flag128thDown;
67171
+ default:
67172
+ return MusicFontSymbol.Flag8thDown;
67167
67173
  }
67168
67174
  }
67169
- getLowestNoteY() {
67170
- return this._internalGetNoteY(NoteYPosition.Center);
67171
- }
67172
- getHighestNoteY() {
67173
- return this._internalGetNoteY(NoteYPosition.Center);
67174
- }
67175
- getNoteY(_note, requestedPosition) {
67176
- return this._internalGetNoteY(requestedPosition);
67175
+ }
67176
+
67177
+ /**
67178
+ * @internal
67179
+ */
67180
+ class RepeatCountGlyph extends Glyph {
67181
+ _count = 0;
67182
+ constructor(x, y, count) {
67183
+ super(x, y);
67184
+ this._count = 0;
67185
+ this._count = count;
67177
67186
  }
67178
- getRestY(requestedPosition) {
67179
- return this._internalGetNoteY(requestedPosition);
67187
+ doLayout() {
67188
+ this.renderer.scoreRenderer.canvas.font = this.renderer.resources.barNumberFont;
67189
+ const size = this.renderer.scoreRenderer.canvas.measureText(`x${this._count}`);
67190
+ this.width = 0; // do not account width
67191
+ this.height = size.height;
67192
+ this.y -= size.height;
67180
67193
  }
67181
- _internalGetNoteY(requestedPosition) {
67182
- let g = null;
67183
- if (this.noteHeads) {
67184
- g = this.noteHeads;
67185
- }
67186
- else if (this.deadSlapped) {
67187
- g = this.deadSlapped;
67194
+ paint(cx, cy, canvas) {
67195
+ const _ = ElementStyleHelper.bar(canvas, this.renderer.repeatsBarSubElement, this.renderer.bar);
67196
+ try {
67197
+ const res = this.renderer.resources;
67198
+ const oldAlign = canvas.textAlign;
67199
+ canvas.font = res.barNumberFont;
67200
+ canvas.textAlign = TextAlign.Right;
67201
+ const s = `x${this._count}`;
67202
+ const w = canvas.measureText(s).width / 1.5;
67203
+ canvas.fillText(s, cx + this.x - w, cy + this.y);
67204
+ canvas.textAlign = oldAlign;
67188
67205
  }
67189
- if (g) {
67190
- let pos = this.y + g.y;
67191
- switch (requestedPosition) {
67192
- case NoteYPosition.Top:
67193
- case NoteYPosition.TopWithStem:
67194
- pos -= g.height / 2;
67195
- break;
67196
- case NoteYPosition.Center:
67197
- break;
67198
- case NoteYPosition.Bottom:
67199
- case NoteYPosition.BottomWithStem:
67200
- pos += g.height / 2;
67201
- break;
67202
- case NoteYPosition.StemUp:
67203
- case NoteYPosition.StemDown:
67204
- break;
67205
- }
67206
- return pos;
67206
+ finally {
67207
+ _?.[Symbol.dispose]?.();
67207
67208
  }
67209
+ }
67210
+ }
67211
+
67212
+ /**
67213
+ * This is a base class for any bar renderer which renders music notation on a staff
67214
+ * with lines like Standard Notation, Guitar Tablatures and Slash Notation.
67215
+ *
67216
+ * This base class takes care of the typical bits like drawing lines,
67217
+ * allowing note positioning and creating glyphs like repeats, bar numbers etc..
67218
+ * @internal
67219
+ */
67220
+ class LineBarRenderer extends BarRendererBase {
67221
+ firstLineY = 0;
67222
+ _startSpacing = false;
67223
+ tupletSize = 0;
67224
+ get lineOffset() {
67225
+ return this.lineSpacing;
67226
+ }
67227
+ get tupletOffset() {
67228
+ return this.smuflMetrics.oneStaffSpace * 0.5;
67229
+ }
67230
+ get topGlyphOverflow() {
67208
67231
  return 0;
67209
67232
  }
67210
- static majorKeySignatureOneValues = [
67211
- // Flats
67212
- 59, 66, 61, 68, 63, 58, 65,
67213
- // natural
67214
- 60,
67215
- // sharps (where the value is true, a flat accidental is required for the notes)
67216
- 67, 62, 69, 64, 71, 66, 61
67217
- ];
67218
- static minorKeySignatureOneValues = [
67219
- // Flats
67220
- 71, 66, 73, 68, 63, 70, 65,
67221
- // natural
67222
- 72,
67223
- // sharps (where the value is true, a flat accidental is required for the notes)
67224
- 67, 74, 69, 64, 71, 66, 73
67225
- ];
67233
+ get bottomGlyphOverflow() {
67234
+ return 0;
67235
+ }
67236
+ initLineBasedSizes() {
67237
+ this.height = this.lineOffset * (this.heightLineCount - 1);
67238
+ }
67239
+ updateSizes() {
67240
+ this.initLineBasedSizes();
67241
+ this.adjustSizes();
67242
+ this.updateFirstLineY();
67243
+ super.updateSizes();
67244
+ }
67245
+ adjustSizes() {
67246
+ }
67247
+ updateFirstLineY() {
67248
+ const fullLineHeight = this.lineOffset * (this.heightLineCount - 1);
67249
+ const actualLineHeight = this.drawnLineCount === 0 ? 0 : (this.drawnLineCount - 1) * this.lineOffset;
67250
+ const lineYOffset = this.smuflMetrics.staffLineThickness / 2;
67251
+ this.firstLineY = (((fullLineHeight - actualLineHeight) / 2) | 0) - lineYOffset;
67252
+ }
67226
67253
  doLayout() {
67227
- // create glyphs
67228
- const sr = this.renderer;
67229
- if (sr.shortestDuration < this.container.beat.duration) {
67230
- sr.shortestDuration = this.container.beat.duration;
67231
- }
67232
- if (!this.container.beat.isEmpty) {
67233
- const glyphY = sr.getLineY(0);
67234
- let numberWithinOctave = '0';
67235
- if (this.container.beat.notes.length > 0) {
67236
- const note = this.container.beat.notes[0];
67237
- const kst = this.renderer.bar.keySignatureType;
67238
- const ks = this.renderer.bar.keySignature;
67239
- const ksi = ks + 7;
67240
- const oneNoteValues = kst === KeySignatureType.Minor
67241
- ? NumberedBeatGlyph.minorKeySignatureOneValues
67242
- : NumberedBeatGlyph.majorKeySignatureOneValues;
67243
- const oneNoteValue = oneNoteValues[ksi];
67244
- if (note.isDead) {
67245
- numberWithinOctave = 'X';
67254
+ this.initLineBasedSizes();
67255
+ this.updateFirstLineY();
67256
+ this.tupletSize = this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tuplet0);
67257
+ super.doLayout();
67258
+ }
67259
+ getLineY(line) {
67260
+ return this.firstLineY + this.getLineHeight(line);
67261
+ }
67262
+ getLineHeight(line) {
67263
+ return this.lineOffset * line;
67264
+ }
67265
+ paintContent(cx, cy, canvas) {
67266
+ super.paintContent(cx, cy, canvas);
67267
+ this.paintBeams(cx, cy, canvas, this.flagsSubElement, this.beamsSubElement);
67268
+ this.paintTuplets(cx, cy, canvas, this.tupletSubElement);
67269
+ }
67270
+ paintBackground(cx, cy, canvas) {
67271
+ super.paintBackground(cx, cy, canvas);
67272
+ // canvas.color = Color.random(100);
67273
+ // canvas.fillRect(cx + this.x, cy + this.y, this.width, this.height);
67274
+ //
67275
+ // draw string lines
67276
+ //
67277
+ this.paintStaffLines(cx, cy, canvas);
67278
+ this.paintSimileMark(cx, cy, canvas);
67279
+ }
67280
+ paintStaffLines(cx, cy, canvas) {
67281
+ const _ = ElementStyleHelper.bar(canvas, this.staffLineBarSubElement, this.bar, true);
67282
+ try {
67283
+ // collect tab note position for spaces
67284
+ const spaces = [];
67285
+ for (let i = 0, j = this.drawnLineCount; i < j; i++) {
67286
+ spaces.push([]);
67287
+ }
67288
+ // on multibar rest glyphs we don't have spaces as they are empty
67289
+ if (!this.additionalMultiRestBars) {
67290
+ this.collectSpaces(spaces);
67291
+ }
67292
+ // if we have multiple voices we need to sort by X-position, otherwise have a wild mix in the list
67293
+ // but painting relies on ascending X-position
67294
+ for (const line of spaces) {
67295
+ line.sort((a, b) => {
67296
+ return a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0;
67297
+ });
67298
+ }
67299
+ // during system fitting it can happen that we have fraction widths
67300
+ // but to have lines until the full end-pixel we round up.
67301
+ // this way we avoid holes,
67302
+ const lineWidth = this.width;
67303
+ // we want the lines to be exactly virtually aligned with the respective Y-position
67304
+ // for note heads to align correctly
67305
+ const lineYOffset = this.smuflMetrics.staffLineThickness / 2;
67306
+ for (let i = 0; i < this.drawnLineCount; i++) {
67307
+ const lineY = this.getLineY(i) - lineYOffset;
67308
+ let lineX = 0;
67309
+ for (const line of spaces[i]) {
67310
+ canvas.fillRect(cx + this.x + lineX, cy + this.y + lineY, line[0] - lineX, this.smuflMetrics.staffLineThickness);
67311
+ lineX = line[0] + line[1];
67246
67312
  }
67247
- else {
67248
- const noteValue = note.displayValue - oneNoteValue;
67249
- const index = noteValue < 0 ? ((noteValue % 12) + 12) % 12 : noteValue % 12;
67250
- let dots = noteValue < 0 ? ((Math.abs(noteValue) + 12) / 12) | 0 : (noteValue / 12) | 0;
67251
- if (noteValue < 0) {
67252
- dots *= -1;
67253
- }
67254
- this.octaveDots = dots;
67255
- sr.registerOctave(this.container.beat, dots);
67256
- const stepList = ModelUtils.keySignatureIsSharp(ks) || ModelUtils.keySignatureIsNatural(ks)
67257
- ? AccidentalHelper.flatNoteSteps
67258
- : AccidentalHelper.sharpNoteSteps;
67259
- let steps = stepList[index] + 1;
67260
- const hasAccidental = ModelUtils.accidentalNotes[index];
67261
- if (hasAccidental &&
67262
- !this.container.preNotes.isNaturalizeAccidental) {
67263
- if (ksi < 7) {
67264
- steps++;
67265
- }
67266
- else {
67267
- steps--;
67268
- }
67269
- }
67270
- numberWithinOctave = steps.toString();
67313
+ canvas.fillRect(cx + this.x + lineX, cy + this.y + lineY, lineWidth - lineX, this.smuflMetrics.staffLineThickness);
67314
+ }
67315
+ }
67316
+ finally {
67317
+ _?.[Symbol.dispose]?.();
67318
+ }
67319
+ }
67320
+ collectSpaces(_spaces) {
67321
+ }
67322
+ createStartSpacing() {
67323
+ if (this._startSpacing) {
67324
+ return;
67325
+ }
67326
+ const padding = this.index === 0 ? this.settings.display.firstStaffPaddingLeft : this.settings.display.staffPaddingLeft;
67327
+ this.addPreBeatGlyph(new SpacingGlyph(0, 0, padding));
67328
+ this._startSpacing = true;
67329
+ }
67330
+ paintTuplets(cx, cy, canvas, beatElement, bracketsAsArcs = false) {
67331
+ for (const v of this.voiceContainer.voiceDrawOrder) {
67332
+ if (this.voiceContainer.tupletGroups.has(v)) {
67333
+ const voice = this.voiceContainer.tupletGroups.get(v);
67334
+ for (const tupletGroup of voice) {
67335
+ this._paintTupletHelper(cx, cy, canvas, tupletGroup, beatElement, bracketsAsArcs);
67271
67336
  }
67272
67337
  }
67273
- if (this.container.beat.deadSlapped) {
67274
- const deadSlapped = new DeadSlappedBeatGlyph();
67275
- deadSlapped.renderer = this.renderer;
67276
- deadSlapped.doLayout();
67277
- this.deadSlapped = deadSlapped;
67278
- this.addEffect(deadSlapped);
67338
+ }
67339
+ }
67340
+ getTupletBeamDirection(helper) {
67341
+ return this.getBeamDirection(helper);
67342
+ }
67343
+ _paintTupletHelper(cx, cy, canvas, h, beatElement, bracketsAsArcs) {
67344
+ const res = this.resources;
67345
+ const oldAlign = canvas.textAlign;
67346
+ const oldBaseLine = canvas.textBaseline;
67347
+ canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
67348
+ canvas.textAlign = TextAlign.Center;
67349
+ canvas.textBaseline = TextBaseline.Middle;
67350
+ let s;
67351
+ const num = h.beats[0].tupletNumerator;
67352
+ const den = h.beats[0].tupletDenominator;
67353
+ // list as in Guitar Pro 7. for certain tuplets only the numerator is shown
67354
+ if (num === 2 && den === 3) {
67355
+ s = [MusicFontSymbol.Tuplet2];
67356
+ }
67357
+ else if (num === 3 && den === 2) {
67358
+ s = [MusicFontSymbol.Tuplet3];
67359
+ }
67360
+ else if (num === 4 && den === 6) {
67361
+ s = [MusicFontSymbol.Tuplet4];
67362
+ }
67363
+ else if (num === 5 && den === 4) {
67364
+ s = [MusicFontSymbol.Tuplet5];
67365
+ }
67366
+ else if (num === 6 && den === 4) {
67367
+ s = [MusicFontSymbol.Tuplet6];
67368
+ }
67369
+ else if (num === 7 && den === 4) {
67370
+ s = [MusicFontSymbol.Tuplet7];
67371
+ }
67372
+ else if (num === 9 && den === 8) {
67373
+ s = [MusicFontSymbol.Tuplet9];
67374
+ }
67375
+ else if (num === 10 && den === 8) {
67376
+ s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet0];
67377
+ }
67378
+ else if (num === 11 && den === 8) {
67379
+ s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet1];
67380
+ }
67381
+ else if (num === 12 && den === 8) {
67382
+ s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet2];
67383
+ }
67384
+ else if (num === 13 && den === 8) {
67385
+ s = [MusicFontSymbol.Tuplet1, MusicFontSymbol.Tuplet3];
67386
+ }
67387
+ else {
67388
+ s = [];
67389
+ const zero = MusicFontSymbol.Tuplet0;
67390
+ if (num > 10) {
67391
+ s.push((zero + Math.floor(num / 10)));
67392
+ s.push((zero + (num - 10)));
67279
67393
  }
67280
67394
  else {
67281
- const isGrace = this.container.beat.graceType !== GraceType.None;
67282
- const noteHeadGlyph = new NumberedNoteHeadGlyph(0, glyphY, numberWithinOctave, isGrace, this.container.beat);
67283
- this.noteHeads = noteHeadGlyph;
67284
- this.addNormal(noteHeadGlyph);
67395
+ s.push((zero + num));
67285
67396
  }
67286
- //
67287
- // Note dots
67288
- if (this.container.beat.dots > 0 && this.container.beat.duration >= Duration.Quarter) {
67289
- for (let i = 0; i < this.container.beat.dots; i++) {
67290
- const dot = new AugmentationDotGlyph(0, glyphY);
67291
- dot.renderer = this.renderer;
67292
- this.addEffect(dot);
67293
- }
67397
+ s.push(MusicFontSymbol.TupletColon);
67398
+ if (den > 10) {
67399
+ s.push((zero + Math.floor(den / 10)));
67400
+ s.push((zero + (den - 10)));
67294
67401
  }
67295
- //
67296
- // Dashes
67297
- let numberOfQuarterNotes = 0;
67298
- switch (this.container.beat.duration) {
67299
- case Duration.QuadrupleWhole:
67300
- numberOfQuarterNotes = 16;
67301
- break;
67302
- case Duration.DoubleWhole:
67303
- numberOfQuarterNotes = 8;
67304
- break;
67305
- case Duration.Whole:
67306
- numberOfQuarterNotes = 4;
67307
- break;
67308
- case Duration.Half:
67309
- numberOfQuarterNotes = 2;
67310
- break;
67402
+ else {
67403
+ s.push((zero + den));
67311
67404
  }
67312
- let numberOfAddedQuarters = numberOfQuarterNotes;
67313
- for (let i = 0; i < this.container.beat.dots; i++) {
67314
- numberOfAddedQuarters = (numberOfAddedQuarters / 2) | 0;
67315
- numberOfQuarterNotes += numberOfAddedQuarters;
67405
+ }
67406
+ // check if we need to paint simple footer
67407
+ const offset = this.tupletOffset;
67408
+ const size = this.tupletSize;
67409
+ const shift = offset + size * 0.5;
67410
+ const _ = ElementStyleHelper.beat(canvas, beatElement, h.beats[0]);
67411
+ try {
67412
+ const l = canvas.lineWidth;
67413
+ canvas.lineWidth = this.smuflMetrics.tupletBracketThickness;
67414
+ if (h.beats.length === 1 || !h.isFull) {
67415
+ for (const beat of h.beats) {
67416
+ const beamingHelper = this.helpers.getBeamingHelperForBeat(beat);
67417
+ if (!beamingHelper) {
67418
+ continue;
67419
+ }
67420
+ const direction = this.getTupletBeamDirection(beamingHelper);
67421
+ const tupletX = this.getBeatX(beat, BeatXPosition.Stem);
67422
+ let tupletY = this.calculateBeamYWithDirection(beamingHelper, tupletX, direction);
67423
+ if (direction === BeamDirection.Down) {
67424
+ tupletY += shift;
67425
+ }
67426
+ else {
67427
+ tupletY -= shift;
67428
+ }
67429
+ canvas.fillMusicFontSymbols(cx + this.x + tupletX, cy + this.y + tupletY + size * 0.5, 1, s, true);
67430
+ }
67316
67431
  }
67317
- for (let i = 0; i < numberOfQuarterNotes - 1; i++) {
67318
- const dash = new NumberedDashGlyph(0, glyphY, this.container.beat);
67319
- dash.renderer = this.renderer;
67320
- this.addNormal(dash);
67432
+ else {
67433
+ const firstBeat = h.beats[0];
67434
+ const lastBeat = h.beats[h.beats.length - 1];
67435
+ let firstNonRestBeat = null;
67436
+ let lastNonRestBeat = null;
67437
+ for (let i = 0; i < h.beats.length; i++) {
67438
+ if (!h.beats[i].isRest) {
67439
+ firstNonRestBeat = h.beats[i];
67440
+ break;
67441
+ }
67442
+ }
67443
+ for (let i = h.beats.length - 1; i >= 0; i--) {
67444
+ if (!h.beats[i].isRest) {
67445
+ lastNonRestBeat = h.beats[i];
67446
+ break;
67447
+ }
67448
+ }
67449
+ let isRestOnly = false;
67450
+ if (!firstNonRestBeat) {
67451
+ firstNonRestBeat = firstBeat;
67452
+ isRestOnly = true;
67453
+ }
67454
+ if (!lastNonRestBeat) {
67455
+ lastNonRestBeat = lastBeat;
67456
+ }
67457
+ //
67458
+ // Calculate the overall area of the tuplet bracket
67459
+ const startX = this.getBeatX(firstBeat, BeatXPosition.OnNotes);
67460
+ const endX = this.getBeatX(lastBeat, BeatXPosition.PostNotes);
67461
+ //
67462
+ // calculate the y positions for our bracket
67463
+ const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
67464
+ const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
67465
+ const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
67466
+ let startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
67467
+ let endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
67468
+ if (isRestOnly) {
67469
+ startY = Math.max(startY, endY);
67470
+ endY = startY;
67471
+ }
67472
+ // align line centered in available space
67473
+ if (direction === BeamDirection.Down) {
67474
+ startY += shift;
67475
+ endY += shift;
67476
+ }
67477
+ else {
67478
+ startY -= shift;
67479
+ endY -= shift;
67480
+ }
67481
+ //
67482
+ // Calculate how many space the text will need
67483
+ const sw = s.reduce((acc, sym) => acc + res.engravingSettings.glyphWidths.get(sym), 0);
67484
+ const sp = res.engravingSettings.oneStaffSpace * 0.5;
67485
+ //
67486
+ // Calculate the offsets where to break the bracket
67487
+ const middleX = (startX + endX) / 2;
67488
+ const offset1X = middleX - sw / 2 - sp;
67489
+ const offset2X = middleX + sw / 2 + sp;
67490
+ const k = (endY - startY) / (endX - startX);
67491
+ const d = startY - k * startX;
67492
+ const offset1Y = k * offset1X + d;
67493
+ const middleY = k * middleX + d;
67494
+ const offset2Y = k * offset2X + d;
67495
+ const angleStartY = direction === BeamDirection.Down ? startY - size * 0.5 : startY + size * 0.5;
67496
+ const angleEndY = direction === BeamDirection.Down ? endY - size * 0.5 : endY + size * 0.5;
67497
+ //
67498
+ // draw the bracket
67499
+ const pixelAlignment = canvas.lineWidth % 2 === 0 ? 0 : 0.5;
67500
+ cx += pixelAlignment;
67501
+ cy += pixelAlignment;
67502
+ if (offset1X > startX) {
67503
+ canvas.beginPath();
67504
+ canvas.moveTo(cx + this.x + startX, cy + this.y + angleStartY);
67505
+ if (bracketsAsArcs) {
67506
+ canvas.quadraticCurveTo(cx + this.x + (offset1X + startX) / 2, cy + this.y + offset1Y, cx + this.x + offset1X, cy + this.y + offset1Y);
67507
+ }
67508
+ else {
67509
+ canvas.lineTo(cx + this.x + startX, cy + this.y + startY);
67510
+ canvas.lineTo(cx + this.x + offset1X, cy + this.y + offset1Y);
67511
+ }
67512
+ canvas.moveTo(cx + this.x + offset2X, cy + this.y + offset2Y);
67513
+ if (bracketsAsArcs) {
67514
+ canvas.quadraticCurveTo(cx + this.x + (endX + offset2X) / 2, cy + this.y + offset2Y, cx + this.x + endX, cy + this.y + angleEndY);
67515
+ }
67516
+ else {
67517
+ canvas.lineTo(cx + this.x + endX, cy + this.y + endY);
67518
+ canvas.lineTo(cx + this.x + endX, cy + this.y + angleEndY);
67519
+ }
67520
+ canvas.stroke();
67521
+ }
67522
+ //
67523
+ // Draw the string
67524
+ canvas.fillMusicFontSymbols(cx + this.x + middleX, cy + this.y + middleY + size * 0.5, 1, s, true);
67321
67525
  }
67526
+ canvas.textAlign = oldAlign;
67527
+ canvas.textBaseline = oldBaseLine;
67528
+ canvas.lineWidth = l;
67322
67529
  }
67323
- super.doLayout();
67324
- if (this.container.beat.isEmpty) {
67325
- this.onTimeX = this.width / 2;
67326
- }
67327
- else if (this.noteHeads) {
67328
- this.onTimeX = this.noteHeads.x + this.noteHeads.width / 2;
67329
- }
67330
- else if (this.deadSlapped) {
67331
- this.onTimeX = this.deadSlapped.x + this.deadSlapped.width / 2;
67530
+ finally {
67531
+ _?.[Symbol.dispose]?.();
67332
67532
  }
67333
- this.middleX = this.onTimeX;
67334
- this.stemX = this.middleX;
67335
67533
  }
67336
- }
67337
-
67338
- /**
67339
- * @internal
67340
- */
67341
- class TabTieGlyph extends NoteTieGlyph {
67342
- calculateTieDirection() {
67343
- if (this.isLeftHandTap) {
67344
- return BeamDirection.Up;
67534
+ paintBeams(cx, cy, canvas, flagsElement, beamsElement) {
67535
+ for (const v of this.voiceContainer.voiceDrawOrder) {
67536
+ for (const h of this.helpers.beamHelpers[v]) {
67537
+ this.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
67538
+ }
67345
67539
  }
67346
- return TabTieGlyph.getBeamDirectionForNote(this.startNote);
67347
- }
67348
- static getBeamDirectionForNote(note) {
67349
- return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
67350
67540
  }
67351
- }
67352
-
67353
- /**
67354
- * @internal
67355
- */
67356
- class TabSlurGlyph extends TabTieGlyph {
67357
- _forSlide;
67358
- constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
67359
- super(slurEffectId, startNote, endNote, forEnd);
67360
- this._forSlide = forSlide;
67541
+ drawBeamHelperAsFlags(h) {
67542
+ return h.beats.length === 1;
67361
67543
  }
67362
- getTieHeight(startX, _startY, endX, _endY) {
67363
- return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
67544
+ hasFlag(beat) {
67545
+ if (beat.isRest) {
67546
+ return false;
67547
+ }
67548
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
67549
+ if (helper) {
67550
+ return helper.hasFlag(this.drawBeamHelperAsFlags(helper), beat);
67551
+ }
67552
+ return BeamingHelper.beatHasFlag(beat);
67364
67553
  }
67365
- tryExpand(startNote, endNote, forSlide, forEnd) {
67366
- // same type required
67367
- if (this._forSlide !== forSlide) {
67554
+ hasStem(beat) {
67555
+ if (beat.isRest) {
67368
67556
  return false;
67369
67557
  }
67370
- // same start and endbeat
67371
- if (this.startNote.beat.id !== startNote.beat.id) {
67558
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
67559
+ if (helper) {
67560
+ return helper.hasStem(this.drawBeamHelperAsFlags(helper), beat);
67561
+ }
67562
+ return BeamingHelper.beatHasStem(beat);
67563
+ }
67564
+ paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
67565
+ canvas.color = h.voice.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
67566
+ if (this.shouldPaintBeamingHelper(h)) {
67567
+ if (this.drawBeamHelperAsFlags(h)) {
67568
+ this.paintFlag(cx, cy, canvas, h, flagsElement);
67569
+ }
67570
+ else {
67571
+ this.paintBar(cx, cy, canvas, h, beamsElement);
67572
+ }
67573
+ }
67574
+ }
67575
+ shouldPaintBeamingHelper(h) {
67576
+ return !h.isRestBeamHelper;
67577
+ }
67578
+ shouldPaintFlag(beat) {
67579
+ // no flags for bend grace beats
67580
+ if (beat.graceType === GraceType.BendGrace) {
67372
67581
  return false;
67373
67582
  }
67374
- if (this.endNote.beat.id !== endNote.beat.id) {
67583
+ if (beat.deadSlapped) {
67375
67584
  return false;
67376
67585
  }
67377
- const isForEnd = this.renderer === this.lookupEndBeatRenderer();
67378
- if (isForEnd !== forEnd) {
67586
+ // no flags for any grace notes on songbook mode
67587
+ if (beat.graceType !== GraceType.None && this.settings.notation.notationMode === NotationMode.SongBook) {
67379
67588
  return false;
67380
67589
  }
67381
- // same draw direction
67382
- if (this.tieDirection !== TabTieGlyph.getBeamDirectionForNote(startNote)) {
67590
+ // only flags for durations with stems
67591
+ if (beat.duration === Duration.Whole ||
67592
+ beat.duration === Duration.DoubleWhole ||
67593
+ beat.duration === Duration.QuadrupleWhole) {
67383
67594
  return false;
67384
67595
  }
67385
- // if we can expand, expand in correct direction
67386
- switch (this.tieDirection) {
67387
- case BeamDirection.Up:
67388
- if (startNote.realValue > this.startNote.realValue) {
67389
- this.startNote = startNote;
67596
+ return true;
67597
+ }
67598
+ paintFlag(cx, cy, canvas, h, flagsElement) {
67599
+ for (const beat of h.beats) {
67600
+ if (!this.shouldPaintFlag(beat)) {
67601
+ continue;
67602
+ }
67603
+ const isGrace = beat.graceType !== GraceType.None;
67604
+ //
67605
+ // draw line
67606
+ //
67607
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
67608
+ const direction = this.getBeamDirection(h);
67609
+ const topY = cy + this.y + this.getFlagTopY(beat, direction);
67610
+ const bottomY = cy + this.y + this.getFlagBottomY(beat, direction);
67611
+ let flagY = 0;
67612
+ if (direction === BeamDirection.Down) {
67613
+ flagY = bottomY;
67614
+ }
67615
+ else {
67616
+ flagY = topY;
67617
+ }
67618
+ if (!h.hasStem(true, beat)) {
67619
+ continue;
67620
+ }
67621
+ this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, topY, bottomY, canvas);
67622
+ const _ = ElementStyleHelper.beat(canvas, flagsElement, beat);
67623
+ try {
67624
+ let flagWidth = 0;
67625
+ //
67626
+ // Draw flag
67627
+ //
67628
+ if (h.hasFlag(true, beat)) {
67629
+ const glyph = new FlagGlyph(cx + this.x + beatLineX, flagY, beat.duration, direction, isGrace);
67630
+ glyph.renderer = this;
67631
+ glyph.doLayout();
67632
+ glyph.paint(0, 0, canvas);
67633
+ flagWidth = glyph.width / 2;
67390
67634
  }
67391
- if (endNote.realValue > this.endNote.realValue) {
67392
- this.endNote = endNote;
67635
+ if (beat.graceType === GraceType.BeforeBeat) {
67636
+ if (direction === BeamDirection.Down) {
67637
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, (topY + bottomY - this.smuflMetrics.glyphHeights.get(MusicFontSymbol.GraceNoteSlashStemDown)) /
67638
+ 2, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemDown, true);
67639
+ }
67640
+ else {
67641
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, (topY + bottomY + this.smuflMetrics.glyphHeights.get(MusicFontSymbol.GraceNoteSlashStemUp)) /
67642
+ 2, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemUp, true);
67643
+ }
67393
67644
  }
67645
+ }
67646
+ finally {
67647
+ _?.[Symbol.dispose]?.();
67648
+ }
67649
+ }
67650
+ }
67651
+ getFlagStemSize(duration, forceMinStem = false) {
67652
+ let size = 0;
67653
+ switch (duration) {
67654
+ case Duration.QuadrupleWhole:
67655
+ case Duration.Half:
67656
+ case Duration.Quarter:
67657
+ case Duration.Eighth:
67658
+ case Duration.Sixteenth:
67659
+ case Duration.ThirtySecond:
67660
+ case Duration.SixtyFourth:
67661
+ case Duration.OneHundredTwentyEighth:
67662
+ case Duration.TwoHundredFiftySixth:
67663
+ size = this.smuflMetrics.standardStemLength + this.smuflMetrics.stemFlagOffsets.get(duration);
67394
67664
  break;
67395
- case BeamDirection.Down:
67396
- if (startNote.realValue < this.startNote.realValue) {
67397
- this.startNote = startNote;
67398
- }
67399
- if (endNote.realValue < this.endNote.realValue) {
67400
- this.endNote = endNote;
67401
- }
67665
+ default:
67666
+ size = forceMinStem ? this.smuflMetrics.standardStemLength : 0;
67402
67667
  break;
67403
67668
  }
67404
- return true;
67669
+ return size;
67405
67670
  }
67406
- }
67407
-
67408
- /**
67409
- * @internal
67410
- */
67411
- class NumberedSlurGlyph extends TabSlurGlyph {
67412
- calculateTieDirection() {
67413
- return BeamDirection.Up;
67671
+ recreatePreBeatGlyphs() {
67672
+ this._startSpacing = false;
67673
+ super.recreatePreBeatGlyphs();
67414
67674
  }
67415
- }
67416
-
67417
- /**
67418
- * @internal
67419
- */
67420
- class NumberedBeatContainerGlyph extends BeatContainerGlyph {
67421
- _slurs = new Map();
67422
- _effectSlurs = [];
67423
- constructor(beat) {
67424
- super(beat);
67425
- this.preNotes = new NumberedBeatPreNotesGlyph();
67426
- this.onNotes = new NumberedBeatGlyph();
67675
+ calculateBeamY(h, x) {
67676
+ return this.calculateBeamYWithDirection(h, x, this.getBeamDirection(h));
67427
67677
  }
67428
- doLayout() {
67429
- this._slurs.clear();
67430
- this._effectSlurs = [];
67431
- super.doLayout();
67678
+ createPreBeatGlyphs() {
67679
+ super.createPreBeatGlyphs();
67680
+ this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
67681
+ this.createLinePreBeatGlyphs();
67682
+ this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
67432
67683
  }
67433
- createTies(n) {
67434
- // create a tie if any effect requires it
67435
- if (!n.isVisible) {
67436
- return;
67437
- }
67438
- if (n.isTieOrigin && n.tieDestination.isVisible && !this._slurs.has('numbered.tie')) {
67439
- const tie = new NumberedTieGlyph(`numbered.tie.${n.beat.id}`, n, n.tieDestination, false);
67440
- this.addTie(tie);
67441
- this._slurs.set(tie.slurEffectId, tie);
67442
- }
67443
- if (n.isTieDestination) {
67444
- const tie = new NumberedTieGlyph(`numbered.tie.${n.tieOrigin.beat.id}`, n.tieOrigin, n, true);
67445
- this.addTie(tie);
67446
- }
67447
- if (n.isLeftHandTapped &&
67448
- !n.isHammerPullDestination &&
67449
- !this._slurs.has(`numbered.tie.leftHandTap.${n.beat.id}`)) {
67450
- const tapSlur = new NumberedTieGlyph(`numbered.tie.leftHandTap.${n.beat.id}`, n, n, false);
67451
- this.addTie(tapSlur);
67452
- this._slurs.set(tapSlur.slurEffectId, tapSlur);
67684
+ createPostBeatGlyphs() {
67685
+ super.createPostBeatGlyphs();
67686
+ const lastBar = this.lastBar;
67687
+ this.addPostBeatGlyph(new BarLineGlyph(true, this.bar.staff.track.score.stylesheet.extendBarLines));
67688
+ if (lastBar.masterBar.isRepeatEnd && lastBar.masterBar.repeatCount > 2) {
67689
+ this.addPostBeatGlyph(new RepeatCountGlyph(0, this.getLineHeight(-0.5), this.bar.masterBar.repeatCount));
67453
67690
  }
67454
- // start effect slur on first beat
67455
- if (n.isEffectSlurOrigin && n.effectSlurDestination) {
67456
- let expanded = false;
67457
- for (const slur of this._effectSlurs) {
67458
- if (slur.tryExpand(n, n.effectSlurDestination, false, false)) {
67459
- expanded = true;
67460
- break;
67461
- }
67462
- }
67463
- if (!expanded) {
67464
- const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n, n.effectSlurDestination, false, false);
67465
- this._effectSlurs.push(effectSlur);
67466
- this.addTie(effectSlur);
67467
- this._slurs.set(effectSlur.slurEffectId, effectSlur);
67468
- this._slurs.set('numbered.slur.effect', effectSlur);
67469
- }
67691
+ }
67692
+ paintBar(cx, cy, canvas, h, beamsElement) {
67693
+ const direction = this.getBeamDirection(h);
67694
+ const isGrace = h.graceType !== GraceType.None;
67695
+ const scaleMod = isGrace ? EngravingSettings.GraceScale : 1;
67696
+ let barSpacing = (this.beamSpacing + this.beamThickness) * scaleMod;
67697
+ let barSize = this.beamThickness * scaleMod;
67698
+ if (direction === BeamDirection.Down) {
67699
+ barSpacing = -barSpacing;
67700
+ barSize = -barSize;
67470
67701
  }
67471
- // end effect slur on last beat
67472
- if (n.isEffectSlurDestination && n.effectSlurOrigin) {
67473
- let expanded = false;
67474
- for (const slur of this._effectSlurs) {
67475
- if (slur.tryExpand(n.effectSlurOrigin, n, false, true)) {
67476
- expanded = true;
67477
- break;
67478
- }
67479
- }
67480
- if (!expanded) {
67481
- const effectSlur = new NumberedSlurGlyph(`numbered.slur.effect`, n.effectSlurOrigin, n, false, true);
67482
- this._effectSlurs.push(effectSlur);
67483
- this.addTie(effectSlur);
67484
- this._slurs.set(effectSlur.slurEffectId, effectSlur);
67485
- this._slurs.set('numbered.slur.effect', effectSlur);
67702
+ for (let i = 0, j = h.beats.length; i < j; i++) {
67703
+ const beat = h.beats[i];
67704
+ if (beat.deadSlapped) {
67705
+ continue;
67486
67706
  }
67487
- }
67488
- }
67489
- }
67490
-
67491
- /**
67492
- * @internal
67493
- */
67494
- class NumberedKeySignatureGlyph extends Glyph {
67495
- _keySignature;
67496
- _keySignatureType;
67497
- _text = '';
67498
- _accidental = AccidentalType.None;
67499
- _accidentalOffset = 0;
67500
- constructor(x, y, keySignature, keySignatureType) {
67501
- super(x, y);
67502
- this._keySignature = keySignature;
67503
- this._keySignatureType = keySignatureType;
67504
- }
67505
- doLayout() {
67506
- super.doLayout();
67507
- const text = '1 = ';
67508
- let text2 = '';
67509
- let accidental = AccidentalType.None;
67510
- switch (this._keySignatureType) {
67511
- case KeySignatureType.Major:
67512
- switch (this._keySignature) {
67513
- case KeySignature.Cb:
67514
- text2 = ' C';
67515
- accidental = AccidentalType.Flat;
67516
- break;
67517
- case KeySignature.Gb:
67518
- text2 = ' G';
67519
- accidental = AccidentalType.Flat;
67520
- break;
67521
- case KeySignature.Db:
67522
- text2 = ' D';
67523
- accidental = AccidentalType.Flat;
67524
- break;
67525
- case KeySignature.Ab:
67526
- text2 = ' A';
67527
- accidental = AccidentalType.Flat;
67528
- break;
67529
- case KeySignature.Eb:
67530
- text2 = ' E';
67531
- accidental = AccidentalType.Flat;
67532
- break;
67533
- case KeySignature.Bb:
67534
- text2 = ' B';
67535
- accidental = AccidentalType.Flat;
67536
- break;
67537
- case KeySignature.F:
67538
- text2 = 'F';
67539
- break;
67540
- case KeySignature.C:
67541
- text2 = 'C';
67542
- accidental = AccidentalType.None;
67543
- break;
67544
- case KeySignature.G:
67545
- text2 = 'G';
67546
- accidental = AccidentalType.None;
67547
- break;
67548
- case KeySignature.D:
67549
- text2 = 'D';
67550
- accidental = AccidentalType.None;
67551
- break;
67552
- case KeySignature.A:
67553
- text2 = 'A';
67554
- accidental = AccidentalType.None;
67555
- break;
67556
- case KeySignature.E:
67557
- text2 = 'E';
67558
- accidental = AccidentalType.None;
67559
- break;
67560
- case KeySignature.B:
67561
- text2 = 'B';
67562
- accidental = AccidentalType.None;
67563
- break;
67564
- case KeySignature.FSharp:
67565
- text2 = ' F';
67566
- accidental = AccidentalType.Sharp;
67567
- break;
67568
- case KeySignature.CSharp:
67569
- text2 = ' C';
67570
- accidental = AccidentalType.Sharp;
67571
- break;
67572
- }
67573
- break;
67574
- case KeySignatureType.Minor:
67575
- switch (this._keySignature) {
67576
- case KeySignature.Cb:
67577
- text2 = ' a';
67578
- accidental = AccidentalType.Flat;
67579
- break;
67580
- case KeySignature.Gb:
67581
- text2 = ' e';
67582
- accidental = AccidentalType.Flat;
67583
- break;
67584
- case KeySignature.Db:
67585
- text2 = ' b';
67586
- accidental = AccidentalType.Flat;
67587
- break;
67588
- case KeySignature.Ab:
67589
- text2 = 'f';
67590
- accidental = AccidentalType.None;
67591
- break;
67592
- case KeySignature.Eb:
67593
- text2 = 'c';
67594
- accidental = AccidentalType.None;
67595
- break;
67596
- case KeySignature.Bb:
67597
- text2 = 'g';
67598
- accidental = AccidentalType.None;
67599
- break;
67600
- case KeySignature.F:
67601
- text2 = 'd';
67602
- break;
67603
- case KeySignature.C:
67604
- text2 = 'a';
67605
- accidental = AccidentalType.None;
67606
- break;
67607
- case KeySignature.G:
67608
- text2 = 'e';
67609
- accidental = AccidentalType.None;
67610
- break;
67611
- case KeySignature.D:
67612
- text2 = 'b';
67613
- accidental = AccidentalType.None;
67614
- break;
67615
- case KeySignature.A:
67616
- text2 = ' f';
67617
- accidental = AccidentalType.Sharp;
67618
- break;
67619
- case KeySignature.E:
67620
- text2 = ' c';
67621
- accidental = AccidentalType.Sharp;
67622
- break;
67623
- case KeySignature.B:
67624
- text2 = ' g';
67625
- accidental = AccidentalType.Sharp;
67626
- break;
67627
- case KeySignature.FSharp:
67628
- text2 = ' d';
67629
- accidental = AccidentalType.Sharp;
67630
- break;
67631
- case KeySignature.CSharp:
67632
- text2 = ' a';
67633
- accidental = AccidentalType.Sharp;
67634
- break;
67707
+ const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
67708
+ const y1 = cy + this.y + this.getBarLineStart(beat, direction);
67709
+ // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
67710
+ // when combining stems and beams on sub-pixel level
67711
+ const y2 = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
67712
+ if (y1 < y2) {
67713
+ this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, y1, y2, canvas);
67714
+ }
67715
+ else {
67716
+ this.paintBeamingStem(beat, cy + this.y, cx + this.x + beatLineX, y2, y1, canvas);
67717
+ }
67718
+ const _ = ElementStyleHelper.beat(canvas, beamsElement, beat);
67719
+ try {
67720
+ const brokenBarOffset = this.smuflMetrics.brokenBeamWidth * scaleMod;
67721
+ const barCount = ModelUtils.getIndex(beat.duration) - 2;
67722
+ const barStart = cy + this.y;
67723
+ for (let barIndex = 0; barIndex < barCount; barIndex++) {
67724
+ let barStartX = 0;
67725
+ let barEndX = 0;
67726
+ let barStartY = 0;
67727
+ let barEndY = 0;
67728
+ const barY = barStart + barIndex * barSpacing;
67729
+ //
67730
+ // Bar to Next?
67731
+ //
67732
+ if (i < h.beats.length - 1) {
67733
+ const isFullBarJoin = BeamingHelper.isFullBarJoin(beat, h.beats[i + 1], barIndex);
67734
+ // force two broken bars on secondary (last) beam?
67735
+ if (barIndex === barCount - 1 &&
67736
+ isFullBarJoin &&
67737
+ beat.beamingMode === BeatBeamingMode.ForceSplitOnSecondaryToNext) {
67738
+ // start part
67739
+ barStartX = beatLineX;
67740
+ barEndX = barStartX + brokenBarOffset;
67741
+ barStartY = barY + this.calculateBeamY(h, barStartX);
67742
+ barEndY = barY + this.calculateBeamY(h, barEndX);
67743
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
67744
+ // end part
67745
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
67746
+ barStartX = barEndX - brokenBarOffset;
67747
+ barStartY = barY + this.calculateBeamY(h, barStartX);
67748
+ barEndY = barY + this.calculateBeamY(h, barEndX);
67749
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
67750
+ }
67751
+ else {
67752
+ if (isFullBarJoin) {
67753
+ // full bar?
67754
+ barStartX = beatLineX;
67755
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.Stem);
67756
+ }
67757
+ else if (i === 0 || !BeamingHelper.isFullBarJoin(h.beats[i - 1], beat, barIndex)) {
67758
+ barStartX = beatLineX;
67759
+ barEndX = barStartX + brokenBarOffset;
67760
+ }
67761
+ else {
67762
+ continue;
67763
+ }
67764
+ barStartY = barY + this.calculateBeamY(h, barStartX);
67765
+ barEndY = barY + this.calculateBeamY(h, barEndX);
67766
+ // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
67767
+ // when combining stems and beams on sub-pixel level
67768
+ if (barIndex === 0) {
67769
+ barStartY = barStartY | 0;
67770
+ barEndY = barEndY | 0;
67771
+ }
67772
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
67773
+ }
67774
+ }
67775
+ else if (i > 0 && !BeamingHelper.isFullBarJoin(beat, h.beats[i - 1], barIndex)) {
67776
+ barStartX = beatLineX - brokenBarOffset;
67777
+ barEndX = beatLineX;
67778
+ barEndX = beatLineX;
67779
+ barStartY = barY + this.calculateBeamY(h, barStartX);
67780
+ barEndY = barY + this.calculateBeamY(h, barEndX);
67781
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barStartY, cx + this.x + barEndX, barEndY, barSize);
67782
+ }
67635
67783
  }
67636
- break;
67637
- }
67638
- this._text = text + text2;
67639
- this._accidental = accidental;
67640
- const c = this.renderer.scoreRenderer.canvas;
67641
- const res = this.renderer.resources;
67642
- c.font = res.numberedNotationFont;
67643
- this._accidentalOffset = c.measureText(text).width;
67644
- this.width = c.measureText(text + text2).width;
67645
- }
67646
- paint(cx, cy, canvas) {
67647
- const _ = ElementStyleHelper.bar(canvas, BarSubElement.NumberedKeySignature, this.renderer.bar);
67648
- try {
67649
- const res = this.renderer.resources;
67650
- canvas.font = res.numberedNotationFont;
67651
- canvas.textBaseline = TextBaseline.Middle;
67652
- canvas.fillText(this._text, cx + this.x, cy + this.y);
67653
- if (this._accidental !== AccidentalType.None) {
67654
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + this._accidentalOffset, cy + this.y, 1, AccidentalGlyph.getMusicSymbol(this._accidental), false);
67784
+ }
67785
+ finally {
67786
+ _?.[Symbol.dispose]?.();
67655
67787
  }
67656
67788
  }
67657
- finally {
67658
- _?.[Symbol.dispose]?.();
67789
+ if (h.graceType === GraceType.BeforeBeat) {
67790
+ const beatLineX = this.getBeatX(h.beats[0], BeatXPosition.Stem);
67791
+ const flagWidth = this.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
67792
+ let slashY = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
67793
+ slashY += barSize + barSpacing;
67794
+ if (direction === BeamDirection.Down) {
67795
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, slashY, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemDown, true);
67796
+ }
67797
+ else {
67798
+ CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + beatLineX + flagWidth / 2, slashY, EngravingSettings.GraceScale, MusicFontSymbol.GraceNoteSlashStemUp, true);
67799
+ }
67659
67800
  }
67660
67801
  }
67661
- }
67662
-
67663
- /**
67664
- * @internal
67665
- */
67666
- class GhostParenthesisGlyph extends Glyph {
67667
- _isOpen;
67668
- colorOverride;
67669
- constructor(isOpen) {
67670
- super(0, 0);
67671
- this._isOpen = isOpen;
67672
- }
67673
- doLayout() {
67674
- super.doLayout();
67675
- this.width =
67676
- this.renderer.smuflMetrics.ghostParenthesisWidth + this.renderer.smuflMetrics.ghostParenthesisPadding;
67802
+ static paintSingleBar(canvas, x1, y1, x2, y2, size) {
67803
+ canvas.beginPath();
67804
+ canvas.moveTo(x1, y1);
67805
+ canvas.lineTo(x2, y2);
67806
+ canvas.lineTo(x2, y2 + size);
67807
+ canvas.lineTo(x1, y1 + size);
67808
+ canvas.closePath();
67809
+ canvas.fill();
67677
67810
  }
67678
- paint(cx, cy, canvas) {
67679
- const c = canvas.color;
67680
- if (this.colorOverride) {
67681
- canvas.color = this.colorOverride;
67811
+ calculateBeamingOverflows(rendererTop, rendererBottom) {
67812
+ let maxNoteY = 0;
67813
+ let minNoteY = 0;
67814
+ const noteOverflowPadding = this.getLineHeight(0.5);
67815
+ for (const v of this.helpers.beamHelpers) {
67816
+ for (const h of v) {
67817
+ if (!this.shouldPaintBeamingHelper(h)) ;
67818
+ else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
67819
+ const tupletDirection = this.getTupletBeamDirection(h);
67820
+ if (h.direction === BeamDirection.Up) {
67821
+ let topY = this.getFlagTopY(h.beats[0], h.direction);
67822
+ if (h.hasTuplet && tupletDirection === h.direction) {
67823
+ topY -= this.tupletSize + this.tupletOffset;
67824
+ }
67825
+ if (topY < maxNoteY) {
67826
+ maxNoteY = topY;
67827
+ }
67828
+ if (h.hasTuplet && tupletDirection !== h.direction) {
67829
+ let bottomY = this.getFlagBottomY(h.beats[0], tupletDirection);
67830
+ bottomY += this.tupletSize + this.tupletOffset;
67831
+ if (bottomY > minNoteY) {
67832
+ minNoteY = bottomY;
67833
+ }
67834
+ }
67835
+ }
67836
+ else {
67837
+ let bottomY = this.getFlagBottomY(h.beats[0], h.direction);
67838
+ if (h.hasTuplet && tupletDirection === h.direction) {
67839
+ bottomY += this.tupletSize + this.tupletOffset;
67840
+ }
67841
+ if (bottomY > minNoteY) {
67842
+ minNoteY = bottomY;
67843
+ }
67844
+ if (h.hasTuplet && tupletDirection !== h.direction) {
67845
+ let topY = this.getFlagTopY(h.beats[0], tupletDirection);
67846
+ topY -= this.tupletSize + this.tupletOffset;
67847
+ if (topY < maxNoteY) {
67848
+ maxNoteY = topY;
67849
+ }
67850
+ }
67851
+ }
67852
+ }
67853
+ else {
67854
+ this.ensureBeamDrawingInfo(h, h.direction);
67855
+ const drawingInfo = h.drawingInfos.get(h.direction);
67856
+ const tupletDirection = this.getTupletBeamDirection(h);
67857
+ if (h.direction === BeamDirection.Up) {
67858
+ let topY = Math.min(drawingInfo.startY, drawingInfo.endY);
67859
+ if (h.hasTuplet && tupletDirection === h.direction) {
67860
+ topY -= this.tupletSize + this.tupletOffset;
67861
+ }
67862
+ if (topY < maxNoteY) {
67863
+ maxNoteY = topY;
67864
+ }
67865
+ let bottomY = this.getBarLineStart(h.beatOfLowestNote, h.direction) + noteOverflowPadding;
67866
+ if (h.hasTuplet && tupletDirection !== h.direction) {
67867
+ bottomY += this.tupletSize + this.tupletOffset;
67868
+ }
67869
+ if (bottomY > minNoteY) {
67870
+ minNoteY = bottomY;
67871
+ }
67872
+ }
67873
+ else {
67874
+ let bottomY = Math.max(drawingInfo.startY, drawingInfo.endY);
67875
+ if (h.hasTuplet && tupletDirection === h.direction) {
67876
+ bottomY += this.tupletSize + this.tupletOffset;
67877
+ }
67878
+ if (bottomY > minNoteY) {
67879
+ minNoteY = bottomY;
67880
+ }
67881
+ let topY = this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
67882
+ if (h.hasTuplet && tupletDirection !== h.direction) {
67883
+ topY -= this.tupletSize + this.tupletOffset;
67884
+ }
67885
+ if (topY < maxNoteY) {
67886
+ maxNoteY = topY;
67887
+ }
67888
+ }
67889
+ }
67890
+ }
67682
67891
  }
67683
- if (this._isOpen) {
67684
- TieGlyph.paintTie(canvas, 1, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisWidth, cy + this.y + this.height, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisWidth, cy + this.y, false, this.renderer.smuflMetrics.ghostParenthesisWidth / 2, this.renderer.smuflMetrics.tieMidpointThickness);
67892
+ if (maxNoteY < rendererTop) {
67893
+ this.registerOverflowTop(Math.abs(maxNoteY));
67685
67894
  }
67686
- else {
67687
- TieGlyph.paintTie(canvas, 1, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisPadding, cy + this.y, cx + this.x + this.renderer.smuflMetrics.ghostParenthesisPadding, cy + this.y + this.height, false, this.renderer.smuflMetrics.ghostParenthesisWidth / 2, this.renderer.smuflMetrics.tieMidpointThickness);
67895
+ if (minNoteY > rendererBottom) {
67896
+ this.registerOverflowBottom(Math.abs(minNoteY) - rendererBottom);
67688
67897
  }
67689
- canvas.color = c;
67690
- }
67691
- }
67692
-
67693
- /**
67694
- * @internal
67695
- */
67696
- class TimeSignatureGlyph extends GlyphGroup {
67697
- _numerator = 0;
67698
- _denominator = 0;
67699
- _isCommon;
67700
- _isFreeTime;
67701
- barSubElement = BarSubElement.StandardNotationTimeSignature;
67702
- constructor(x, y, numerator, denominator, isCommon, isFreeTime) {
67703
- super(x, y);
67704
- this._numerator = numerator;
67705
- this._denominator = denominator;
67706
- this._isCommon = isCommon;
67707
- this._isFreeTime = isFreeTime;
67708
67898
  }
67709
- paint(cx, cy, canvas) {
67710
- const _ = ElementStyleHelper.bar(canvas, this.barSubElement, this.renderer.bar);
67711
- try {
67712
- super.paint(cx, cy, canvas);
67899
+ initializeBeamDrawingInfo(h, direction) {
67900
+ const drawingInfo = new BeamingHelperDrawInfo();
67901
+ const firstBeat = h.beats[0];
67902
+ const lastBeat = h.beats[h.beats.length - 1];
67903
+ // 1. put direct diagonal line.
67904
+ drawingInfo.startBeat = firstBeat;
67905
+ drawingInfo.startX = this.getBeatX(firstBeat, BeatXPosition.Stem);
67906
+ drawingInfo.startY =
67907
+ direction === BeamDirection.Up
67908
+ ? this.getFlagTopY(firstBeat, direction)
67909
+ : this.getFlagBottomY(firstBeat, direction);
67910
+ drawingInfo.endBeat = lastBeat;
67911
+ drawingInfo.endX = this.getBeatX(lastBeat, BeatXPosition.Stem);
67912
+ drawingInfo.endY =
67913
+ direction === BeamDirection.Up
67914
+ ? this.getFlagTopY(lastBeat, direction)
67915
+ : this.getFlagBottomY(lastBeat, direction);
67916
+ // 2. ensure max slope
67917
+ // we use the min/max notes to place the beam along their real position
67918
+ // we only want a maximum of 10 offset for their gradient
67919
+ const maxSlope = this.smuflMetrics.oneStaffSpace;
67920
+ if (direction === BeamDirection.Down &&
67921
+ drawingInfo.startY > drawingInfo.endY &&
67922
+ drawingInfo.startY - drawingInfo.endY > maxSlope) {
67923
+ drawingInfo.endY = drawingInfo.startY - maxSlope;
67713
67924
  }
67714
- finally {
67715
- _?.[Symbol.dispose]?.();
67925
+ if (direction === BeamDirection.Down &&
67926
+ drawingInfo.endY > drawingInfo.startY &&
67927
+ drawingInfo.endY - drawingInfo.startY > maxSlope) {
67928
+ drawingInfo.startY = drawingInfo.endY - maxSlope;
67716
67929
  }
67717
- }
67718
- doLayout() {
67719
- if (this._isCommon && this._numerator === 2 && this._denominator === 2) {
67720
- const common = new MusicFontGlyph(0, 0, this.commonScale, MusicFontSymbol.TimeSigCutCommon);
67721
- this.addGlyph(common);
67722
- super.doLayout();
67930
+ if (direction === BeamDirection.Up &&
67931
+ drawingInfo.startY < drawingInfo.endY &&
67932
+ drawingInfo.endY - drawingInfo.startY > maxSlope) {
67933
+ drawingInfo.endY = drawingInfo.startY + maxSlope;
67723
67934
  }
67724
- else if (this._isCommon && this._numerator === 4 && this._denominator === 4) {
67725
- const common = new MusicFontGlyph(0, 0, this.commonScale, MusicFontSymbol.TimeSigCommon);
67726
- this.addGlyph(common);
67727
- super.doLayout();
67935
+ if (direction === BeamDirection.Up &&
67936
+ drawingInfo.endY < drawingInfo.startY &&
67937
+ drawingInfo.startY - drawingInfo.endY > maxSlope) {
67938
+ drawingInfo.startY = drawingInfo.endY + maxSlope;
67728
67939
  }
67729
- else {
67730
- const numerator = new NumberGlyph(0, 0, this._numerator, TextBaseline.Top, this.numberScale);
67731
- const denominator = new NumberGlyph(0, 0, this._denominator, TextBaseline.Bottom, this.numberScale);
67732
- this.addGlyph(numerator);
67733
- this.addGlyph(denominator);
67734
- super.doLayout();
67735
- const glyphSpace = this.width;
67736
- numerator.x = (glyphSpace - numerator.width) / 2;
67737
- denominator.x = (glyphSpace - denominator.width) / 2;
67738
- this.width = Math.max(numerator.x + numerator.width, denominator.x + denominator.width);
67940
+ return drawingInfo;
67941
+ }
67942
+ get beamSpacing() {
67943
+ return this.smuflMetrics.beamSpacing;
67944
+ }
67945
+ get beamThickness() {
67946
+ return this.smuflMetrics.beamThickness;
67947
+ }
67948
+ ensureBeamDrawingInfo(h, direction) {
67949
+ if (h.drawingInfos.has(direction)) {
67950
+ return;
67739
67951
  }
67740
- if (this._isFreeTime) {
67741
- const numberHeight = this.renderer.smuflMetrics.oneStaffSpace * 2;
67742
- const openParenthesis = new GhostParenthesisGlyph(true);
67743
- openParenthesis.renderer = this.renderer;
67744
- openParenthesis.y = -numberHeight;
67745
- openParenthesis.height = numberHeight * 2;
67746
- openParenthesis.doLayout();
67747
- for (const g of this.glyphs) {
67748
- g.x += openParenthesis.width;
67952
+ // the beaming logic works like this:
67953
+ // 1. we take the first and last note, add the stem, and put a diagnal line between them.
67954
+ // 2. the height of the diagonal line must not exceed a max height,
67955
+ // - if this is the case, the line on the more distant note just gets longer
67956
+ // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
67957
+ const drawingInfo = this.initializeBeamDrawingInfo(h, direction);
67958
+ h.drawingInfos.set(direction, drawingInfo);
67959
+ const isRest = h.isRestBeamHelper;
67960
+ const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
67961
+ const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
67962
+ // 3. adjust beam drawing order
67963
+ // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
67964
+ // here we shift accordingly
67965
+ let barDrawingShift = 0;
67966
+ if (barCount > 2 && !isRest) {
67967
+ const beamSpacing = this.beamSpacing * scale;
67968
+ const beamThickness = this.beamThickness * scale;
67969
+ const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
67970
+ if (direction === BeamDirection.Up) {
67971
+ const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
67972
+ const barTopY = bottomBarY - totalBarsHeight;
67973
+ const diff = drawingInfo.startY - barTopY;
67974
+ if (diff > 0) {
67975
+ barDrawingShift = diff * -1;
67976
+ drawingInfo.startY -= diff;
67977
+ drawingInfo.endY -= diff;
67978
+ }
67979
+ }
67980
+ else {
67981
+ const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
67982
+ const barBottomY = topBarY + totalBarsHeight;
67983
+ const diff = barBottomY - drawingInfo.startY;
67984
+ if (diff > 0) {
67985
+ barDrawingShift = diff;
67986
+ drawingInfo.startY += diff;
67987
+ drawingInfo.endY += diff;
67988
+ }
67989
+ }
67990
+ }
67991
+ // 4. let middle elements shift up/down
67992
+ if (h.beats.length > 1) {
67993
+ // check if highest note shifts bar up or down
67994
+ if (direction === BeamDirection.Up) {
67995
+ const yNeededForHighestNote = barDrawingShift + this.getFlagTopY(h.beatOfHighestNote, direction);
67996
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfHighestNote, BeatXPosition.Stem));
67997
+ const diff = yGivenByCurrentValues - yNeededForHighestNote;
67998
+ if (diff > 0) {
67999
+ drawingInfo.startY -= diff;
68000
+ drawingInfo.endY -= diff;
68001
+ }
68002
+ }
68003
+ else {
68004
+ const yNeededForLowestNote = barDrawingShift + this.getFlagBottomY(h.beatOfLowestNote, direction);
68005
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(h.beatOfLowestNote, BeatXPosition.Stem));
68006
+ const diff = yNeededForLowestNote - yGivenByCurrentValues;
68007
+ if (diff > 0) {
68008
+ drawingInfo.startY += diff;
68009
+ drawingInfo.endY += diff;
68010
+ }
68011
+ }
68012
+ // check if rest shifts bar up or down
68013
+ let barSpacing = 0;
68014
+ if (h.restBeats.length > 0) {
68015
+ // space needed for the bars, rests need to be below them
68016
+ const scaleMod = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
68017
+ barSpacing = barCount * (this.beamSpacing + this.beamThickness) * scaleMod;
68018
+ }
68019
+ for (const b of h.restBeats) {
68020
+ // rest beats which are "under" the beam
68021
+ if (b.isRest && b.index < h.beats[h.beats.length - 1].index) {
68022
+ if (direction === BeamDirection.Up) {
68023
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxTop() - barSpacing;
68024
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
68025
+ const diff = yGivenByCurrentValues - yNeededForRest;
68026
+ if (diff > 0) {
68027
+ drawingInfo.startY -= diff;
68028
+ drawingInfo.endY -= diff;
68029
+ }
68030
+ }
68031
+ else if (direction === BeamDirection.Down) {
68032
+ const yNeededForRest = this.getBeatContainer(b).getBoundingBoxBottom() + barSpacing;
68033
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
68034
+ const diff = yNeededForRest - yGivenByCurrentValues;
68035
+ if (diff > 0) {
68036
+ drawingInfo.startY += diff;
68037
+ drawingInfo.endY += diff;
68038
+ }
68039
+ }
68040
+ }
68041
+ }
68042
+ // check if slash shifts bar up or down
68043
+ if (h.slashBeats.length > 0) {
68044
+ for (const b of h.slashBeats) {
68045
+ const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
68046
+ const yNeededForSlash = h.direction === BeamDirection.Up
68047
+ ? this.getFlagTopY(b, h.direction)
68048
+ : this.getFlagBottomY(b, h.direction);
68049
+ const diff = yNeededForSlash - yGivenByCurrentValues;
68050
+ if (diff > 0) {
68051
+ drawingInfo.startY += diff;
68052
+ drawingInfo.endY += diff;
68053
+ }
68054
+ }
67749
68055
  }
67750
- this.width += openParenthesis.width;
67751
- this.addGlyph(openParenthesis);
67752
- const closeParenthesis = new GhostParenthesisGlyph(false);
67753
- closeParenthesis.renderer = this.renderer;
67754
- closeParenthesis.x = this.width;
67755
- closeParenthesis.y = -numberHeight;
67756
- closeParenthesis.height = numberHeight * 2;
67757
- closeParenthesis.doLayout();
67758
- this.addGlyph(closeParenthesis);
67759
- this.width += closeParenthesis.width;
67760
68056
  }
67761
68057
  }
67762
- }
67763
-
67764
- /**
67765
- * @internal
67766
- */
67767
- class ScoreTimeSignatureGlyph extends TimeSignatureGlyph {
67768
- get commonScale() {
67769
- return 1;
68058
+ getMinLineOfBeat(_beat) {
68059
+ return 0;
67770
68060
  }
67771
- get numberScale() {
67772
- return 1;
68061
+ getMaxLineOfBeat(_beat) {
68062
+ return 0;
67773
68063
  }
67774
68064
  }
67775
68065
 
@@ -67782,27 +68072,9 @@ class NumberedBarRenderer extends LineBarRenderer {
67782
68072
  simpleWhammyOverflow = 0;
67783
68073
  _isOnlyNumbered;
67784
68074
  shortestDuration = Duration.QuadrupleWhole;
67785
- lowestOctave = null;
67786
- highestOctave = null;
67787
- octaves = new Map();
67788
68075
  get dotSpacing() {
67789
68076
  return this.smuflMetrics.glyphHeights.get(MusicFontSymbol.AugmentationDot) * 2;
67790
68077
  }
67791
- registerOctave(beat, octave) {
67792
- this.octaves.set(beat, octave);
67793
- if (this.lowestOctave === null) {
67794
- this.lowestOctave = octave;
67795
- this.highestOctave = octave;
67796
- }
67797
- else {
67798
- if (octave < this.lowestOctave) {
67799
- this.lowestOctave = octave;
67800
- }
67801
- if (octave > this.highestOctave) {
67802
- this.highestOctave = octave;
67803
- }
67804
- }
67805
- }
67806
68078
  get repeatsBarSubElement() {
67807
68079
  return BarSubElement.NumberedRepeats;
67808
68080
  }
@@ -67840,89 +68112,61 @@ class NumberedBarRenderer extends LineBarRenderer {
67840
68112
  get tupletSubElement() {
67841
68113
  return BeatSubElement.NumberedTuplet;
67842
68114
  }
67843
- doLayout() {
67844
- super.doLayout();
67845
- if (this.voiceContainer.tupletGroups.size > 0) {
67846
- this.registerOverflowTop(this.tupletSize);
67847
- }
67848
- if (!this.bar.isEmpty) {
67849
- const barCount = ModelUtils.getIndex(this.shortestDuration) - 2;
67850
- const dotSpacing = this.dotSpacing;
67851
- if (barCount > 0) {
67852
- const barSpacing = this.smuflMetrics.numberedBarRendererBarSpacing;
67853
- const barSize = this.smuflMetrics.numberedBarRendererBarSize;
67854
- const barOverflow = (barCount - 1) * barSpacing + barSize;
67855
- let dotOverflow = 0;
67856
- const lowestOctave = this.lowestOctave;
67857
- if (lowestOctave !== null) {
67858
- dotOverflow =
67859
- Math.abs(lowestOctave) * dotSpacing +
67860
- this.smuflMetrics.glyphHeights.get(MusicFontSymbol.AugmentationDot);
67861
- }
67862
- this.registerOverflowBottom(barOverflow + dotOverflow);
67863
- }
67864
- const highestOctave = this.highestOctave;
67865
- if (highestOctave !== null) {
67866
- const dotOverflow = Math.abs(highestOctave) * dotSpacing +
67867
- this.smuflMetrics.glyphHeights.get(MusicFontSymbol.AugmentationDot);
67868
- this.registerOverflowTop(dotOverflow);
67869
- }
67870
- }
68115
+ shouldPaintBeamingHelper(_h) {
68116
+ return true;
67871
68117
  }
67872
68118
  paintFlag(cx, cy, canvas, h, flagsElement) {
67873
68119
  this.paintBar(cx, cy, canvas, h, flagsElement);
67874
68120
  }
67875
68121
  paintBar(cx, cy, canvas, h, flagsElement) {
67876
- if (h.beats.length === 0) {
68122
+ if (h.beats.length === 0 || h.graceType !== GraceType.None) {
67877
68123
  return;
67878
68124
  }
67879
- const res = this.resources;
67880
68125
  for (let i = 0, j = h.beats.length; i < j; i++) {
67881
68126
  const beat = h.beats[i];
67882
68127
  const _ = ElementStyleHelper.beat(canvas, flagsElement, beat);
67883
68128
  try {
67884
- //
67885
- // draw line
67886
- //
67887
- const barSpacing = this.smuflMetrics.numberedBarRendererBarSpacing;
67888
- const barSize = this.smuflMetrics.numberedBarRendererBarSize;
67889
- const barCount = ModelUtils.getIndex(beat.duration) - 2;
67890
- const barStart = cy + this.y;
67891
- const beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes);
67892
- const beamY = this.calculateBeamY(h, beatLineX);
67893
- for (let barIndex = 0; barIndex < barCount; barIndex++) {
67894
- let barStartX = 0;
67895
- let barEndX = 0;
67896
- let barStartY = 0;
67897
- const barY = barStart + barIndex * barSpacing;
67898
- if (i === h.beats.length - 1) {
67899
- barStartX = beatLineX;
67900
- barEndX = this.getBeatX(beat, BeatXPosition.PostNotes);
67901
- }
67902
- else {
67903
- barStartX = beatLineX;
67904
- barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes);
67905
- }
67906
- barStartY = barY + beamY;
67907
- canvas.fillRect(cx + this.x + barStartX, barStartY, barEndX - barStartX, barSize);
68129
+ const direction = this.getBeamDirection(h);
68130
+ const isGrace = h.graceType !== GraceType.None;
68131
+ const scaleMod = isGrace ? EngravingSettings.GraceScale : 1;
68132
+ let barSpacing = (this.beamSpacing + this.beamThickness) * scaleMod;
68133
+ let barSize = this.beamThickness * scaleMod;
68134
+ if (direction === BeamDirection.Down) {
68135
+ barSpacing = -barSpacing;
68136
+ barSize = -barSize;
67908
68137
  }
67909
- let dotCount = this.octaves.has(beat) ? this.octaves.get(beat) : 0;
67910
- const dotSpacing = this.dotSpacing;
67911
- let dotsY = 0;
67912
- let dotsOffset = 0;
67913
- if (dotCount > 0) {
67914
- dotsY = barStart + this.getLineY(0) - res.numberedNotationFont.size;
67915
- dotsOffset = dotSpacing * -1;
68138
+ let barCount = ModelUtils.getIndex(beat.duration) - 2;
68139
+ let beatLineX = this.getBeatX(beat, BeatXPosition.PreNotes);
68140
+ let barStartX = 0;
68141
+ let barEndX = 0;
68142
+ if (i === h.beats.length - 1) {
68143
+ barStartX = beatLineX;
68144
+ barEndX = this.getBeatX(beat, BeatXPosition.PostNotes);
67916
68145
  }
67917
- else if (dotCount < 0) {
67918
- dotsY = barStart + beamY + barCount * (barSpacing + barSize);
67919
- dotsOffset = dotSpacing;
68146
+ else {
68147
+ barStartX = beatLineX;
68148
+ barEndX = this.getBeatX(h.beats[i + 1], BeatXPosition.PreNotes);
67920
68149
  }
67921
- const dotX = this.getBeatX(beat, BeatXPosition.MiddleNotes);
67922
- dotCount = Math.abs(dotCount);
67923
- for (let d = 0; d < dotCount; d++) {
67924
- CanvasHelper.fillMusicFontSymbolSafe(canvas, cx + this.x + dotX, dotsY, 1, MusicFontSymbol.AugmentationDot, true);
67925
- dotsY += dotsOffset;
68150
+ const barStart = cy + this.y + this.calculateBeamY(h, beatLineX);
68151
+ for (let barIndex = 0; barIndex < barCount; barIndex++) {
68152
+ const barY = barStart + barIndex * barSpacing;
68153
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + barStartX, barY, cx + this.x + barEndX, barY, barSize);
68154
+ }
68155
+ // dashes for additional numbers
68156
+ const container = this.voiceContainer.getBeatContainer(beat);
68157
+ if (container && container.hasAdditionalNumbers) {
68158
+ for (const additionalNumber of container.iterateAdditionalNumbers()) {
68159
+ barCount = additionalNumber.barCount;
68160
+ beatLineX =
68161
+ this.beatGlyphsStart + additionalNumber.x + additionalNumber.getBeatX(BeatXPosition.PreNotes, false);
68162
+ for (let barIndex = 0; barIndex < barCount; barIndex++) {
68163
+ const barY = barStart + barIndex * barSpacing;
68164
+ const additionalBarEndX = this.beatGlyphsStart +
68165
+ additionalNumber.x +
68166
+ additionalNumber.getBeatX(BeatXPosition.PostNotes, false);
68167
+ LineBarRenderer.paintSingleBar(canvas, cx + this.x + beatLineX, barY, cx + this.x + additionalBarEndX, barY, barSize);
68168
+ }
68169
+ }
67926
68170
  }
67927
68171
  }
67928
68172
  finally {
@@ -67930,20 +68174,58 @@ class NumberedBarRenderer extends LineBarRenderer {
67930
68174
  }
67931
68175
  }
67932
68176
  }
68177
+ calculateOverflows(rendererTop, rendererBottom) {
68178
+ super.calculateOverflows(rendererTop, rendererBottom);
68179
+ if (this.bar.isEmpty) {
68180
+ return;
68181
+ }
68182
+ this.calculateBeamingOverflows(rendererTop, rendererBottom);
68183
+ }
67933
68184
  getNoteLine(_note) {
67934
68185
  return 0;
67935
68186
  }
67936
- get tupletOffset() {
67937
- // Shift tuplet above the number by:
67938
- // * 1 to get back to the center (calculateBeamYWithDirection places the beam below the number)
67939
- // * 1.5 to get back to the top of the number
67940
- return super.tupletOffset + this.resources.numberedNotationFont.size * 1.5;
68187
+ _calculateBarHeight(beat) {
68188
+ const barCount = ModelUtils.getIndex(beat.duration) - 2;
68189
+ let barHeight = 0;
68190
+ if (barCount > 0) {
68191
+ const smufl = this.smuflMetrics;
68192
+ barHeight =
68193
+ smufl.numberedBarRendererBarSpacing +
68194
+ barCount * (smufl.numberedBarRendererBarSpacing + smufl.numberedBarRendererBarSize);
68195
+ }
68196
+ return barHeight;
67941
68197
  }
67942
- getFlagTopY(_beat, _direction) {
67943
- return this.getLineY(0) - this.resources.numberedNotationFont.size;
68198
+ getFlagTopY(beat, direction) {
68199
+ const barHeight = this._calculateBarHeight(beat);
68200
+ const container = this.voiceContainer.getBeatContainer(beat);
68201
+ if (!container) {
68202
+ if (direction === BeamDirection.Up) {
68203
+ return this.voiceContainer.getBoundingBoxTop() - barHeight;
68204
+ }
68205
+ return this.voiceContainer.getBoundingBoxBottom();
68206
+ }
68207
+ if (direction === BeamDirection.Up) {
68208
+ return container.getBoundingBoxTop() - barHeight;
68209
+ }
68210
+ return container.getBoundingBoxBottom();
67944
68211
  }
67945
- getFlagBottomY(_beat, _direction) {
67946
- return this.getLineY(0) - this.resources.numberedNotationFont.size;
68212
+ getFlagBottomY(beat, direction) {
68213
+ const barHeight = this._calculateBarHeight(beat);
68214
+ const container = this.voiceContainer.getBeatContainer(beat);
68215
+ if (!container) {
68216
+ if (direction === BeamDirection.Down) {
68217
+ return this.voiceContainer.getBoundingBoxBottom() + barHeight;
68218
+ }
68219
+ return this.getLineY(0);
68220
+ }
68221
+ if (direction === BeamDirection.Down) {
68222
+ return container.getBoundingBoxBottom() + barHeight;
68223
+ }
68224
+ return this.getLineY(0);
68225
+ }
68226
+ completeBeamingHelper(helper) {
68227
+ super.completeBeamingHelper(helper);
68228
+ helper.direction = BeamDirection.Down;
67947
68229
  }
67948
68230
  getBeamDirection(_helper) {
67949
68231
  return BeamDirection.Down;
@@ -67958,13 +68240,26 @@ class NumberedBarRenderer extends LineBarRenderer {
67958
68240
  }
67959
68241
  return y;
67960
68242
  }
67961
- calculateBeamYWithDirection(_h, _x, _direction) {
67962
- const res = this.resources.numberedNotationFont;
67963
- return this.getLineY(0) + res.size;
68243
+ calculateBeamYWithDirection(h, _x, direction) {
68244
+ if (h.beats.length === 0) {
68245
+ return this.getLineY(0);
68246
+ }
68247
+ this.ensureBeamDrawingInfo(h, direction);
68248
+ const info = h.drawingInfos.get(direction);
68249
+ if (direction === BeamDirection.Up) {
68250
+ return Math.min(info.startY, info.endY);
68251
+ }
68252
+ else {
68253
+ return Math.max(info.startY, info.endY);
68254
+ }
67964
68255
  }
67965
- getBarLineStart(_beat, _direction) {
67966
- const noteHeadHeight = this.smuflMetrics.glyphHeights.get(MusicFontSymbol.NoteheadBlack);
67967
- return this.getLineY(0) - noteHeadHeight / 2;
68256
+ getBarLineStart(beat, _direction) {
68257
+ // NOTE: this is only for the overflow calculation, this renderer has a custom bar drawing logic
68258
+ const container = this.voiceContainer.getBeatContainer(beat);
68259
+ if (!container) {
68260
+ return this.voiceContainer.getBoundingBoxTop();
68261
+ }
68262
+ return container.getBoundingBoxTop();
67968
68263
  }
67969
68264
  createPreBeatGlyphs() {
67970
68265
  this.wasFirstOfStaff = this.isFirstOfStaff;
@@ -67972,14 +68267,10 @@ class NumberedBarRenderer extends LineBarRenderer {
67972
68267
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
67973
68268
  }
67974
68269
  this.createLinePreBeatGlyphs();
68270
+ this.createStartSpacing();
67975
68271
  this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
67976
68272
  }
67977
68273
  createLinePreBeatGlyphs() {
67978
- // Key signature
67979
- if (!this.bar.previousBar || this.bar.keySignature !== this.bar.previousBar.keySignature) {
67980
- this.createStartSpacing();
67981
- this._createKeySignatureGlyphs();
67982
- }
67983
68274
  if (this._isOnlyNumbered &&
67984
68275
  (!this.bar.previousBar ||
67985
68276
  (this.bar.previousBar &&
@@ -67995,11 +68286,7 @@ class NumberedBarRenderer extends LineBarRenderer {
67995
68286
  this._createTimeSignatureGlyphs();
67996
68287
  }
67997
68288
  }
67998
- _createKeySignatureGlyphs() {
67999
- this.addPreBeatGlyph(new NumberedKeySignatureGlyph(0, this.getLineY(0), this.bar.keySignature, this.bar.keySignatureType));
68000
- }
68001
68289
  _createTimeSignatureGlyphs() {
68002
- this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
68003
68290
  const masterBar = this.bar.masterBar;
68004
68291
  const g = new ScoreTimeSignatureGlyph(0, this.getLineY(0), masterBar.timeSignatureNumerator, masterBar.timeSignatureDenominator, masterBar.timeSignatureCommon, masterBar.isFreeTime &&
68005
68292
  (masterBar.previousMasterBar == null ||
@@ -68017,12 +68304,40 @@ class NumberedBarRenderer extends LineBarRenderer {
68017
68304
  return;
68018
68305
  }
68019
68306
  super.createVoiceGlyphs(v);
68307
+ const absoluteStart = this.bar.masterBar.start;
68020
68308
  for (const b of v.beats) {
68021
- this.addBeatGlyph(new NumberedBeatContainerGlyph(b));
68309
+ const mainContainer = new NumberedBeatContainerGlyph(b);
68310
+ this.addBeatGlyph(mainContainer);
68311
+ // create dashes and filler glyphs
68312
+ // we want a glyph on every quarter tick
68313
+ if (b.duration < Duration.Quarter) {
68314
+ const endTick = b.displayStart + b.displayDuration;
68315
+ let dashTick = b.displayStart + MidiUtils.QuarterTime;
68316
+ while (dashTick < endTick) {
68317
+ const isFullTick = endTick - dashTick >= MidiUtils.QuarterTime;
68318
+ if (isFullTick) {
68319
+ const dash = new NumberedDashBeatContainerGlyph(v.index, absoluteStart + dashTick);
68320
+ this.addBeatGlyph(dash);
68321
+ mainContainer.addDash(dash);
68322
+ }
68323
+ else if (b.duration === Duration.Half && b.dots > 1) {
68324
+ const remainingTickNumber = new NumberedNoteBeatContainerGlyphBase(b, absoluteStart + dashTick, endTick - dashTick);
68325
+ this.addBeatGlyph(remainingTickNumber);
68326
+ mainContainer.addNotes(remainingTickNumber);
68327
+ }
68328
+ dashTick += MidiUtils.QuarterTime;
68329
+ }
68330
+ }
68022
68331
  }
68023
68332
  }
68024
68333
  paintBeamingStem(_beat, _cy, _x, _topY, _bottomY, _canvas) {
68025
68334
  }
68335
+ get beamSpacing() {
68336
+ return this.smuflMetrics.numberedBarRendererBarSpacing;
68337
+ }
68338
+ get beamThickness() {
68339
+ return this.smuflMetrics.numberedBarRendererBarSize;
68340
+ }
68026
68341
  paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement) {
68027
68342
  if (h.voice?.index === 0) {
68028
68343
  super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
@@ -69554,6 +69869,7 @@ class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
69554
69869
  break;
69555
69870
  }
69556
69871
  super.doLayout();
69872
+ this.width = this.width / 2;
69557
69873
  }
69558
69874
  paint(cx, cy, canvas) {
69559
69875
  const beat = this._beat;
@@ -70312,6 +70628,10 @@ class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
70312
70628
  this._beat = container.beat;
70313
70629
  this._container = container;
70314
70630
  }
70631
+ doLayout() {
70632
+ super.doLayout();
70633
+ this.width = 0;
70634
+ }
70315
70635
  getBoundingBoxTop() {
70316
70636
  return super.getBoundingBoxTop() - this._calculateMaxSlurHeight(BeamDirection.Up);
70317
70637
  }
@@ -71171,7 +71491,6 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
71171
71491
  updateWidth() {
71172
71492
  super.updateWidth();
71173
71493
  this.width += this._flagStretch;
71174
- this.minWidth += this._flagStretch;
71175
71494
  }
71176
71495
  }
71177
71496
 
@@ -71840,7 +72159,6 @@ class SlashBeatContainerGlyph extends BeatContainerGlyph {
71840
72159
  updateWidth() {
71841
72160
  super.updateWidth();
71842
72161
  this.width += this._flagStretch;
71843
- this.minWidth += this._flagStretch;
71844
72162
  }
71845
72163
  }
71846
72164
 
@@ -73589,7 +73907,9 @@ class Environment {
73589
73907
  ]),
73590
73908
  //
73591
73909
  // Numbered
73592
- new NumberedBarRendererFactory([]),
73910
+ new NumberedBarRendererFactory([
73911
+ { effect: new NumberedBarKeySignatureEffectInfo(), mode: EffectBandMode.OwnedTop, order: 1000 }
73912
+ ]),
73593
73913
  //
73594
73914
  // Tabs
73595
73915
  new TabBarRendererFactory([