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