@coderline/alphatab 1.8.0-alpha.1660 → 1.8.0-alpha.1667

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * alphaTab v1.8.0-alpha.1660 (develop, build 1660)
2
+ * alphaTab v1.8.0-alpha.1667 (develop, build 1667)
3
3
  *
4
- * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
4
+ * Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
5
5
  *
6
6
  * This Source Code Form is subject to the terms of the Mozilla Public
7
7
  * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
203
203
  * @internal
204
204
  */
205
205
  class VersionInfo {
206
- static version = '1.8.0-alpha.1660';
207
- static date = '2025-12-30T02:22:19.477Z';
208
- static commit = 'adca157f6f531fbf0e712c814f0c9c627bf33b74';
206
+ static version = '1.8.0-alpha.1667';
207
+ static date = '2026-01-06T02:24:13.312Z';
208
+ static commit = '52bfad1512fe6b0ca977ecefc2e3b5943729385d';
209
209
  static print(print) {
210
210
  print(`alphaTab ${VersionInfo.version}`);
211
211
  print(`commit: ${VersionInfo.commit}`);
@@ -4640,9 +4640,12 @@ var MusicFontSymbol;
4640
4640
  MusicFontSymbol[MusicFontSymbol["TextTupletBracketStartLongStem"] = 57857] = "TextTupletBracketStartLongStem";
4641
4641
  MusicFontSymbol[MusicFontSymbol["TextTuplet3LongStem"] = 57858] = "TextTuplet3LongStem";
4642
4642
  MusicFontSymbol[MusicFontSymbol["TextTupletBracketEndLongStem"] = 57859] = "TextTupletBracketEndLongStem";
4643
- MusicFontSymbol[MusicFontSymbol["Tremolo3"] = 57890] = "Tremolo3";
4644
- MusicFontSymbol[MusicFontSymbol["Tremolo2"] = 57889] = "Tremolo2";
4645
4643
  MusicFontSymbol[MusicFontSymbol["Tremolo1"] = 57888] = "Tremolo1";
4644
+ MusicFontSymbol[MusicFontSymbol["Tremolo2"] = 57889] = "Tremolo2";
4645
+ MusicFontSymbol[MusicFontSymbol["Tremolo3"] = 57890] = "Tremolo3";
4646
+ MusicFontSymbol[MusicFontSymbol["Tremolo4"] = 57891] = "Tremolo4";
4647
+ MusicFontSymbol[MusicFontSymbol["Tremolo5"] = 57892] = "Tremolo5";
4648
+ MusicFontSymbol[MusicFontSymbol["BuzzRoll"] = 57898] = "BuzzRoll";
4646
4649
  MusicFontSymbol[MusicFontSymbol["Flag8thUp"] = 57920] = "Flag8thUp";
4647
4650
  MusicFontSymbol[MusicFontSymbol["Flag8thDown"] = 57921] = "Flag8thDown";
4648
4651
  MusicFontSymbol[MusicFontSymbol["Flag16thUp"] = 57922] = "Flag16thUp";
@@ -6949,6 +6952,61 @@ var Rasgueado;
6949
6952
  Rasgueado[Rasgueado["Peami"] = 18] = "Peami";
6950
6953
  })(Rasgueado || (Rasgueado = {}));
6951
6954
 
6955
+ /**
6956
+ * The style of tremolo affecting mainly the display of the effect.
6957
+ * @public
6958
+ */
6959
+ var TremoloPickingStyle;
6960
+ (function (TremoloPickingStyle) {
6961
+ /**
6962
+ * A classic tremolo expressed by diagonal bars on the stem.
6963
+ */
6964
+ TremoloPickingStyle[TremoloPickingStyle["Default"] = 0] = "Default";
6965
+ /**
6966
+ * A buzz roll tremolo expressed by a 'z' shaped symbol.
6967
+ */
6968
+ TremoloPickingStyle[TremoloPickingStyle["BuzzRoll"] = 1] = "BuzzRoll";
6969
+ })(TremoloPickingStyle || (TremoloPickingStyle = {}));
6970
+ /**
6971
+ * Describes a tremolo picking effect.
6972
+ * @json
6973
+ * @json_strict
6974
+ * @cloneable
6975
+ * @public
6976
+ */
6977
+ class TremoloPickingEffect {
6978
+ /**
6979
+ * The minimum number of marks for the tremolo picking effect to be valid.
6980
+ */
6981
+ static minMarks = 0;
6982
+ /**
6983
+ * The max number of marks for the tremolo picking effect to be valid.
6984
+ */
6985
+ static maxMarks = 5;
6986
+ /**
6987
+ * The number of marks for the tremolo.
6988
+ * A mark is equal to a single bar shown for a default tremolos.
6989
+ */
6990
+ marks = 0;
6991
+ /**
6992
+ * The style of the tremolo picking.
6993
+ */
6994
+ style = TremoloPickingStyle.Default;
6995
+ /**
6996
+ * The number of marks define the note value of the note repetition.
6997
+ * e.g. a single mark is an 8th note.
6998
+ */
6999
+ get duration() {
7000
+ let marks = this.marks;
7001
+ if (marks < 1) {
7002
+ marks = 1;
7003
+ }
7004
+ const baseDuration = Duration.Eighth;
7005
+ const actualDuration = baseDuration * Math.pow(2, marks);
7006
+ return actualDuration;
7007
+ }
7008
+ }
7009
+
6952
7010
  /**
6953
7011
  * Lists the different modes on how beaming for a beat should be done.
6954
7012
  * @public
@@ -7371,13 +7429,59 @@ class Beat {
7371
7429
  * Gets or sets the pickstroke applied on this beat.
7372
7430
  */
7373
7431
  pickStroke = PickStroke.None;
7432
+ /**
7433
+ * Whether this beat has a tremolo picking effect.
7434
+ */
7374
7435
  get isTremolo() {
7375
- return !!this.tremoloSpeed;
7436
+ return this.tremoloPicking !== undefined;
7437
+ }
7438
+ /**
7439
+ * The tremolo picking effect.
7440
+ */
7441
+ tremoloPicking;
7442
+ /**
7443
+ * The speed of the tremolo.
7444
+ * @deprecated Set {@link tremoloPicking} instead.
7445
+ */
7446
+ get tremoloSpeed() {
7447
+ const tremolo = this.tremoloPicking;
7448
+ if (tremolo) {
7449
+ return tremolo.duration;
7450
+ }
7451
+ return null;
7376
7452
  }
7377
7453
  /**
7378
- * Gets or sets the speed of the tremolo effect.
7454
+ * The speed of the tremolo.
7455
+ * @deprecated Set {@link tremoloPicking} instead.
7379
7456
  */
7380
- tremoloSpeed = null;
7457
+ set tremoloSpeed(value) {
7458
+ if (value === null) {
7459
+ this.tremoloPicking = undefined;
7460
+ return;
7461
+ }
7462
+ let effect = this.tremoloPicking;
7463
+ if (effect === undefined) {
7464
+ effect = new TremoloPickingEffect();
7465
+ this.tremoloPicking = effect;
7466
+ }
7467
+ switch (value) {
7468
+ case Duration.Eighth:
7469
+ effect.marks = 1;
7470
+ break;
7471
+ case Duration.Sixteenth:
7472
+ effect.marks = 2;
7473
+ break;
7474
+ case Duration.ThirtySecond:
7475
+ effect.marks = 3;
7476
+ break;
7477
+ case Duration.SixtyFourth:
7478
+ effect.marks = 4;
7479
+ break;
7480
+ case Duration.OneHundredTwentyEighth:
7481
+ effect.marks = 5;
7482
+ break;
7483
+ }
7484
+ }
7381
7485
  /**
7382
7486
  * Gets or sets whether a crescendo/decrescendo is applied on this beat.
7383
7487
  */
@@ -7672,6 +7776,12 @@ class Beat {
7672
7776
  if (this.brushType === BrushType.None) {
7673
7777
  this.brushDuration = 0;
7674
7778
  }
7779
+ const tremolo = this.tremoloPicking;
7780
+ if (tremolo !== undefined) {
7781
+ if (tremolo.marks < TremoloPickingEffect.minMarks || tremolo.marks > TremoloPickingEffect.maxMarks) {
7782
+ this.tremoloPicking = undefined;
7783
+ }
7784
+ }
7675
7785
  const displayMode = !settings ? NotationMode.GuitarPro : settings.notation.notationMode;
7676
7786
  let isGradual = this.text === 'grad' || this.text === 'grad.';
7677
7787
  if (isGradual && displayMode === NotationMode.SongBook) {
@@ -8080,6 +8190,23 @@ class AutomationCloner {
8080
8190
  }
8081
8191
  }
8082
8192
 
8193
+ // <auto-generated>
8194
+ // This code was auto-generated.
8195
+ // Changes to this file may cause incorrect behavior and will be lost if
8196
+ // the code is regenerated.
8197
+ // </auto-generated>
8198
+ /**
8199
+ * @internal
8200
+ */
8201
+ class TremoloPickingEffectCloner {
8202
+ static clone(original) {
8203
+ const clone = new TremoloPickingEffect();
8204
+ clone.marks = original.marks;
8205
+ clone.style = original.style;
8206
+ return clone;
8207
+ }
8208
+ }
8209
+
8083
8210
  // <auto-generated>
8084
8211
  // This code was auto-generated.
8085
8212
  // Changes to this file may cause incorrect behavior and will be lost if
@@ -8132,7 +8259,7 @@ class BeatCloner {
8132
8259
  clone.chordId = original.chordId;
8133
8260
  clone.graceType = original.graceType;
8134
8261
  clone.pickStroke = original.pickStroke;
8135
- clone.tremoloSpeed = original.tremoloSpeed;
8262
+ clone.tremoloPicking = original.tremoloPicking ? TremoloPickingEffectCloner.clone(original.tremoloPicking) : undefined;
8136
8263
  clone.crescendo = original.crescendo;
8137
8264
  clone.displayStart = original.displayStart;
8138
8265
  clone.playbackStart = original.playbackStart;
@@ -8515,6 +8642,11 @@ class AlphaTex1EnumMappings {
8515
8642
  ['dadoublecoda', 18]
8516
8643
  ]);
8517
8644
  static directionReversed = AlphaTex1EnumMappings._reverse(AlphaTex1EnumMappings.direction);
8645
+ static tremoloPickingStyle = new Map([
8646
+ ['default', 0],
8647
+ ['buzzroll', 1]
8648
+ ]);
8649
+ static tremoloPickingStyleReversed = AlphaTex1EnumMappings._reverse(AlphaTex1EnumMappings.tremoloPickingStyle);
8518
8650
  static keySignaturesMinorReversed = new Map([
8519
8651
  [-7, 'abminor'],
8520
8652
  [-6, 'ebminor'],
@@ -9275,7 +9407,15 @@ class AlphaTex1LanguageDefinitions {
9275
9407
  ],
9276
9408
  ['volume', [[[[16], 0]]]],
9277
9409
  ['balance', [[[[16], 0]]]],
9278
- ['tp', [[[[16], 0, ['8', '16', '32']]]]],
9410
+ [
9411
+ 'tp',
9412
+ [
9413
+ [
9414
+ [[16], 0],
9415
+ [[10, 17], 1, ['default', 'buzzroll']]
9416
+ ]
9417
+ ]
9418
+ ],
9279
9419
  [
9280
9420
  'barre',
9281
9421
  [
@@ -15193,28 +15333,45 @@ class AlphaTex1LanguageHandler {
15193
15333
  beat.automations.push(balanceAutomation);
15194
15334
  return ApplyNodeResult.Applied;
15195
15335
  case 'tp':
15196
- beat.tremoloSpeed = Duration.Eighth;
15336
+ const tremolo = new TremoloPickingEffect();
15337
+ beat.tremoloPicking = tremolo;
15197
15338
  if (p.arguments && p.arguments.arguments.length > 0) {
15198
- const tremoloSpeedValue = p.arguments.arguments[0].value;
15199
- switch (tremoloSpeedValue) {
15200
- case 8:
15201
- beat.tremoloSpeed = Duration.Eighth;
15202
- break;
15203
- case 16:
15204
- beat.tremoloSpeed = Duration.Sixteenth;
15205
- break;
15206
- case 32:
15207
- beat.tremoloSpeed = Duration.ThirtySecond;
15208
- break;
15209
- default:
15210
- importer.addSemanticDiagnostic({
15211
- code: AlphaTexDiagnosticCode.AT209,
15212
- message: `Unexpected tremolo speed value '${tremoloSpeedValue}, expected: 8, 16 or 32`,
15213
- severity: AlphaTexDiagnosticsSeverity.Error,
15214
- start: p.arguments.arguments[0].start,
15215
- end: p.arguments.arguments[0].end
15216
- });
15339
+ if (p.arguments.arguments.length > 0) {
15340
+ const tremoloMarks = p.arguments.arguments[0].value;
15341
+ if (tremoloMarks >= TremoloPickingEffect.minMarks &&
15342
+ tremoloMarks <= TremoloPickingEffect.maxMarks) {
15343
+ tremolo.marks = tremoloMarks;
15344
+ }
15345
+ else {
15346
+ switch (tremoloMarks) {
15347
+ // backwards compatibility
15348
+ case 8:
15349
+ tremolo.marks = 1;
15350
+ break;
15351
+ case 16:
15352
+ tremolo.marks = 2;
15353
+ break;
15354
+ case 32:
15355
+ tremolo.marks = 3;
15356
+ break;
15357
+ default:
15358
+ importer.addSemanticDiagnostic({
15359
+ code: AlphaTexDiagnosticCode.AT209,
15360
+ message: `Unexpected tremolo marks value '${tremoloMarks}, expected: ${TremoloPickingEffect.minMarks}-${TremoloPickingEffect.maxMarks}, or legacy: 8, 16 or 32`,
15361
+ severity: AlphaTexDiagnosticsSeverity.Error,
15362
+ start: p.arguments.arguments[0].start,
15363
+ end: p.arguments.arguments[0].end
15364
+ });
15365
+ return ApplyNodeResult.NotAppliedSemanticError;
15366
+ }
15367
+ }
15368
+ }
15369
+ if (p.arguments.arguments.length > 1) {
15370
+ const tremoloStyle = AlphaTex1LanguageHandler._parseEnumValue(importer, p.arguments, 'tremolo picking style', AlphaTex1EnumMappings.tremoloPickingStyle, 1);
15371
+ if (tremoloStyle === undefined) {
15217
15372
  return ApplyNodeResult.NotAppliedSemanticError;
15373
+ }
15374
+ tremolo.style = tremoloStyle;
15218
15375
  }
15219
15376
  }
15220
15377
  return ApplyNodeResult.Applied;
@@ -16421,7 +16578,11 @@ class AlphaTex1LanguageHandler {
16421
16578
  : Atnf.identValue(AlphaTex1EnumMappings.graceTypeReversed.get(beat.graceType)));
16422
16579
  }
16423
16580
  if (beat.isTremolo) {
16424
- Atnf.prop(properties, 'tp', Atnf.numberValue(beat.tremoloSpeed));
16581
+ const values = [Atnf.number(beat.tremoloPicking.marks)];
16582
+ if (beat.tremoloPicking.style !== TremoloPickingStyle.Default) {
16583
+ values.push(Atnf.ident(TremoloPickingStyle[beat.tremoloPicking.style]));
16584
+ }
16585
+ Atnf.prop(properties, 'tp', Atnf.args(values));
16425
16586
  }
16426
16587
  switch (beat.crescendo) {
16427
16588
  case CrescendoType.Crescendo:
@@ -16822,9 +16983,6 @@ class AlphaTexImporter extends ScoreImporter {
16822
16983
  // even start translating when we have parser errors
16823
16984
  // as long we have some nodes, we can already start semantically
16824
16985
  // validating and using them
16825
- if (scoreNode.bars.length === 0) {
16826
- throw new UnsupportedFormatError('No alphaTex data found');
16827
- }
16828
16986
  this._bars(scoreNode);
16829
16987
  if (this.semanticDiagnostics.hasErrors) {
16830
16988
  if (this._state.hasAnyProperData) {
@@ -21445,18 +21603,9 @@ class Gp3To5Importer extends ScoreImporter {
21445
21603
  graceBeat.addNote(graceNote);
21446
21604
  }
21447
21605
  readTremoloPicking(beat) {
21448
- const speed = this.data.readByte();
21449
- switch (speed) {
21450
- case 1:
21451
- beat.tremoloSpeed = Duration.Eighth;
21452
- break;
21453
- case 2:
21454
- beat.tremoloSpeed = Duration.Sixteenth;
21455
- break;
21456
- case 3:
21457
- beat.tremoloSpeed = Duration.ThirtySecond;
21458
- break;
21459
- }
21606
+ const effect = new TremoloPickingEffect();
21607
+ beat.tremoloPicking = effect;
21608
+ effect.marks = this.data.readByte();
21460
21609
  }
21461
21610
  readSlide(note) {
21462
21611
  if (this._versionNumber >= 500) {
@@ -23703,15 +23852,17 @@ class GpifParser {
23703
23852
  }
23704
23853
  break;
23705
23854
  case 'Tremolo':
23855
+ const tremolo = new TremoloPickingEffect();
23856
+ beat.tremoloPicking = tremolo;
23706
23857
  switch (c.innerText) {
23707
23858
  case '1/2':
23708
- beat.tremoloSpeed = Duration.Eighth;
23859
+ tremolo.marks = 1;
23709
23860
  break;
23710
23861
  case '1/4':
23711
- beat.tremoloSpeed = Duration.Sixteenth;
23862
+ tremolo.marks = 2;
23712
23863
  break;
23713
23864
  case '1/8':
23714
- beat.tremoloSpeed = Duration.ThirtySecond;
23865
+ tremolo.marks = 3;
23715
23866
  break;
23716
23867
  }
23717
23868
  break;
@@ -28779,16 +28930,12 @@ class MusicXmlImporter extends ScoreImporter {
28779
28930
  break;
28780
28931
  // case 'schleifer': Not supported
28781
28932
  case 'tremolo':
28782
- switch (c.innerText) {
28783
- case '1':
28784
- note.beat.tremoloSpeed = Duration.Eighth;
28785
- break;
28786
- case '2':
28787
- note.beat.tremoloSpeed = Duration.Sixteenth;
28788
- break;
28789
- case '3':
28790
- note.beat.tremoloSpeed = Duration.ThirtySecond;
28791
- break;
28933
+ const tremolo = new TremoloPickingEffect();
28934
+ note.beat.tremoloPicking = tremolo;
28935
+ tremolo.marks = Number.parseInt(c.innerText, 10);
28936
+ if ((c.getAttribute('type', '') === 'unmeasured' && tremolo.marks === 0) ||
28937
+ c.getAttribute('smufl', '') === 'buzzRoll') {
28938
+ tremolo.style = TremoloPickingStyle.BuzzRoll;
28792
28939
  }
28793
28940
  break;
28794
28941
  // case 'haydn': Not supported
@@ -37899,6 +38046,38 @@ class NoteSerializer {
37899
38046
  }
37900
38047
  }
37901
38048
 
38049
+ /**
38050
+ * @internal
38051
+ */
38052
+ class TremoloPickingEffectSerializer {
38053
+ static fromJson(obj, m) {
38054
+ if (!m) {
38055
+ return;
38056
+ }
38057
+ JsonHelper.forEach(m, (v, k) => TremoloPickingEffectSerializer.setProperty(obj, k, v));
38058
+ }
38059
+ static toJson(obj) {
38060
+ if (!obj) {
38061
+ return null;
38062
+ }
38063
+ const o = new Map();
38064
+ o.set("marks", obj.marks);
38065
+ o.set("style", obj.style);
38066
+ return o;
38067
+ }
38068
+ static setProperty(obj, property, v) {
38069
+ switch (property) {
38070
+ case "marks":
38071
+ obj.marks = v;
38072
+ return true;
38073
+ case "style":
38074
+ obj.style = JsonHelper.parseEnum(v, TremoloPickingStyle);
38075
+ return true;
38076
+ }
38077
+ return false;
38078
+ }
38079
+ }
38080
+
37902
38081
  /**
37903
38082
  * @internal
37904
38083
  */
@@ -37981,7 +38160,9 @@ class BeatSerializer {
37981
38160
  o.set("chordid", obj.chordId);
37982
38161
  o.set("gracetype", obj.graceType);
37983
38162
  o.set("pickstroke", obj.pickStroke);
37984
- o.set("tremolospeed", obj.tremoloSpeed);
38163
+ if (obj.tremoloPicking) {
38164
+ o.set("tremolopicking", TremoloPickingEffectSerializer.toJson(obj.tremoloPicking));
38165
+ }
37985
38166
  o.set("crescendo", obj.crescendo);
37986
38167
  o.set("displaystart", obj.displayStart);
37987
38168
  o.set("playbackstart", obj.playbackStart);
@@ -38107,8 +38288,14 @@ class BeatSerializer {
38107
38288
  case "pickstroke":
38108
38289
  obj.pickStroke = JsonHelper.parseEnum(v, PickStroke);
38109
38290
  return true;
38110
- case "tremolospeed":
38111
- obj.tremoloSpeed = JsonHelper.parseEnum(v, Duration) ?? null;
38291
+ case "tremolopicking":
38292
+ if (v) {
38293
+ obj.tremoloPicking = new TremoloPickingEffect();
38294
+ TremoloPickingEffectSerializer.fromJson(obj.tremoloPicking, v);
38295
+ }
38296
+ else {
38297
+ obj.tremoloPicking = undefined;
38298
+ }
38112
38299
  return true;
38113
38300
  case "crescendo":
38114
38301
  obj.crescendo = JsonHelper.parseEnum(v, CrescendoType);
@@ -39422,6 +39609,7 @@ class EngravingSettingsCloner {
39422
39609
  clone.tuningGlyphStringRowPadding = original.tuningGlyphStringRowPadding;
39423
39610
  clone.directionsScale = original.directionsScale;
39424
39611
  clone.multiVoiceDisplacedNoteHeadSpacing = original.multiVoiceDisplacedNoteHeadSpacing;
39612
+ clone.stemFlagHeight = new Map(original.stemFlagHeight);
39425
39613
  return clone;
39426
39614
  }
39427
39615
  }
@@ -39733,6 +39921,20 @@ class EngravingSettings {
39733
39921
  this.stemFlagOffsets.set(Duration.SixtyFourth, 0);
39734
39922
  this.stemFlagOffsets.set(Duration.OneHundredTwentyEighth, 0);
39735
39923
  this.stemFlagOffsets.set(Duration.TwoHundredFiftySixth, 0);
39924
+ // Workaround for: https://github.com/w3c/smufl/issues/203
39925
+ // There is no clear anchor for the height of flags on the stem side yet.
39926
+ // These aproximations are tested with bravura
39927
+ this.stemFlagHeight.set(Duration.QuadrupleWhole, 0);
39928
+ this.stemFlagHeight.set(Duration.DoubleWhole, 0);
39929
+ this.stemFlagHeight.set(Duration.Whole, 0);
39930
+ this.stemFlagHeight.set(Duration.Half, 0);
39931
+ this.stemFlagHeight.set(Duration.Quarter, 0);
39932
+ this.stemFlagHeight.set(Duration.Eighth, 1 * this.oneStaffSpace);
39933
+ this.stemFlagHeight.set(Duration.Sixteenth, 1.5 * this.oneStaffSpace);
39934
+ this.stemFlagHeight.set(Duration.ThirtySecond, 2 * this.oneStaffSpace);
39935
+ this.stemFlagHeight.set(Duration.SixtyFourth, 3 * this.oneStaffSpace);
39936
+ this.stemFlagHeight.set(Duration.OneHundredTwentyEighth, 3.5 * this.oneStaffSpace);
39937
+ this.stemFlagHeight.set(Duration.TwoHundredFiftySixth, 4.2 * this.oneStaffSpace);
39736
39938
  for (const [g, v] of Object.entries(smufl.glyphsWithAnchors)) {
39737
39939
  const symbol = EngravingSettings._smuflNameToMusicFontSymbol(g);
39738
39940
  if (symbol) {
@@ -40079,6 +40281,19 @@ class EngravingSettings {
40079
40281
  * in case of multi-voice note head overlaps.
40080
40282
  */
40081
40283
  multiVoiceDisplacedNoteHeadSpacing = 0;
40284
+ /**
40285
+ * Calculates the stem height for a note of the given duration.
40286
+ * @param duration The duration to calculate the height respecting flag sizes.
40287
+ * @param hasFlag True if we need to respect flags, false if we have beams.
40288
+ * @returns The total stem height
40289
+ */
40290
+ getStemLength(duration, hasFlag) {
40291
+ return this.standardStemLength + (hasFlag ? this.stemFlagOffsets.get(duration) : 0);
40292
+ }
40293
+ /**
40294
+ * The space needed by flags on the stem-side from top to bottom to place.
40295
+ */
40296
+ stemFlagHeight = new Map();
40082
40297
  // Idea: maybe we can encode and pack this large metadata into a more compact format (e.g. BSON or a custom binary blob?)
40083
40298
  // This metadata below is updated automatically from the bravura_metadata.json via npm script
40084
40299
  static bravuraMetadata =
@@ -40211,6 +40426,10 @@ class EngravingSettings {
40211
40426
  bBoxNE: [1.876, 1.18],
40212
40427
  bBoxSW: [0, 0]
40213
40428
  },
40429
+ buzzRoll: {
40430
+ bBoxNE: [0.624, 0.464],
40431
+ bBoxSW: [-0.62, -0.464]
40432
+ },
40214
40433
  cClef: {
40215
40434
  bBoxNE: [2.796, 2.024],
40216
40435
  bBoxSW: [0, -2.024]
@@ -41167,6 +41386,14 @@ class EngravingSettings {
41167
41386
  bBoxNE: [0.6, 1.112],
41168
41387
  bBoxSW: [-0.6, -1.12]
41169
41388
  },
41389
+ tremolo4: {
41390
+ bBoxNE: [0.6, 1.496],
41391
+ bBoxSW: [-0.6, -1.48]
41392
+ },
41393
+ tremolo5: {
41394
+ bBoxNE: [0.6, 1.88],
41395
+ bBoxSW: [-0.604, -1.84]
41396
+ },
41170
41397
  tuplet0: {
41171
41398
  bBoxNE: [1.2731041262817027, 1.5],
41172
41399
  bBoxSW: [-0.001204330173715796, -0.032]
@@ -41814,6 +42041,13 @@ class EngravingSettingsSerializer {
41814
42041
  o.set("tuningglyphstringrowpadding", obj.tuningGlyphStringRowPadding);
41815
42042
  o.set("directionsscale", obj.directionsScale);
41816
42043
  o.set("multivoicedisplacednoteheadspacing", obj.multiVoiceDisplacedNoteHeadSpacing);
42044
+ {
42045
+ const m = new Map();
42046
+ o.set("stemflagheight", m);
42047
+ for (const [k, v] of obj.stemFlagHeight) {
42048
+ m.set(k.toString(), v);
42049
+ }
42050
+ }
41817
42051
  return o;
41818
42052
  }
41819
42053
  static setProperty(obj, property, v) {
@@ -42095,6 +42329,12 @@ class EngravingSettingsSerializer {
42095
42329
  case "multivoicedisplacednoteheadspacing":
42096
42330
  obj.multiVoiceDisplacedNoteHeadSpacing = v;
42097
42331
  return true;
42332
+ case "stemflagheight":
42333
+ obj.stemFlagHeight = new Map();
42334
+ JsonHelper.forEach(v, (v, k) => {
42335
+ obj.stemFlagHeight.set(JsonHelper.parseEnum(k, Duration), v);
42336
+ });
42337
+ return true;
42098
42338
  }
42099
42339
  return false;
42100
42340
  }
@@ -48316,7 +48556,12 @@ class MidiFileGenerator {
48316
48556
  }
48317
48557
  _generateTremoloPicking(note, noteStart, noteDuration, noteKey, dynamicValue, channel) {
48318
48558
  const track = note.beat.voice.bar.staff.track;
48319
- let tpLength = MidiUtils.toTicks(note.beat.tremoloSpeed);
48559
+ const marks = note.beat.tremoloPicking.marks;
48560
+ if (marks === 0) {
48561
+ return;
48562
+ }
48563
+ // the marks represent the duration
48564
+ let tpLength = MidiUtils.toTicks(note.beat.tremoloPicking.duration);
48320
48565
  let tick = noteStart;
48321
48566
  const end = noteStart + noteDuration.untilTieOrSlideEnd;
48322
48567
  while (tick + 10 < end) {
@@ -48742,6 +48987,12 @@ class BeatContainerGlyph extends BeatContainerGlyphBase {
48742
48987
  beat;
48743
48988
  preNotes;
48744
48989
  onNotes;
48990
+ getLowestNoteY(requestedPosition) {
48991
+ return this.onNotes.getLowestNoteY(requestedPosition);
48992
+ }
48993
+ getHighestNoteY(requestedPosition) {
48994
+ return this.onNotes.getHighestNoteY(requestedPosition);
48995
+ }
48745
48996
  get beatId() {
48746
48997
  return this.beat.id;
48747
48998
  }
@@ -48805,7 +49056,7 @@ class BeatContainerGlyph extends BeatContainerGlyphBase {
48805
49056
  return helper.hasFlag(false, undefined);
48806
49057
  }
48807
49058
  get postBeatStretch() {
48808
- return (this.onNotes.computedWidth + this._tieWidth) - this.onNotes.onTimeX;
49059
+ return this.onNotes.computedWidth + this._tieWidth - this.onNotes.onTimeX;
48809
49060
  }
48810
49061
  registerLayoutingInfo(layoutings) {
48811
49062
  const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
@@ -60015,16 +60266,22 @@ class EffectBandContainer {
60015
60266
  return false;
60016
60267
  }
60017
60268
  let y = 0;
60269
+ // TODO. activate padding
60270
+ // const paddingTop = this._isTopContainer ? 0 : this._renderer.settings.display.effectBandPaddingBottom;
60271
+ // const paddingBottom = this._isTopContainer ? this._renderer.settings.display.effectBandPaddingBottom : 0;
60272
+ const paddingTop = 0;
60273
+ const paddingBottom = this._renderer.settings.display.effectBandPaddingBottom;
60018
60274
  for (const slot of this._effectBandSizingInfo.slots) {
60019
60275
  slot.shared.y = y;
60020
60276
  for (const band of slot.bands) {
60277
+ y += paddingTop;
60021
60278
  band.y = y;
60022
60279
  if (finalize) {
60023
60280
  band.finalizeBand();
60024
60281
  }
60025
60282
  band.height = slot.shared.height;
60026
60283
  }
60027
- y += slot.shared.height + this._renderer.settings.display.effectBandPaddingBottom;
60284
+ y += slot.shared.height + paddingBottom;
60028
60285
  }
60029
60286
  y = Math.ceil(y);
60030
60287
  if (y !== this.height) {
@@ -60254,6 +60511,20 @@ class MultiVoiceContainerGlyph extends Glyph {
60254
60511
  }
60255
60512
  return 0;
60256
60513
  }
60514
+ getLowestNoteY(beat, position) {
60515
+ const container = this.getBeatContainer(beat);
60516
+ if (container) {
60517
+ return container.y + container.getLowestNoteY(position);
60518
+ }
60519
+ return 0;
60520
+ }
60521
+ getHighestNoteY(beat, position) {
60522
+ const container = this.getBeatContainer(beat);
60523
+ if (container) {
60524
+ return container.y + container.getHighestNoteY(position);
60525
+ }
60526
+ return 0;
60527
+ }
60257
60528
  getNoteX(note, requestedPosition) {
60258
60529
  const container = this.getBeatContainer(note.beat);
60259
60530
  if (container) {
@@ -60266,14 +60537,14 @@ class MultiVoiceContainerGlyph extends Glyph {
60266
60537
  if (beat) {
60267
60538
  return beat.y + beat.getNoteY(note, requestedPosition);
60268
60539
  }
60269
- return Number.NaN;
60540
+ return 0;
60270
60541
  }
60271
60542
  getRestY(beat, requestedPosition) {
60272
60543
  const container = this.getBeatContainer(beat);
60273
60544
  if (container) {
60274
60545
  return container.y + container.getRestY(requestedPosition);
60275
60546
  }
60276
- return Number.NaN;
60547
+ return 0;
60277
60548
  }
60278
60549
  getBeatContainer(beat) {
60279
60550
  if (!this._beatGlyphLookup.has(beat.id)) {
@@ -60943,16 +61214,18 @@ class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
60943
61214
  const g = this._glyph;
60944
61215
  if (g) {
60945
61216
  switch (requestedPosition) {
60946
- case NoteYPosition.TopWithStem:
60947
61217
  case NoteYPosition.Top:
60948
61218
  return g.y;
61219
+ case NoteYPosition.TopWithStem:
61220
+ return g.y - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
60949
61221
  case NoteYPosition.Center:
60950
61222
  case NoteYPosition.StemUp:
60951
61223
  case NoteYPosition.StemDown:
60952
61224
  return g.y + g.height / 2;
60953
61225
  case NoteYPosition.Bottom:
60954
- case NoteYPosition.BottomWithStem:
60955
61226
  return g.y + g.height;
61227
+ case NoteYPosition.BottomWithStem:
61228
+ return g.y + g.height + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
60956
61229
  }
60957
61230
  }
60958
61231
  return 0;
@@ -60960,6 +61233,12 @@ class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
60960
61233
  getNoteY(_note, requestedPosition) {
60961
61234
  return this.getRestY(requestedPosition);
60962
61235
  }
61236
+ getHighestNoteY(position) {
61237
+ return this.getRestY(position);
61238
+ }
61239
+ getLowestNoteY(position) {
61240
+ return this.getRestY(position);
61241
+ }
60963
61242
  getNoteX(_note, requestedPosition) {
60964
61243
  const g = this._glyph;
60965
61244
  if (g) {
@@ -61054,7 +61333,6 @@ class BeamingHelper {
61054
61333
  voice = null;
61055
61334
  beats = [];
61056
61335
  shortestDuration = Duration.QuadrupleWhole;
61057
- tremoloDuration;
61058
61336
  /**
61059
61337
  * an indicator whether any beat has a tuplet on it.
61060
61338
  */
@@ -61097,43 +61375,9 @@ class BeamingHelper {
61097
61375
  this.drawingInfos.clear();
61098
61376
  }
61099
61377
  }
61100
- direction = BeamDirection.Up;
61101
61378
  finish() {
61102
- this.direction = this._calculateDirection();
61103
61379
  this._renderer.completeBeamingHelper(this);
61104
61380
  }
61105
- _calculateDirection() {
61106
- // no proper voice (should not happen usually)
61107
- if (!this.voice) {
61108
- return BeamDirection.Up;
61109
- }
61110
- // we have a preferred direction
61111
- if (this.preferredBeamDirection !== null) {
61112
- return this.preferredBeamDirection;
61113
- }
61114
- // on multi-voice setups secondary voices are always down
61115
- if (this.voice.index > 0) {
61116
- return this._invert(BeamDirection.Down);
61117
- }
61118
- // on multi-voice setups primary voices are always up
61119
- if (this.voice.bar.isMultiVoice) {
61120
- return this._invert(BeamDirection.Up);
61121
- }
61122
- // grace notes are always up
61123
- if (this.beats[0].graceType !== GraceType.None) {
61124
- return this._invert(BeamDirection.Up);
61125
- }
61126
- // the average line is used for determination
61127
- // key lowerequal than middle line -> up
61128
- // key higher than middle line -> down
61129
- if (this.highestNoteInHelper && this.lowestNoteInHelper) {
61130
- const highestNotePosition = this._renderer.getNoteY(this.highestNoteInHelper, NoteYPosition.Center);
61131
- const lowestNotePosition = this._renderer.getNoteY(this.lowestNoteInHelper, NoteYPosition.Center);
61132
- const avg = (highestNotePosition + lowestNotePosition) / 2;
61133
- return this._invert(this._renderer.middleYPosition < avg ? BeamDirection.Up : BeamDirection.Down);
61134
- }
61135
- return this._invert(BeamDirection.Up);
61136
- }
61137
61381
  static computeLineHeightsForRest(duration) {
61138
61382
  switch (duration) {
61139
61383
  case Duration.QuadrupleWhole:
@@ -61161,18 +61405,6 @@ class BeamingHelper {
61161
61405
  }
61162
61406
  return [0, 0];
61163
61407
  }
61164
- _invert(direction) {
61165
- if (!this.invertBeamDirection) {
61166
- return direction;
61167
- }
61168
- switch (direction) {
61169
- case BeamDirection.Down:
61170
- return BeamDirection.Up;
61171
- // case BeamDirection.Up:
61172
- default:
61173
- return BeamDirection.Down;
61174
- }
61175
- }
61176
61408
  checkBeat(beat) {
61177
61409
  if (beat.invertBeamDirection) {
61178
61410
  this.invertBeamDirection = true;
@@ -61206,11 +61438,6 @@ class BeamingHelper {
61206
61438
  if (beat.hasTuplet) {
61207
61439
  this.hasTuplet = true;
61208
61440
  }
61209
- if (beat.isTremolo) {
61210
- if (!this.tremoloDuration || this.tremoloDuration < beat.tremoloSpeed) {
61211
- this.tremoloDuration = beat.tremoloSpeed;
61212
- }
61213
- }
61214
61441
  if (beat.graceType !== GraceType.None) {
61215
61442
  this.graceType = beat.graceType;
61216
61443
  }
@@ -62109,9 +62336,6 @@ class BarRendererBase {
62109
62336
  }
62110
62337
  completeBeamingHelper(_helper) {
62111
62338
  }
62112
- getBeatDirection(beat) {
62113
- return this.helpers.getBeamingHelperForBeat(beat)?.direction ?? BeamDirection.Up;
62114
- }
62115
62339
  }
62116
62340
 
62117
62341
  /**
@@ -67194,11 +67418,11 @@ class NumberedBeatGlyph extends BeatOnNoteGlyphBase {
67194
67418
  beatBounds.addNote(noteBounds);
67195
67419
  }
67196
67420
  }
67197
- getLowestNoteY() {
67198
- return this._internalGetNoteY(NoteYPosition.Center);
67421
+ getLowestNoteY(requestedPosition) {
67422
+ return this._internalGetNoteY(requestedPosition);
67199
67423
  }
67200
- getHighestNoteY() {
67201
- return this._internalGetNoteY(NoteYPosition.Center);
67424
+ getHighestNoteY(requestedPosition) {
67425
+ return this._internalGetNoteY(requestedPosition);
67202
67426
  }
67203
67427
  getNoteY(_note, requestedPosition) {
67204
67428
  return this._internalGetNoteY(requestedPosition);
@@ -67647,6 +67871,12 @@ class NumberedDashBeatContainerGlyph extends BeatContainerGlyphBase {
67647
67871
  get isLastOfVoice() {
67648
67872
  return false;
67649
67873
  }
67874
+ getLowestNoteY(_requestedPosition) {
67875
+ return 0;
67876
+ }
67877
+ getHighestNoteY(_requestedPosition) {
67878
+ return 0;
67879
+ }
67650
67880
  getNoteY(_note, _requestedPosition) {
67651
67881
  return 0;
67652
67882
  }
@@ -67798,6 +68028,11 @@ class FlagGlyph extends MusicFontGlyph {
67798
68028
  constructor(x, y, duration, direction, isGrace) {
67799
68029
  super(x, y, isGrace ? EngravingSettings.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
67800
68030
  }
68031
+ paint(cx, cy, canvas) {
68032
+ const c = canvas.color;
68033
+ super.paint(cx, cy, canvas);
68034
+ canvas.color = c;
68035
+ }
67801
68036
  static getSymbol(duration, direction, isGrace) {
67802
68037
  if (isGrace) {
67803
68038
  duration = Duration.Eighth;
@@ -68002,9 +68237,17 @@ class LineBarRenderer extends BarRendererBase {
68002
68237
  }
68003
68238
  }
68004
68239
  }
68240
+ getBeatDirection(beat) {
68241
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
68242
+ return helper ? this.getBeamDirection(helper) : BeamDirection.Up;
68243
+ }
68005
68244
  getTupletBeamDirection(helper) {
68006
68245
  return this.getBeamDirection(helper);
68007
68246
  }
68247
+ calculateBeamYWithDirection(h, x, direction) {
68248
+ this.ensureBeamDrawingInfo(h, direction);
68249
+ return h.drawingInfos.get(direction).calcY(x);
68250
+ }
68008
68251
  _paintTupletHelper(cx, cy, canvas, h, beatElement, bracketsAsArcs) {
68009
68252
  const res = this.resources;
68010
68253
  const oldAlign = canvas.textAlign;
@@ -68313,26 +68556,6 @@ class LineBarRenderer extends BarRendererBase {
68313
68556
  }
68314
68557
  }
68315
68558
  }
68316
- getFlagStemSize(duration, forceMinStem = false) {
68317
- let size = 0;
68318
- switch (duration) {
68319
- case Duration.QuadrupleWhole:
68320
- case Duration.Half:
68321
- case Duration.Quarter:
68322
- case Duration.Eighth:
68323
- case Duration.Sixteenth:
68324
- case Duration.ThirtySecond:
68325
- case Duration.SixtyFourth:
68326
- case Duration.OneHundredTwentyEighth:
68327
- case Duration.TwoHundredFiftySixth:
68328
- size = this.smuflMetrics.standardStemLength + this.smuflMetrics.stemFlagOffsets.get(duration);
68329
- break;
68330
- default:
68331
- size = forceMinStem ? this.smuflMetrics.standardStemLength : 0;
68332
- break;
68333
- }
68334
- return size;
68335
- }
68336
68559
  recreatePreBeatGlyphs() {
68337
68560
  this._startSpacing = false;
68338
68561
  super.recreatePreBeatGlyphs();
@@ -68344,6 +68567,9 @@ class LineBarRenderer extends BarRendererBase {
68344
68567
  super.createPreBeatGlyphs();
68345
68568
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
68346
68569
  this.createLinePreBeatGlyphs();
68570
+ if (this.index === 0) {
68571
+ this.createStartSpacing();
68572
+ }
68347
68573
  this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
68348
68574
  }
68349
68575
  createPostBeatGlyphs() {
@@ -68370,7 +68596,13 @@ class LineBarRenderer extends BarRendererBase {
68370
68596
  continue;
68371
68597
  }
68372
68598
  const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
68373
- const y1 = cy + this.y + this.getBarLineStart(beat, direction);
68599
+ let y1 = cy + this.y;
68600
+ if (direction === BeamDirection.Up) {
68601
+ y1 += this.getFlagBottomY(beat, direction);
68602
+ }
68603
+ else {
68604
+ y1 += this.getFlagTopY(beat, direction);
68605
+ }
68374
68606
  // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
68375
68607
  // when combining stems and beams on sub-pixel level
68376
68608
  const y2 = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
@@ -68476,21 +68708,22 @@ class LineBarRenderer extends BarRendererBase {
68476
68708
  calculateBeamingOverflows(rendererTop, rendererBottom) {
68477
68709
  let maxNoteY = 0;
68478
68710
  let minNoteY = 0;
68479
- const noteOverflowPadding = this.getLineHeight(0.5);
68480
68711
  for (const v of this.helpers.beamHelpers) {
68481
68712
  for (const h of v) {
68482
68713
  if (!this.shouldPaintBeamingHelper(h)) ;
68483
68714
  else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
68484
68715
  const tupletDirection = this.getTupletBeamDirection(h);
68485
- if (h.direction === BeamDirection.Up) {
68486
- let topY = this.getFlagTopY(h.beats[0], h.direction);
68487
- if (h.hasTuplet && tupletDirection === h.direction) {
68716
+ const direction = this.getBeamDirection(h);
68717
+ const flagOverflow = this.smuflMetrics.stemFlagOffsets.get(h.beats[0].duration);
68718
+ if (direction === BeamDirection.Up) {
68719
+ let topY = this.getFlagTopY(h.beats[0], direction) - flagOverflow;
68720
+ if (h.hasTuplet && tupletDirection === direction) {
68488
68721
  topY -= this.tupletSize + this.tupletOffset;
68489
68722
  }
68490
68723
  if (topY < maxNoteY) {
68491
68724
  maxNoteY = topY;
68492
68725
  }
68493
- if (h.hasTuplet && tupletDirection !== h.direction) {
68726
+ if (h.hasTuplet && tupletDirection !== direction) {
68494
68727
  let bottomY = this.getFlagBottomY(h.beats[0], tupletDirection);
68495
68728
  bottomY += this.tupletSize + this.tupletOffset;
68496
68729
  if (bottomY > minNoteY) {
@@ -68499,14 +68732,14 @@ class LineBarRenderer extends BarRendererBase {
68499
68732
  }
68500
68733
  }
68501
68734
  else {
68502
- let bottomY = this.getFlagBottomY(h.beats[0], h.direction);
68503
- if (h.hasTuplet && tupletDirection === h.direction) {
68735
+ let bottomY = this.getFlagBottomY(h.beats[0], direction) + flagOverflow;
68736
+ if (h.hasTuplet && tupletDirection === direction) {
68504
68737
  bottomY += this.tupletSize + this.tupletOffset;
68505
68738
  }
68506
68739
  if (bottomY > minNoteY) {
68507
68740
  minNoteY = bottomY;
68508
68741
  }
68509
- if (h.hasTuplet && tupletDirection !== h.direction) {
68742
+ if (h.hasTuplet && tupletDirection !== direction) {
68510
68743
  let topY = this.getFlagTopY(h.beats[0], tupletDirection);
68511
68744
  topY -= this.tupletSize + this.tupletOffset;
68512
68745
  if (topY < maxNoteY) {
@@ -68516,19 +68749,20 @@ class LineBarRenderer extends BarRendererBase {
68516
68749
  }
68517
68750
  }
68518
68751
  else {
68519
- this.ensureBeamDrawingInfo(h, h.direction);
68520
- const drawingInfo = h.drawingInfos.get(h.direction);
68752
+ const direction = this.getBeamDirection(h);
68753
+ this.ensureBeamDrawingInfo(h, direction);
68754
+ const drawingInfo = h.drawingInfos.get(direction);
68521
68755
  const tupletDirection = this.getTupletBeamDirection(h);
68522
- if (h.direction === BeamDirection.Up) {
68756
+ if (direction === BeamDirection.Up) {
68523
68757
  let topY = Math.min(drawingInfo.startY, drawingInfo.endY);
68524
- if (h.hasTuplet && tupletDirection === h.direction) {
68758
+ if (h.hasTuplet && tupletDirection === direction) {
68525
68759
  topY -= this.tupletSize + this.tupletOffset;
68526
68760
  }
68527
68761
  if (topY < maxNoteY) {
68528
68762
  maxNoteY = topY;
68529
68763
  }
68530
- let bottomY = this.getBarLineStart(h.beatOfLowestNote, h.direction) + noteOverflowPadding;
68531
- if (h.hasTuplet && tupletDirection !== h.direction) {
68764
+ let bottomY = this.voiceContainer.getLowestNoteY(h.beatOfLowestNote, NoteYPosition.Bottom);
68765
+ if (h.hasTuplet && tupletDirection !== direction) {
68532
68766
  bottomY += this.tupletSize + this.tupletOffset;
68533
68767
  }
68534
68768
  if (bottomY > minNoteY) {
@@ -68537,14 +68771,14 @@ class LineBarRenderer extends BarRendererBase {
68537
68771
  }
68538
68772
  else {
68539
68773
  let bottomY = Math.max(drawingInfo.startY, drawingInfo.endY);
68540
- if (h.hasTuplet && tupletDirection === h.direction) {
68774
+ if (h.hasTuplet && tupletDirection === direction) {
68541
68775
  bottomY += this.tupletSize + this.tupletOffset;
68542
68776
  }
68543
68777
  if (bottomY > minNoteY) {
68544
68778
  minNoteY = bottomY;
68545
68779
  }
68546
- let topY = this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
68547
- if (h.hasTuplet && tupletDirection !== h.direction) {
68780
+ let topY = this.voiceContainer.getHighestNoteY(h.beatOfHighestNote, NoteYPosition.Top);
68781
+ if (h.hasTuplet && tupletDirection !== direction) {
68548
68782
  topY -= this.tupletSize + this.tupletOffset;
68549
68783
  }
68550
68784
  if (topY < maxNoteY) {
@@ -68621,38 +68855,11 @@ class LineBarRenderer extends BarRendererBase {
68621
68855
  // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
68622
68856
  const drawingInfo = this.initializeBeamDrawingInfo(h, direction);
68623
68857
  h.drawingInfos.set(direction, drawingInfo);
68624
- const isRest = h.isRestBeamHelper;
68625
- const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
68626
68858
  const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
68627
68859
  // 3. adjust beam drawing order
68628
68860
  // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
68629
68861
  // here we shift accordingly
68630
- let barDrawingShift = 0;
68631
- if (barCount > 2 && !isRest) {
68632
- const beamSpacing = this.beamSpacing * scale;
68633
- const beamThickness = this.beamThickness * scale;
68634
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
68635
- if (direction === BeamDirection.Up) {
68636
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
68637
- const barTopY = bottomBarY - totalBarsHeight;
68638
- const diff = drawingInfo.startY - barTopY;
68639
- if (diff > 0) {
68640
- barDrawingShift = diff * -1;
68641
- drawingInfo.startY -= diff;
68642
- drawingInfo.endY -= diff;
68643
- }
68644
- }
68645
- else {
68646
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
68647
- const barBottomY = topBarY + totalBarsHeight;
68648
- const diff = barBottomY - drawingInfo.startY;
68649
- if (diff > 0) {
68650
- barDrawingShift = diff;
68651
- drawingInfo.startY += diff;
68652
- drawingInfo.endY += diff;
68653
- }
68654
- }
68655
- }
68862
+ const barDrawingShift = this.applyBarShift(h, direction, drawingInfo, barCount);
68656
68863
  // 4. let middle elements shift up/down
68657
68864
  if (h.beats.length > 1) {
68658
68865
  // check if highest note shifts bar up or down
@@ -68708,9 +68915,9 @@ class LineBarRenderer extends BarRendererBase {
68708
68915
  if (h.slashBeats.length > 0) {
68709
68916
  for (const b of h.slashBeats) {
68710
68917
  const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
68711
- const yNeededForSlash = h.direction === BeamDirection.Up
68712
- ? this.getFlagTopY(b, h.direction)
68713
- : this.getFlagBottomY(b, h.direction);
68918
+ const yNeededForSlash = direction === BeamDirection.Up
68919
+ ? this.getFlagTopY(b, direction)
68920
+ : this.getFlagBottomY(b, direction);
68714
68921
  const diff = yNeededForSlash - yGivenByCurrentValues;
68715
68922
  if (diff > 0) {
68716
68923
  drawingInfo.startY += diff;
@@ -68720,6 +68927,37 @@ class LineBarRenderer extends BarRendererBase {
68720
68927
  }
68721
68928
  }
68722
68929
  }
68930
+ applyBarShift(h, direction, drawingInfo, barCount) {
68931
+ let barDrawingShift = 0;
68932
+ const isRest = h.isRestBeamHelper;
68933
+ const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
68934
+ if (barCount > 2 && !isRest) {
68935
+ const beamSpacing = this.beamSpacing * scale;
68936
+ const beamThickness = this.beamThickness * scale;
68937
+ const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
68938
+ if (direction === BeamDirection.Up) {
68939
+ const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
68940
+ const barTopY = bottomBarY - totalBarsHeight;
68941
+ const diff = drawingInfo.startY - barTopY;
68942
+ if (diff > 0) {
68943
+ barDrawingShift = diff * -1;
68944
+ drawingInfo.startY -= diff;
68945
+ drawingInfo.endY -= diff;
68946
+ }
68947
+ }
68948
+ else {
68949
+ const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
68950
+ const barBottomY = topBarY + totalBarsHeight;
68951
+ const diff = barBottomY - drawingInfo.startY;
68952
+ if (diff > 0) {
68953
+ barDrawingShift = diff;
68954
+ drawingInfo.startY += diff;
68955
+ drawingInfo.endY += diff;
68956
+ }
68957
+ }
68958
+ }
68959
+ return barDrawingShift;
68960
+ }
68723
68961
  getMinLineOfBeat(_beat) {
68724
68962
  return 0;
68725
68963
  }
@@ -68823,7 +69061,9 @@ class NumberedBarRenderer extends LineBarRenderer {
68823
69061
  for (const additionalNumber of container.iterateAdditionalNumbers()) {
68824
69062
  barCount = additionalNumber.barCount;
68825
69063
  beatLineX =
68826
- this.beatGlyphsStart + additionalNumber.x + additionalNumber.getBeatX(BeatXPosition.PreNotes, false);
69064
+ this.beatGlyphsStart +
69065
+ additionalNumber.x +
69066
+ additionalNumber.getBeatX(BeatXPosition.PreNotes, false);
68827
69067
  for (let barIndex = 0; barIndex < barCount; barIndex++) {
68828
69068
  const barY = barStart + barIndex * barSpacing;
68829
69069
  const additionalBarEndX = this.beatGlyphsStart +
@@ -68888,44 +69128,12 @@ class NumberedBarRenderer extends LineBarRenderer {
68888
69128
  }
68889
69129
  return this.getLineY(0);
68890
69130
  }
68891
- completeBeamingHelper(helper) {
68892
- super.completeBeamingHelper(helper);
68893
- helper.direction = BeamDirection.Down;
68894
- }
68895
69131
  getBeamDirection(_helper) {
68896
69132
  return BeamDirection.Down;
68897
69133
  }
68898
69134
  getTupletBeamDirection(_helper) {
68899
69135
  return BeamDirection.Up;
68900
69136
  }
68901
- getNoteY(note, requestedPosition) {
68902
- let y = super.getNoteY(note, requestedPosition);
68903
- if (Number.isNaN(y)) {
68904
- y = this.getLineY(0);
68905
- }
68906
- return y;
68907
- }
68908
- calculateBeamYWithDirection(h, _x, direction) {
68909
- if (h.beats.length === 0) {
68910
- return this.getLineY(0);
68911
- }
68912
- this.ensureBeamDrawingInfo(h, direction);
68913
- const info = h.drawingInfos.get(direction);
68914
- if (direction === BeamDirection.Up) {
68915
- return Math.min(info.startY, info.endY);
68916
- }
68917
- else {
68918
- return Math.max(info.startY, info.endY);
68919
- }
68920
- }
68921
- getBarLineStart(beat, _direction) {
68922
- // NOTE: this is only for the overflow calculation, this renderer has a custom bar drawing logic
68923
- const container = this.voiceContainer.getBeatContainer(beat);
68924
- if (!container) {
68925
- return this.voiceContainer.getBoundingBoxTop();
68926
- }
68927
- return container.getBoundingBoxTop();
68928
- }
68929
69137
  createPreBeatGlyphs() {
68930
69138
  this.wasFirstOfStaff = this.isFirstOfStaff;
68931
69139
  if (this.index === 0 || (this.bar.masterBar.isRepeatStart && this._isOnlyNumbered)) {
@@ -69008,6 +69216,19 @@ class NumberedBarRenderer extends LineBarRenderer {
69008
69216
  super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
69009
69217
  }
69010
69218
  }
69219
+ applyBarShift(_h, _direction, _drawingInfo, _barCount) {
69220
+ return 0;
69221
+ }
69222
+ calculateBeamYWithDirection(h, _x, direction) {
69223
+ this.ensureBeamDrawingInfo(h, direction);
69224
+ const info = h.drawingInfos.get(direction);
69225
+ if (direction === BeamDirection.Up) {
69226
+ return Math.min(info.startY, info.endY);
69227
+ }
69228
+ else {
69229
+ return Math.max(info.startY, info.endY);
69230
+ }
69231
+ }
69011
69232
  }
69012
69233
 
69013
69234
  /**
@@ -69176,132 +69397,6 @@ class KeySignatureGlyph extends LeftToRightLayoutingGlyphGroup {
69176
69397
  }
69177
69398
  }
69178
69399
 
69179
- /**
69180
- * @internal
69181
- */
69182
- class NoteHeadGlyphBase extends MusicFontGlyph {
69183
- centerOnStem = false;
69184
- constructor(x, y, isGrace, symbol) {
69185
- super(x, y, isGrace ? EngravingSettings.GraceScale : 1, symbol);
69186
- }
69187
- paint(cx, cy, canvas) {
69188
- if (this.centerOnStem) {
69189
- this.center = true;
69190
- }
69191
- super.paint(cx, cy, canvas);
69192
- }
69193
- }
69194
- /**
69195
- * @internal
69196
- */
69197
- class NoteHeadGlyph extends NoteHeadGlyphBase {
69198
- constructor(x, y, duration, isGrace) {
69199
- super(x, y, isGrace, NoteHeadGlyph.getSymbol(duration));
69200
- }
69201
- static getSymbol(duration) {
69202
- switch (duration) {
69203
- case Duration.QuadrupleWhole:
69204
- return MusicFontSymbol.NoteheadDoubleWholeSquare;
69205
- case Duration.DoubleWhole:
69206
- return MusicFontSymbol.NoteheadDoubleWhole;
69207
- case Duration.Whole:
69208
- return MusicFontSymbol.NoteheadWhole;
69209
- case Duration.Half:
69210
- return MusicFontSymbol.NoteheadHalf;
69211
- default:
69212
- return MusicFontSymbol.NoteheadBlack;
69213
- }
69214
- }
69215
- }
69216
-
69217
- /**
69218
- * @internal
69219
- */
69220
- class SlashNoteHeadGlyph extends NoteHeadGlyphBase {
69221
- beatEffects = new Map();
69222
- noteHeadElement = NoteSubElement.SlashNoteHead;
69223
- effectElement = BeatSubElement.SlashEffects;
69224
- _symbol;
69225
- stemX = 0;
69226
- constructor(x, y, duration, isGrace, beat) {
69227
- super(x, y, isGrace, SlashNoteHeadGlyph.getSymbol(duration));
69228
- this._symbol = SlashNoteHeadGlyph.getSymbol(duration);
69229
- this.beat = beat;
69230
- }
69231
- paint(cx, cy, canvas) {
69232
- const _ = this.beat.notes.length === 0
69233
- ? undefined
69234
- : ElementStyleHelper.note(canvas, this.noteHeadElement, this.beat.notes[0]);
69235
- try {
69236
- super.paint(cx, cy, canvas);
69237
- this._paintEffects(cx, cy, canvas);
69238
- }
69239
- finally {
69240
- _?.[Symbol.dispose]?.();
69241
- }
69242
- }
69243
- _paintEffects(cx, cy, canvas) {
69244
- const _ = ElementStyleHelper.beat(canvas, this.effectElement, this.beat);
69245
- try {
69246
- for (const g of this.beatEffects.values()) {
69247
- g.paint(cx + this.x, cy + this.y, canvas);
69248
- }
69249
- }
69250
- finally {
69251
- _?.[Symbol.dispose]?.();
69252
- }
69253
- }
69254
- doLayout() {
69255
- super.doLayout();
69256
- const effectSpacing = this.renderer.smuflMetrics.onNoteEffectPadding;
69257
- let effectY = this.renderer.smuflMetrics.glyphHeights.get(this._symbol);
69258
- let minEffectY = Number.NaN;
69259
- let maxEffectY = Number.NaN;
69260
- for (const g of this.beatEffects.values()) {
69261
- g.y += effectY;
69262
- g.x += this.width / 2;
69263
- g.renderer = this.renderer;
69264
- effectY += g.height + effectSpacing;
69265
- g.doLayout();
69266
- if (Number.isNaN(minEffectY) || minEffectY > effectY) {
69267
- minEffectY = effectY;
69268
- }
69269
- if (Number.isNaN(maxEffectY) || maxEffectY < effectY) {
69270
- maxEffectY = effectY;
69271
- }
69272
- }
69273
- if (!Number.isNaN(minEffectY)) {
69274
- this.renderer.registerBeatEffectOverflows(minEffectY, maxEffectY);
69275
- }
69276
- const direction = this.renderer.getBeatDirection(this.beat);
69277
- const symbol = this._symbol;
69278
- if (direction === BeamDirection.Up) {
69279
- const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
69280
- ? this.renderer.smuflMetrics.stemUp.get(symbol).x
69281
- : 0;
69282
- this.stemX = stemInfoUp;
69283
- }
69284
- else {
69285
- const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69286
- ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69287
- : 0;
69288
- this.stemX = stemInfoDown;
69289
- }
69290
- }
69291
- static getSymbol(duration) {
69292
- switch (duration) {
69293
- case Duration.QuadrupleWhole:
69294
- case Duration.DoubleWhole:
69295
- case Duration.Whole:
69296
- return MusicFontSymbol.NoteheadSlashWhiteWhole;
69297
- case Duration.Half:
69298
- return MusicFontSymbol.NoteheadSlashWhiteHalf;
69299
- default:
69300
- return MusicFontSymbol.NoteheadSlashHorizontalEnds;
69301
- }
69302
- }
69303
- }
69304
-
69305
69400
  /**
69306
69401
  * @internal
69307
69402
  */
@@ -69351,6 +69446,44 @@ class ArticStaccatoAboveGlyph extends MusicFontGlyph {
69351
69446
  }
69352
69447
  }
69353
69448
 
69449
+ /**
69450
+ * @internal
69451
+ */
69452
+ class NoteHeadGlyphBase extends MusicFontGlyph {
69453
+ centerOnStem = false;
69454
+ constructor(x, y, isGrace, symbol) {
69455
+ super(x, y, isGrace ? EngravingSettings.GraceScale : 1, symbol);
69456
+ }
69457
+ paint(cx, cy, canvas) {
69458
+ if (this.centerOnStem) {
69459
+ this.center = true;
69460
+ }
69461
+ super.paint(cx, cy, canvas);
69462
+ }
69463
+ }
69464
+ /**
69465
+ * @internal
69466
+ */
69467
+ class NoteHeadGlyph extends NoteHeadGlyphBase {
69468
+ constructor(x, y, duration, isGrace) {
69469
+ super(x, y, isGrace, NoteHeadGlyph.getSymbol(duration));
69470
+ }
69471
+ static getSymbol(duration) {
69472
+ switch (duration) {
69473
+ case Duration.QuadrupleWhole:
69474
+ return MusicFontSymbol.NoteheadDoubleWholeSquare;
69475
+ case Duration.DoubleWhole:
69476
+ return MusicFontSymbol.NoteheadDoubleWhole;
69477
+ case Duration.Whole:
69478
+ return MusicFontSymbol.NoteheadWhole;
69479
+ case Duration.Half:
69480
+ return MusicFontSymbol.NoteheadHalf;
69481
+ default:
69482
+ return MusicFontSymbol.NoteheadBlack;
69483
+ }
69484
+ }
69485
+ }
69486
+
69354
69487
  /**
69355
69488
  * @internal
69356
69489
  */
@@ -69714,8 +69847,8 @@ class ScoreNoteChordGlyphBase extends Glyph {
69714
69847
  _infos = [];
69715
69848
  _noteHeadInfo;
69716
69849
  noteGroup;
69717
- minNote = null;
69718
- maxNote = null;
69850
+ minStepsNote = null;
69851
+ maxStepsNote = null;
69719
69852
  get stemX() {
69720
69853
  if (!this.noteGroup) {
69721
69854
  return 0;
@@ -69728,25 +69861,19 @@ class ScoreNoteChordGlyphBase extends Glyph {
69728
69861
  super(0, 0);
69729
69862
  }
69730
69863
  getBoundingBoxTop() {
69731
- return this.minNote ? this.minNote.glyph.getBoundingBoxTop() : this.y;
69864
+ return this.minStepsNote ? this.minStepsNote.glyph.getBoundingBoxTop() : this.y;
69732
69865
  }
69733
69866
  getBoundingBoxBottom() {
69734
- return this.maxNote ? this.maxNote.glyph.getBoundingBoxBottom() : this.y + this.height;
69735
- }
69736
- getLowestNoteY() {
69737
- return this.maxNote ? this.renderer.getScoreY(this.maxNote.steps) : 0;
69738
- }
69739
- getHighestNoteY() {
69740
- return this.minNote ? this.renderer.getScoreY(this.minNote.steps) : 0;
69867
+ return this.maxStepsNote ? this.maxStepsNote.glyph.getBoundingBoxBottom() : this.y + this.height;
69741
69868
  }
69742
69869
  add(noteGlyph, noteSteps) {
69743
69870
  const info = { glyph: noteGlyph, steps: noteSteps };
69744
69871
  this._infos.push(info);
69745
- if (!this.minNote || this.minNote.steps > info.steps) {
69746
- this.minNote = info;
69872
+ if (!this.minStepsNote || this.minStepsNote.steps > info.steps) {
69873
+ this.minStepsNote = info;
69747
69874
  }
69748
- if (!this.maxNote || this.maxNote.steps < info.steps) {
69749
- this.maxNote = info;
69875
+ if (!this.maxStepsNote || this.maxStepsNote.steps < info.steps) {
69876
+ this.maxStepsNote = info;
69750
69877
  }
69751
69878
  }
69752
69879
  _prepareForLayout(info) {
@@ -69982,7 +70109,7 @@ class ScoreNoteChordGlyphBase extends Glyph {
69982
70109
  }
69983
70110
  }
69984
70111
  _paintLedgerLines(cx, cy, canvas) {
69985
- if (!this.minNote) {
70112
+ if (!this.minStepsNote) {
69986
70113
  return;
69987
70114
  }
69988
70115
  const scoreRenderer = this.renderer;
@@ -69994,8 +70121,8 @@ class ScoreNoteChordGlyphBase extends Glyph {
69994
70121
  const lineSpacing = scoreRenderer.getLineHeight(1);
69995
70122
  const firstTopLedgerY = scoreRenderer.getLineY(-1);
69996
70123
  const firstBottomLedgerY = scoreRenderer.getLineY(scoreRenderer.drawnLineCount);
69997
- const minNoteLineY = scoreRenderer.getLineY(this.minNote.steps / 2);
69998
- const maxNoteLineY = scoreRenderer.getLineY(this.maxNote.steps / 2);
70124
+ const minNoteLineY = scoreRenderer.getLineY(this.minStepsNote.steps / 2);
70125
+ const maxNoteLineY = scoreRenderer.getLineY(this.maxStepsNote.steps / 2);
69999
70126
  const lineYOffset = (this.renderer.smuflMetrics.legerLineThickness * scale) / 2;
70000
70127
  let y = firstTopLedgerY;
70001
70128
  while (y >= minNoteLineY) {
@@ -70018,21 +70145,87 @@ class ScoreNoteChordGlyphBase extends Glyph {
70018
70145
  * @internal
70019
70146
  */
70020
70147
  class TremoloPickingGlyph extends MusicFontGlyph {
70021
- constructor(x, y, duration) {
70022
- super(x, y, 1, TremoloPickingGlyph._getSymbol(duration));
70148
+ constructor(x, y, effect) {
70149
+ super(x, y, 1, TremoloPickingGlyph._getSymbol(effect));
70023
70150
  }
70024
- static _getSymbol(duration) {
70025
- switch (duration) {
70026
- case Duration.ThirtySecond:
70027
- return MusicFontSymbol.Tremolo3;
70028
- case Duration.Sixteenth:
70029
- return MusicFontSymbol.Tremolo2;
70030
- case Duration.Eighth:
70031
- return MusicFontSymbol.Tremolo1;
70032
- default:
70033
- return MusicFontSymbol.None;
70151
+ static _getSymbol(effect) {
70152
+ if (effect.style === TremoloPickingStyle.BuzzRoll) {
70153
+ return MusicFontSymbol.BuzzRoll;
70154
+ }
70155
+ else {
70156
+ switch (effect.marks) {
70157
+ case 1:
70158
+ return MusicFontSymbol.Tremolo1;
70159
+ case 2:
70160
+ return MusicFontSymbol.Tremolo2;
70161
+ case 3:
70162
+ return MusicFontSymbol.Tremolo3;
70163
+ case 4:
70164
+ return MusicFontSymbol.Tremolo4;
70165
+ case 5:
70166
+ return MusicFontSymbol.Tremolo5;
70167
+ default:
70168
+ return MusicFontSymbol.None;
70169
+ }
70034
70170
  }
70035
70171
  }
70172
+ stemExtensionHeight = 0;
70173
+ alignTremoloPickingGlyph(direction, flagEnd, firstNoteY, duration) {
70174
+ const lr = this.renderer;
70175
+ const smufl = lr.smuflMetrics;
70176
+ let tremoloY = 0;
70177
+ const tremoloOverlap = smufl.glyphHeights.get(MusicFontSymbol.Tremolo1) / 2;
70178
+ const tremoloCenterOffset = this.height / 2;
70179
+ // whether the center or top bar should be aligned with a staff line
70180
+ const forceAlignWithStaffLine = this.symbol === MusicFontSymbol.Tremolo1;
70181
+ const lineSpacing = lr.lineSpacing;
70182
+ const spacing = forceAlignWithStaffLine ? lineSpacing : lineSpacing / 2;
70183
+ if (direction === BeamDirection.Up) {
70184
+ // start at note
70185
+ let flagBottom = flagEnd;
70186
+ // to bottom of stem
70187
+ flagBottom += smufl.stemFlagHeight.get(duration);
70188
+ flagBottom -= smufl.stemFlagOffsets.get(duration);
70189
+ // align with closest step line
70190
+ tremoloY = spacing * Math.ceil(flagBottom / spacing);
70191
+ // ensure at least 1 staff space distance between note and tremolo bottom bar
70192
+ const tremoloBottomY = tremoloY + tremoloCenterOffset;
70193
+ const minSpacingY = firstNoteY - lineSpacing;
70194
+ if (minSpacingY < tremoloBottomY) {
70195
+ tremoloY = minSpacingY - tremoloCenterOffset;
70196
+ }
70197
+ // reserve the additional space needed in the stem height
70198
+ flagBottom += tremoloOverlap;
70199
+ const tremoloTop = tremoloY - tremoloCenterOffset;
70200
+ if (flagBottom > tremoloTop) {
70201
+ this.stemExtensionHeight = flagBottom - tremoloTop;
70202
+ }
70203
+ else {
70204
+ this.stemExtensionHeight = 0;
70205
+ }
70206
+ }
70207
+ else {
70208
+ // same logic as above but inverted
70209
+ let flagTop = flagEnd;
70210
+ flagTop -= smufl.stemFlagHeight.get(duration);
70211
+ flagTop += smufl.stemFlagOffsets.get(duration);
70212
+ tremoloY = spacing * Math.floor(flagTop / spacing);
70213
+ const tremoloTopY = tremoloY - tremoloCenterOffset;
70214
+ const minSpacingY = firstNoteY + lineSpacing;
70215
+ if (minSpacingY > tremoloTopY) {
70216
+ tremoloY = minSpacingY + tremoloCenterOffset;
70217
+ }
70218
+ flagTop -= tremoloOverlap;
70219
+ const tremoloBottom = tremoloY + tremoloCenterOffset;
70220
+ if (flagTop < tremoloBottom) {
70221
+ this.stemExtensionHeight = tremoloBottom - flagTop;
70222
+ }
70223
+ else {
70224
+ this.stemExtensionHeight = 0;
70225
+ }
70226
+ }
70227
+ this.y = tremoloY;
70228
+ }
70036
70229
  }
70037
70230
 
70038
70231
  /**
@@ -70043,6 +70236,7 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70043
70236
  _notes = [];
70044
70237
  _deadSlapped = null;
70045
70238
  _tremoloPicking = null;
70239
+ _stemLengthExtension = 0;
70046
70240
  aboveBeatEffects = new Map();
70047
70241
  belowBeatEffects = new Map();
70048
70242
  beat;
@@ -70064,7 +70258,7 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70064
70258
  return new ScoreChordNoteHeadInfo(this.direction);
70065
70259
  }
70066
70260
  const staff = this.beat.voice.bar.staff;
70067
- const key = `score.noteheads.${staff.track.index}.${staff.index}.${this.beat.absoluteDisplayStart}`;
70261
+ const key = `score.noteheads.${staff.track.index}.${staff.index}.${this.beat.voice.bar.index}.${this.beat.absoluteDisplayStart}`;
70068
70262
  let existing = this.renderer.staff.getSharedLayoutData(key, undefined);
70069
70263
  if (!existing) {
70070
70264
  existing = new ScoreChordNoteHeadInfo(this.direction);
@@ -70097,19 +70291,26 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70097
70291
  }
70098
70292
  return 0;
70099
70293
  }
70294
+ getLowestNoteY(requestedPosition) {
70295
+ return this.maxStepsNote ? this._internalGetNoteY(this.maxStepsNote.glyph, requestedPosition) : 0;
70296
+ }
70297
+ getHighestNoteY(requestedPosition) {
70298
+ return this.minStepsNote ? this._internalGetNoteY(this.minStepsNote.glyph, requestedPosition) : 0;
70299
+ }
70100
70300
  _internalGetNoteY(n, requestedPosition) {
70101
70301
  let pos = this.y + n.y;
70302
+ const sr = this.renderer;
70102
70303
  const scale = this.beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
70103
70304
  switch (requestedPosition) {
70104
70305
  case NoteYPosition.TopWithStem:
70105
70306
  // stem start
70106
70307
  pos -=
70107
- (this.renderer.smuflMetrics.stemUp.has(n.symbol)
70108
- ? this.renderer.smuflMetrics.stemUp.get(n.symbol).bottomY
70109
- : 0) * scale;
70308
+ (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol).bottomY : 0) * scale;
70110
70309
  // stem size according to duration
70111
- pos -= this.renderer.smuflMetrics.standardStemLength * scale;
70112
- const topCenterY = this.renderer.centerStaffStemY(this.direction);
70310
+ pos -= sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
70311
+ pos -= this._stemLengthExtension;
70312
+ let topCenterY = sr.centerStaffStemY(this.direction);
70313
+ topCenterY -= this._stemLengthExtension;
70113
70314
  return Math.min(topCenterY, pos);
70114
70315
  case NoteYPosition.Top:
70115
70316
  pos -= n.height / 2;
@@ -70125,20 +70326,20 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70125
70326
  ? this.renderer.smuflMetrics.stemDown.get(n.symbol).topY
70126
70327
  : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70127
70328
  // stem size according to duration
70128
- pos += this.renderer.smuflMetrics.standardStemLength * scale;
70129
- const bottomCenterY = this.renderer.centerStaffStemY(this.direction);
70329
+ pos += sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
70330
+ pos += this._stemLengthExtension;
70331
+ let bottomCenterY = sr.centerStaffStemY(this.direction);
70332
+ bottomCenterY += this._stemLengthExtension;
70130
70333
  return Math.max(bottomCenterY, pos);
70131
70334
  case NoteYPosition.StemUp:
70132
70335
  pos -=
70133
- (this.renderer.smuflMetrics.stemUp.has(n.symbol)
70134
- ? this.renderer.smuflMetrics.stemUp.get(n.symbol).bottomY
70135
- : 0) * scale;
70336
+ (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol).bottomY : 0) * scale;
70136
70337
  break;
70137
70338
  case NoteYPosition.StemDown:
70138
70339
  pos -=
70139
- (this.renderer.smuflMetrics.stemDown.has(n.symbol)
70140
- ? this.renderer.smuflMetrics.stemDown.get(n.symbol).topY
70141
- : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70340
+ (sr.smuflMetrics.stemDown.has(n.symbol)
70341
+ ? sr.smuflMetrics.stemDown.get(n.symbol).topY
70342
+ : -sr.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70142
70343
  break;
70143
70344
  }
70144
70345
  return pos;
@@ -70170,14 +70371,15 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70170
70371
  }
70171
70372
  else {
70172
70373
  if (this.direction === BeamDirection.Up) {
70173
- belowBeatEffectsY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.Bottom) + effectSpacing;
70374
+ belowBeatEffectsY =
70375
+ this._internalGetNoteY(this.maxStepsNote.glyph, NoteYPosition.Bottom) + effectSpacing;
70174
70376
  aboveBeatEffectsY =
70175
- this._internalGetNoteY(this.minNote.glyph, NoteYPosition.TopWithStem) - effectSpacing;
70377
+ this._internalGetNoteY(this.minStepsNote.glyph, NoteYPosition.TopWithStem) - effectSpacing;
70176
70378
  }
70177
70379
  else {
70178
70380
  belowBeatEffectsY =
70179
- this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.BottomWithStem) + effectSpacing;
70180
- aboveBeatEffectsY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.Top) - effectSpacing;
70381
+ this._internalGetNoteY(this.maxStepsNote.glyph, NoteYPosition.BottomWithStem) + effectSpacing;
70382
+ aboveBeatEffectsY = this._internalGetNoteY(this.minStepsNote.glyph, NoteYPosition.Top) - effectSpacing;
70181
70383
  }
70182
70384
  }
70183
70385
  let minEffectY = null;
@@ -70211,27 +70413,27 @@ class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
70211
70413
  scoreRenderer.registerBeatEffectOverflows(minEffectY, maxEffectY ?? 0);
70212
70414
  }
70213
70415
  if (this.beat.isTremolo && !this.beat.deadSlapped) {
70214
- const direction = this.direction;
70215
- let tremoloY = 0;
70216
- if (direction === BeamDirection.Up) {
70217
- const topY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.TopWithStem);
70218
- const bottomY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.StemUp);
70219
- tremoloY = (topY + bottomY) / 2;
70220
- }
70221
- else {
70222
- const topY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.StemDown);
70223
- const bottomY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.BottomWithStem);
70224
- tremoloY = (topY + bottomY) / 2;
70225
- }
70226
- let tremoloX = this.stemX;
70227
- const speed = this.beat.tremoloSpeed;
70228
- if (this.beat.duration < Duration.Half) {
70229
- tremoloX = this.width / 2;
70230
- }
70231
- this._tremoloPicking = new TremoloPickingGlyph(tremoloX, tremoloY, speed);
70416
+ this._tremoloPicking = new TremoloPickingGlyph(0, 0, this.beat.tremoloPicking);
70232
70417
  this._tremoloPicking.renderer = this.renderer;
70233
70418
  this._tremoloPicking.doLayout();
70419
+ this._alignTremoloPickingGlyph();
70420
+ }
70421
+ }
70422
+ _alignTremoloPickingGlyph() {
70423
+ const g = this._tremoloPicking;
70424
+ const direction = this.direction;
70425
+ if (direction === BeamDirection.Up) {
70426
+ g.alignTremoloPickingGlyph(direction, this.getHighestNoteY(NoteYPosition.TopWithStem), this.getHighestNoteY(NoteYPosition.Center), this.beat.duration);
70427
+ }
70428
+ else {
70429
+ g.alignTremoloPickingGlyph(direction, this.getLowestNoteY(NoteYPosition.BottomWithStem), this.getLowestNoteY(NoteYPosition.Center), this.beat.duration);
70430
+ }
70431
+ this._stemLengthExtension = g.stemExtensionHeight;
70432
+ let tremoloX = this.stemX;
70433
+ if (this.beat.duration < Duration.Half) {
70434
+ tremoloX = this.width / 2;
70234
70435
  }
70436
+ g.x = tremoloX;
70235
70437
  }
70236
70438
  buildBoundingsLookup(beatBounds, cx, cy) {
70237
70439
  for (const note of this._notes) {
@@ -70480,17 +70682,17 @@ class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
70480
70682
  if (!endGlyph) {
70481
70683
  return false;
70482
70684
  }
70483
- return !!endGlyph.minNote && !!endGlyph.maxNote;
70685
+ return !!endGlyph.minStepsNote && !!endGlyph.maxStepsNote;
70484
70686
  }
70485
70687
  getBoundingBoxTop() {
70486
- if (this._endGlyph?.minNote) {
70487
- return this._endGlyph.minNote.glyph.getBoundingBoxTop();
70688
+ if (this._endGlyph?.minStepsNote) {
70689
+ return this._endGlyph.minStepsNote.glyph.getBoundingBoxTop();
70488
70690
  }
70489
70691
  return super.getBoundingBoxTop();
70490
70692
  }
70491
70693
  getBoundingBoxBottom() {
70492
- if (this._endGlyph?.maxNote) {
70493
- return this._endGlyph.maxNote.glyph.getBoundingBoxBottom();
70694
+ if (this._endGlyph?.maxStepsNote) {
70695
+ return this._endGlyph.maxStepsNote.glyph.getBoundingBoxBottom();
70494
70696
  }
70495
70697
  return super.getBoundingBoxBottom();
70496
70698
  }
@@ -70723,6 +70925,89 @@ class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
70723
70925
  }
70724
70926
  }
70725
70927
 
70928
+ /**
70929
+ * @internal
70930
+ */
70931
+ class SlashNoteHeadGlyph extends NoteHeadGlyphBase {
70932
+ beatEffects = new Map();
70933
+ noteHeadElement = NoteSubElement.SlashNoteHead;
70934
+ effectElement = BeatSubElement.SlashEffects;
70935
+ stemX = 0;
70936
+ constructor(x, y, beat) {
70937
+ super(x, y, beat.graceType !== GraceType.None, SlashNoteHeadGlyph.getSymbol(beat.duration));
70938
+ this.beat = beat;
70939
+ }
70940
+ paint(cx, cy, canvas) {
70941
+ const _ = this.beat.notes.length === 0
70942
+ ? undefined
70943
+ : ElementStyleHelper.note(canvas, this.noteHeadElement, this.beat.notes[0]);
70944
+ try {
70945
+ super.paint(cx, cy, canvas);
70946
+ this._paintEffects(cx, cy, canvas);
70947
+ }
70948
+ finally {
70949
+ _?.[Symbol.dispose]?.();
70950
+ }
70951
+ }
70952
+ _paintEffects(cx, cy, canvas) {
70953
+ const _ = ElementStyleHelper.beat(canvas, this.effectElement, this.beat);
70954
+ try {
70955
+ for (const g of this.beatEffects.values()) {
70956
+ g.paint(cx + this.x, cy + this.y, canvas);
70957
+ }
70958
+ }
70959
+ finally {
70960
+ _?.[Symbol.dispose]?.();
70961
+ }
70962
+ }
70963
+ doLayout() {
70964
+ super.doLayout();
70965
+ const lr = this.renderer;
70966
+ const effectSpacing = lr.smuflMetrics.onNoteEffectPadding;
70967
+ let effectY = lr.smuflMetrics.glyphHeights.get(this.symbol);
70968
+ let minEffectY = Number.NaN;
70969
+ let maxEffectY = Number.NaN;
70970
+ for (const g of this.beatEffects.values()) {
70971
+ g.y += effectY;
70972
+ g.x += this.width / 2;
70973
+ g.renderer = lr;
70974
+ effectY += g.height + effectSpacing;
70975
+ g.doLayout();
70976
+ if (Number.isNaN(minEffectY) || minEffectY > effectY) {
70977
+ minEffectY = effectY;
70978
+ }
70979
+ if (Number.isNaN(maxEffectY) || maxEffectY < effectY) {
70980
+ maxEffectY = effectY;
70981
+ }
70982
+ }
70983
+ if (!Number.isNaN(minEffectY)) {
70984
+ lr.registerBeatEffectOverflows(minEffectY, maxEffectY);
70985
+ }
70986
+ const direction = lr.getBeatDirection(this.beat);
70987
+ const symbol = this.symbol;
70988
+ if (direction === BeamDirection.Up) {
70989
+ const stemInfoUp = lr.smuflMetrics.stemUp.has(symbol) ? lr.smuflMetrics.stemUp.get(symbol).x : 0;
70990
+ this.stemX = stemInfoUp;
70991
+ }
70992
+ else {
70993
+ const stemInfoDown = lr.smuflMetrics.stemDown.has(symbol) ? lr.smuflMetrics.stemDown.get(symbol).x : 0;
70994
+ this.stemX = stemInfoDown;
70995
+ }
70996
+ }
70997
+ static getSymbol(duration) {
70998
+ switch (duration) {
70999
+ case Duration.QuadrupleWhole:
71000
+ case Duration.DoubleWhole:
71001
+ case Duration.Whole:
71002
+ return MusicFontSymbol.NoteheadSlashWhiteWhole;
71003
+ case Duration.Half:
71004
+ return MusicFontSymbol.NoteheadSlashWhiteHalf;
71005
+ default:
71006
+ return MusicFontSymbol.NoteheadSlashHorizontalEnds;
71007
+ }
71008
+ }
71009
+ }
71010
+
70726
71011
  /**
70727
71012
  * @internal
70728
71013
  */
@@ -70761,9 +71046,6 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70761
71046
  get effectElement() {
70762
71047
  return BeatSubElement.StandardNotationEffects;
70763
71048
  }
70764
- getNoteX(note, requestedPosition) {
70765
- return this.noteHeads ? this.noteHeads.getNoteX(note, requestedPosition) : 0;
70766
- }
70767
71049
  buildBoundingsLookup(beatBounds, cx, cy) {
70768
71050
  if (this.noteHeads) {
70769
71051
  this.noteHeads.buildBoundingsLookup(beatBounds, cx + this.x, cy + this.y);
@@ -70795,20 +71077,34 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70795
71077
  }
70796
71078
  return y;
70797
71079
  }
70798
- getLowestNoteY() {
70799
- return this.noteHeads ? this.noteHeads.getLowestNoteY() : 0;
71080
+ getLowestNoteY(requestedPosition) {
71081
+ // NOTE: slash handled automatically
71082
+ return this.noteHeads ? this.noteHeads.getLowestNoteY(requestedPosition) : 0;
70800
71083
  }
70801
- getHighestNoteY() {
70802
- return this.noteHeads ? this.noteHeads.getHighestNoteY() : 0;
71084
+ getHighestNoteY(requestedPosition) {
71085
+ // NOTE: slash handled automatically
71086
+ return this.noteHeads ? this.noteHeads.getHighestNoteY(requestedPosition) : 0;
70803
71087
  }
70804
71088
  getNoteY(note, requestedPosition) {
71089
+ // for slashed beats always lookup first note
71090
+ if (note.beat.slashed) {
71091
+ note = note.beat.notes[0];
71092
+ }
70805
71093
  return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
70806
71094
  }
71095
+ getNoteX(note, requestedPosition) {
71096
+ // for slashed beats always lookup first note
71097
+ if (note.beat.slashed) {
71098
+ note = note.beat.notes[0];
71099
+ }
71100
+ return this.noteHeads ? this.noteHeads.getNoteX(note, requestedPosition) : 0;
71101
+ }
70807
71102
  getRestY(requestedPosition) {
70808
71103
  const g = this.restGlyph;
70809
71104
  if (g) {
70810
71105
  switch (requestedPosition) {
70811
71106
  case NoteYPosition.TopWithStem:
71107
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
70812
71108
  case NoteYPosition.Top:
70813
71109
  return g.getBoundingBoxTop();
70814
71110
  case NoteYPosition.Center:
@@ -70816,8 +71112,9 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70816
71112
  case NoteYPosition.StemDown:
70817
71113
  return g.getBoundingBoxTop() + g.height / 2;
70818
71114
  case NoteYPosition.Bottom:
70819
- case NoteYPosition.BottomWithStem:
70820
71115
  return g.getBoundingBoxBottom();
71116
+ case NoteYPosition.BottomWithStem:
71117
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
70821
71118
  }
70822
71119
  }
70823
71120
  return 0;
@@ -70900,10 +71197,18 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70900
71197
  noteHeads.beat = this.container.beat;
70901
71198
  const ghost = new GhostNoteContainerGlyph(false);
70902
71199
  ghost.renderer = this.renderer;
70903
- for (const note of this.container.beat.notes) {
70904
- if (note.isVisible && (!note.beat.slashed || note.index === 0)) {
70905
- this._createNoteGlyph(note);
70906
- ghost.addParenthesis(note);
71200
+ if (this.container.beat.slashed) {
71201
+ const steps = sr.heightLineCount - 1;
71202
+ const slash = new SlashNoteHeadGlyph(0, sr.getScoreY(steps), this.container.beat);
71203
+ slash.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationNoteHead, this.container.beat.notes[0]);
71204
+ this.noteHeads.addMainNoteGlyph(slash, this.container.beat.notes[0], steps);
71205
+ }
71206
+ else {
71207
+ for (const note of this.container.beat.notes) {
71208
+ if (note.isVisible) {
71209
+ this._createNoteGlyph(note);
71210
+ ghost.addParenthesis(note);
71211
+ }
70907
71212
  }
70908
71213
  }
70909
71214
  this.addNormal(noteHeads);
@@ -70932,6 +71237,24 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70932
71237
  this.addEffect(group);
70933
71238
  }
70934
71239
  }
71240
+ if (this.renderer.bar.isMultiVoice) {
71241
+ let highestNotePosition = 0;
71242
+ let lowestNotePosition = 0;
71243
+ const direction = sr.getBeatDirection(this.container.beat);
71244
+ let offset = 0;
71245
+ if (this.container.beat.hasTuplet) {
71246
+ offset += sr.tupletOffset + sr.tupletSize;
71247
+ }
71248
+ if (direction === BeamDirection.Up) {
71249
+ highestNotePosition = this.getHighestNoteY(NoteYPosition.TopWithStem) - offset;
71250
+ lowestNotePosition = this.getLowestNoteY(NoteYPosition.Bottom);
71251
+ }
71252
+ else {
71253
+ highestNotePosition = this.getHighestNoteY(NoteYPosition.Top);
71254
+ lowestNotePosition = this.getLowestNoteY(NoteYPosition.BottomWithStem) + offset;
71255
+ }
71256
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, highestNotePosition, lowestNotePosition);
71257
+ }
70935
71258
  }
70936
71259
  _createRestGlyphs() {
70937
71260
  const sr = this.renderer;
@@ -70998,9 +71321,6 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
70998
71321
  }
70999
71322
  Logger.warning('Rendering', `No articulation found for percussion instrument ${n.percussionArticulation}`);
71000
71323
  }
71001
- if (n.beat.slashed) {
71002
- return new SlashNoteHeadGlyph(0, 0, n.beat.duration, isGrace, n.beat);
71003
- }
71004
71324
  if (n.isDead) {
71005
71325
  return new DeadNoteHeadGlyph(0, 0, isGrace);
71006
71326
  }
@@ -71020,13 +71340,7 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
71020
71340
  const noteHeadGlyph = this._createNoteHeadGlyph(n);
71021
71341
  noteHeadGlyph.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationNoteHead, n);
71022
71342
  // calculate y position
71023
- let steps;
71024
- if (n.beat.slashed) {
71025
- steps = sr.heightLineCount - 1;
71026
- }
71027
- else {
71028
- steps = sr.getNoteSteps(n);
71029
- }
71343
+ let steps = sr.getNoteSteps(n);
71030
71344
  noteHeadGlyph.y = sr.getScoreY(steps);
71031
71345
  this.noteHeads.addMainNoteGlyph(noteHeadGlyph, n, steps);
71032
71346
  if (!n.beat.slashed && n.harmonicType !== HarmonicType.None && n.harmonicType !== HarmonicType.Natural) {
@@ -71040,7 +71354,7 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
71040
71354
  }
71041
71355
  const belowBeatEffects = this.noteHeads.belowBeatEffects;
71042
71356
  const aboveBeatEffects = this.noteHeads.aboveBeatEffects;
71043
- const outsideBeatEffects = this.renderer.getBeatDirection(this.container.beat) === BeamDirection.Up
71357
+ const outsideBeatEffects = sr.getBeatDirection(this.container.beat) === BeamDirection.Up
71044
71358
  ? this.noteHeads.belowBeatEffects
71045
71359
  : this.noteHeads.aboveBeatEffects;
71046
71360
  if (n.isStaccato && !belowBeatEffects.has('Staccato')) {
@@ -71357,16 +71671,16 @@ class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
71357
71671
  switch (note.bendType) {
71358
71672
  case BendType.Bend:
71359
71673
  case BendType.PrebendBend:
71360
- endY = this._endNoteGlyph.minNote.glyph.getBoundingBoxTop();
71674
+ endY = this._endNoteGlyph.minStepsNote.glyph.getBoundingBoxTop();
71361
71675
  endX = width;
71362
71676
  break;
71363
71677
  case BendType.BendRelease:
71364
- endY = this._middleNoteGlyph.minNote.glyph.getBoundingBoxTop();
71678
+ endY = this._middleNoteGlyph.minStepsNote.glyph.getBoundingBoxTop();
71365
71679
  endX = width / 2;
71366
71680
  break;
71367
71681
  case BendType.Release:
71368
71682
  case BendType.PrebendRelease:
71369
- endY = this._endNoteGlyph.maxNote.glyph.getBoundingBoxTop();
71683
+ endY = this._endNoteGlyph.maxStepsNote.glyph.getBoundingBoxTop();
71370
71684
  endX = width;
71371
71685
  break;
71372
71686
  }
@@ -72056,15 +72370,15 @@ class ScoreBeatContainerGlyph extends BeatContainerGlyph {
72056
72370
  const beat = this.beat;
72057
72371
  const isGrace = beat.graceType !== GraceType.None;
72058
72372
  if (sr.hasFlag(beat)) {
72059
- const direction = this.renderer.getBeatDirection(beat);
72373
+ const direction = sr.getBeatDirection(beat);
72060
72374
  const scale = isGrace ? EngravingSettings.GraceScale : 1;
72061
72375
  const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
72062
- const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
72376
+ const flagWidth = sr.smuflMetrics.glyphWidths.get(symbol) * scale;
72063
72377
  this._flagStretch = flagWidth;
72064
72378
  }
72065
72379
  else if (isGrace) {
72066
72380
  // always use flag size as spacing on grace notes
72067
- const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72381
+ const graceSpacing = sr.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72068
72382
  this._flagStretch = graceSpacing;
72069
72383
  }
72070
72384
  super.doLayout();
@@ -72246,73 +72560,26 @@ class ScoreBarRenderer extends LineBarRenderer {
72246
72560
  get tupletSubElement() {
72247
72561
  return BeatSubElement.StandardNotationTuplet;
72248
72562
  }
72249
- _getSlashFlagY() {
72250
- const line = (this.heightLineCount - 1) / 2;
72251
- const slashY = this.getLineY(line);
72252
- return slashY;
72253
- }
72254
72563
  getFlagTopY(beat, direction) {
72255
- if (beat.slashed) {
72256
- let slashY = this._getSlashFlagY();
72257
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72258
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72259
- if (direction === BeamDirection.Down) {
72260
- slashY -= this.smuflMetrics.stemDown.has(symbol)
72261
- ? this.smuflMetrics.stemDown.get(symbol).topY * scale
72262
- : 0;
72263
- }
72264
- else {
72265
- slashY -= this.smuflMetrics.stemUp.has(symbol)
72266
- ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
72267
- : 0;
72268
- if (!beat.isRest) {
72269
- slashY -= this.smuflMetrics.standardStemLength + scale;
72270
- }
72271
- }
72272
- return slashY;
72273
- }
72274
- const minNote = this.accidentalHelper.getMinStepsNote(beat);
72275
- if (minNote) {
72276
- return this.getNoteY(minNote, direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown);
72564
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
72565
+ if (beat.isRest) {
72566
+ return this.getRestY(beat, position);
72277
72567
  }
72278
- let y = this.getScoreY(this.accidentalHelper.getMinSteps(beat));
72279
- if (direction === BeamDirection.Up && !beat.isRest) {
72280
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72281
- y -= this.smuflMetrics.standardStemLength * scale;
72568
+ else {
72569
+ return this.voiceContainer.getHighestNoteY(beat, position);
72282
72570
  }
72283
- return y;
72284
72571
  }
72285
72572
  getFlagBottomY(beat, direction) {
72286
- if (beat.slashed) {
72287
- let slashY = this._getSlashFlagY();
72288
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72289
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72290
- if (direction === BeamDirection.Down) {
72291
- slashY -= this.smuflMetrics.stemDown.has(symbol)
72292
- ? this.smuflMetrics.stemDown.get(symbol).topY * scale
72293
- : 0;
72294
- slashY += this.smuflMetrics.standardStemLength + scale;
72295
- }
72296
- else {
72297
- slashY -= this.smuflMetrics.stemUp.has(symbol)
72298
- ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
72299
- : 0;
72300
- }
72301
- return slashY;
72302
- }
72303
- const maxNote = this.accidentalHelper.getMaxStepsNote(beat);
72304
- if (maxNote) {
72305
- return this.getNoteY(maxNote, direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem);
72573
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
72574
+ if (beat.isRest) {
72575
+ return this.getRestY(beat, position);
72306
72576
  }
72307
- let y = this.getScoreY(this.accidentalHelper.getMaxSteps(beat));
72308
- if (direction === BeamDirection.Down) {
72309
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72310
- y += this.smuflMetrics.standardStemLength * scale;
72577
+ else {
72578
+ return this.voiceContainer.getLowestNoteY(beat, position);
72311
72579
  }
72312
- return y;
72313
72580
  }
72314
72581
  getBeamDirection(helper) {
72315
- return helper.direction;
72582
+ return this._beamDirections.has(helper) ? this._beamDirections.get(helper) : BeamDirection.Up;
72316
72583
  }
72317
72584
  centerStaffStemY(direction) {
72318
72585
  const isStandardFive = this.bar.staff.standardNotationLineCount === Staff.DefaultStandardNotationLineCount;
@@ -72326,50 +72593,9 @@ class ScoreBarRenderer extends LineBarRenderer {
72326
72593
  }
72327
72594
  return this.getScoreY(0);
72328
72595
  }
72329
- getStemBottomY(_beamingHelper) {
72330
- throw new Error('Method not implemented.');
72331
- }
72332
72596
  get middleYPosition() {
72333
72597
  return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
72334
72598
  }
72335
- getNoteY(note, requestedPosition) {
72336
- if (note.beat.slashed) {
72337
- const line = (this.heightLineCount - 1) / 2;
72338
- return this.getLineY(line);
72339
- }
72340
- let y = super.getNoteY(note, requestedPosition);
72341
- if (Number.isNaN(y)) {
72342
- // NOTE: some might request the note position before the glyphs have been created
72343
- // e.g. the beaming helper, for these we just need a rough
72344
- // estimate on the position
72345
- const steps = AccidentalHelper.computeStepsWithoutAccidentals(this.bar, note);
72346
- y = this.getScoreY(steps);
72347
- const scale = note.beat.graceType === GraceType.None ? 1 : EngravingSettings.GraceScale;
72348
- const stemHeight = this.smuflMetrics.standardStemLength * scale;
72349
- const noteHeadHeight = this.smuflMetrics.glyphHeights.get(NoteHeadGlyph.getSymbol(note.beat.duration)) * scale;
72350
- switch (requestedPosition) {
72351
- case NoteYPosition.TopWithStem:
72352
- y -= stemHeight;
72353
- break;
72354
- case NoteYPosition.Top:
72355
- y -= noteHeadHeight / 2;
72356
- break;
72357
- case NoteYPosition.Center:
72358
- break;
72359
- case NoteYPosition.Bottom:
72360
- y += noteHeadHeight / 2;
72361
- break;
72362
- case NoteYPosition.BottomWithStem:
72363
- y += stemHeight;
72364
- break;
72365
- case NoteYPosition.StemUp:
72366
- break;
72367
- case NoteYPosition.StemDown:
72368
- break;
72369
- }
72370
- }
72371
- return y;
72372
- }
72373
72599
  applyLayoutingInfo() {
72374
72600
  const result = super.applyLayoutingInfo();
72375
72601
  if (result && this.bar.isMultiVoice) {
@@ -72386,34 +72612,6 @@ class ScoreBarRenderer extends LineBarRenderer {
72386
72612
  }
72387
72613
  return result;
72388
72614
  }
72389
- calculateBeamYWithDirection(h, x, direction) {
72390
- if (h.beats.length === 0) {
72391
- return direction === BeamDirection.Up
72392
- ? this.getFlagTopY(h.beats[0], direction)
72393
- : this.getFlagBottomY(h.beats[0], direction);
72394
- }
72395
- this.ensureBeamDrawingInfo(h, direction);
72396
- return h.drawingInfos.get(direction).calcY(x);
72397
- }
72398
- getBarLineStart(beat, direction) {
72399
- if (beat.slashed) {
72400
- return direction === BeamDirection.Down
72401
- ? this.getFlagTopY(beat, direction)
72402
- : this.getFlagBottomY(beat, direction);
72403
- }
72404
- if (direction === BeamDirection.Up) {
72405
- const maxNote = this.accidentalHelper.getMaxStepsNote(beat);
72406
- if (maxNote) {
72407
- return this.getNoteY(maxNote, NoteYPosition.StemUp);
72408
- }
72409
- return this.getScoreY(this.accidentalHelper.getMaxSteps(beat));
72410
- }
72411
- const minNote = this.accidentalHelper.getMinStepsNote(beat);
72412
- if (minNote) {
72413
- return this.getNoteY(minNote, NoteYPosition.StemDown);
72414
- }
72415
- return this.getScoreY(this.accidentalHelper.getMinSteps(beat));
72416
- }
72417
72615
  getMinLineOfBeat(beat) {
72418
72616
  return this.accidentalHelper.getMinSteps(beat) / 2;
72419
72617
  }
@@ -72555,27 +72753,62 @@ class ScoreBarRenderer extends LineBarRenderer {
72555
72753
  getNoteSteps(n) {
72556
72754
  return this.accidentalHelper.getNoteSteps(n);
72557
72755
  }
72756
+ _beamDirections = new Map();
72558
72757
  completeBeamingHelper(helper) {
72559
- // for multi-voice bars we need to register the positions
72560
- // for multi-voice rest displacement to avoid collisions
72561
- if (this.bar.isMultiVoice && helper.highestNoteInHelper && helper.lowestNoteInHelper) {
72562
- let highestNotePosition = 0;
72563
- let lowestNotePosition = 0;
72564
- let offset = 0;
72565
- if (helper.hasTuplet) {
72566
- offset += this.resources.effectFont.size * 2;
72567
- }
72568
- if (helper.direction === BeamDirection.Up) {
72569
- highestNotePosition = this.getNoteY(helper.highestNoteInHelper, NoteYPosition.TopWithStem) - offset;
72570
- lowestNotePosition = this.getNoteY(helper.lowestNoteInHelper, NoteYPosition.Bottom);
72571
- }
72572
- else {
72573
- highestNotePosition = this.getNoteY(helper.highestNoteInHelper, NoteYPosition.Top);
72574
- lowestNotePosition = this.getNoteY(helper.lowestNoteInHelper, NoteYPosition.BottomWithStem) + offset;
72575
- }
72576
- for (const beat of helper.beats) {
72577
- this.helpers.collisionHelper.reserveBeatSlot(beat, highestNotePosition, lowestNotePosition);
72578
- }
72758
+ const direction = this._calculateBeamDirection(helper);
72759
+ this._beamDirections.set(helper, direction);
72760
+ }
72761
+ _calculateBeamDirection(helper) {
72762
+ // no proper voice (should not happen usually)
72763
+ if (!helper.voice) {
72764
+ return BeamDirection.Up;
72765
+ }
72766
+ // we have a preferred direction
72767
+ if (helper.preferredBeamDirection !== null) {
72768
+ return helper.preferredBeamDirection;
72769
+ }
72770
+ // on multi-voice setups secondary voices are always down
72771
+ if (helper.voice.index > 0) {
72772
+ return this._invertBeamDirection(helper, BeamDirection.Down);
72773
+ }
72774
+ // on multi-voice setups primary voices are always up
72775
+ if (helper.voice.bar.isMultiVoice) {
72776
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72777
+ }
72778
+ // grace notes are always up
72779
+ if (helper.beats[0].graceType !== GraceType.None) {
72780
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72781
+ }
72782
+ if (helper.beats.length === 1 && helper.beats[0].slashed) {
72783
+ return this._invertBeamDirection(helper, BeamDirection.Down);
72784
+ }
72785
+ // the average line is used for determination
72786
+ // key lowerequal than middle line -> up
72787
+ // key higher than middle line -> down
72788
+ if (helper.highestNoteInHelper && helper.lowestNoteInHelper) {
72789
+ // NOTE: This is the only place where we need the locations before we have positioned the notes
72790
+ // TODO: we should first register all note-heads and calculate the accidentals+steps
72791
+ const highestNotePosition = this._getNoteCenterYBeforeLayouting(helper.highestNoteInHelper);
72792
+ const lowestNotePosition = this._getNoteCenterYBeforeLayouting(helper.lowestNoteInHelper);
72793
+ const avg = (highestNotePosition + lowestNotePosition) / 2;
72794
+ return this._invertBeamDirection(helper, this.middleYPosition < avg ? BeamDirection.Up : BeamDirection.Down);
72795
+ }
72796
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72797
+ }
72798
+ _getNoteCenterYBeforeLayouting(note) {
72799
+ const steps = AccidentalHelper.computeStepsWithoutAccidentals(this.bar, note);
72800
+ return this.getScoreY(steps);
72801
+ }
72802
+ _invertBeamDirection(helper, direction) {
72803
+ if (!helper.invertBeamDirection) {
72804
+ return direction;
72805
+ }
72806
+ switch (direction) {
72807
+ case BeamDirection.Down:
72808
+ return BeamDirection.Up;
72809
+ // case BeamDirection.Up:
72810
+ default:
72811
+ return BeamDirection.Down;
72579
72812
  }
72580
72813
  }
72581
72814
  paintBeamingStem(beat, _cy, x, topY, bottomY, canvas) {
@@ -72618,6 +72851,8 @@ class SlashRestGlyph extends ScoreRestGlyph {
72618
72851
  * @internal
72619
72852
  */
72620
72853
  class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72854
+ _tremoloPicking;
72855
+ _stemLengthExtension = 0;
72621
72856
  noteHeads = null;
72622
72857
  deadSlapped = null;
72623
72858
  restGlyph = null;
@@ -72660,17 +72895,18 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72660
72895
  beatBounds.addNote(noteBounds);
72661
72896
  }
72662
72897
  }
72663
- getLowestNoteY() {
72664
- return this.noteHeads ? this.noteHeads.y : 0;
72898
+ getLowestNoteY(requestedPosition) {
72899
+ return this._internalGetNoteY(requestedPosition);
72665
72900
  }
72666
- getHighestNoteY() {
72667
- return this.noteHeads ? this.noteHeads.y : 0;
72901
+ getHighestNoteY(requestedPosition) {
72902
+ return this._internalGetNoteY(requestedPosition);
72668
72903
  }
72669
72904
  getRestY(requestedPosition) {
72670
72905
  const g = this.restGlyph;
72671
72906
  if (g) {
72672
72907
  switch (requestedPosition) {
72673
72908
  case NoteYPosition.TopWithStem:
72909
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
72674
72910
  case NoteYPosition.Top:
72675
72911
  return g.getBoundingBoxTop();
72676
72912
  case NoteYPosition.Center:
@@ -72678,35 +72914,70 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72678
72914
  case NoteYPosition.StemDown:
72679
72915
  return g.getBoundingBoxTop() + g.height / 2;
72680
72916
  case NoteYPosition.Bottom:
72681
- case NoteYPosition.BottomWithStem:
72682
72917
  return g.getBoundingBoxBottom();
72918
+ case NoteYPosition.BottomWithStem:
72919
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
72683
72920
  }
72684
72921
  }
72685
72922
  return 0;
72686
72923
  }
72687
- getNoteY(note, requestedPosition) {
72924
+ getNoteY(_note, requestedPosition) {
72925
+ return this._internalGetNoteY(requestedPosition);
72926
+ }
72927
+ _internalGetNoteY(requestedPosition) {
72688
72928
  let g = null;
72689
72929
  let symbol = MusicFontSymbol.None;
72930
+ let hasStem = false;
72690
72931
  if (this.noteHeads) {
72691
72932
  g = this.noteHeads;
72692
- symbol = SlashNoteHeadGlyph.getSymbol(note.beat.duration);
72933
+ symbol = SlashNoteHeadGlyph.getSymbol(this.container.beat.duration);
72934
+ hasStem = true;
72693
72935
  }
72694
72936
  else if (this.deadSlapped) {
72695
72937
  g = this.deadSlapped;
72696
72938
  }
72697
72939
  if (g) {
72698
72940
  let pos = this.y + g.y;
72941
+ const sr = this.renderer;
72942
+ const beat = this.container.beat;
72943
+ const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72699
72944
  switch (requestedPosition) {
72700
- case NoteYPosition.Top:
72701
72945
  case NoteYPosition.TopWithStem:
72946
+ if (hasStem) {
72947
+ // stem start
72948
+ pos -=
72949
+ (sr.smuflMetrics.stemUp.has(symbol) ? sr.smuflMetrics.stemUp.get(symbol).bottomY : 0) *
72950
+ scale;
72951
+ // stem size according to duration
72952
+ pos -= sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale;
72953
+ pos -= this._stemLengthExtension;
72954
+ }
72955
+ else {
72956
+ pos -= g.height / 2;
72957
+ }
72958
+ return pos;
72959
+ case NoteYPosition.Top:
72702
72960
  pos -= g.height / 2;
72703
72961
  break;
72704
72962
  case NoteYPosition.Center:
72705
72963
  break;
72706
72964
  case NoteYPosition.Bottom:
72707
- case NoteYPosition.BottomWithStem:
72708
72965
  pos += g.height / 2;
72709
72966
  break;
72967
+ case NoteYPosition.BottomWithStem:
72968
+ if (hasStem) {
72969
+ pos -=
72970
+ (sr.smuflMetrics.stemDown.has(symbol)
72971
+ ? sr.smuflMetrics.stemDown.get(symbol).topY
72972
+ : -sr.smuflMetrics.glyphHeights.get(symbol) / 2) * scale;
72973
+ // stem size according to duration
72974
+ pos += sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale;
72975
+ pos += this._stemLengthExtension;
72976
+ }
72977
+ else {
72978
+ pos += g.height / 2;
72979
+ }
72980
+ return pos;
72710
72981
  case NoteYPosition.StemUp:
72711
72982
  pos -= this.renderer.smuflMetrics.stemUp.has(symbol)
72712
72983
  ? this.renderer.smuflMetrics.stemUp.get(symbol).bottomY
@@ -72735,11 +73006,16 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72735
73006
  }
72736
73007
  else if (!this.container.beat.isEmpty) {
72737
73008
  if (!this.container.beat.isRest) {
72738
- const isGrace = this.container.beat.graceType !== GraceType.None;
72739
- const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat.duration, isGrace, this.container.beat);
73009
+ const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat);
72740
73010
  this.noteHeads = noteHeadGlyph;
72741
73011
  noteHeadGlyph.beat = this.container.beat;
72742
73012
  this.addNormal(noteHeadGlyph);
73013
+ if (this.container.beat.isTremolo) {
73014
+ this._tremoloPicking = new TremoloPickingGlyph(0, 0, this.container.beat.tremoloPicking);
73015
+ this._tremoloPicking.renderer = this.renderer;
73016
+ this._tremoloPicking.doLayout();
73017
+ this._alignTremoloPickingGlyph();
73018
+ }
72743
73019
  }
72744
73020
  else {
72745
73021
  const restGlyph = new SlashRestGlyph(0, glyphY, this.container.beat.duration);
@@ -72774,6 +73050,27 @@ class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72774
73050
  this.stemX = this.onTimeX;
72775
73051
  }
72776
73052
  this.middleX = this.onTimeX;
73053
+ const tremolo = this._tremoloPicking;
73054
+ if (tremolo) {
73055
+ tremolo.x = this.container.beat.duration < Duration.Half ? this.width / 2 : this.stemX;
73056
+ }
73057
+ }
73058
+ _alignTremoloPickingGlyph() {
73059
+ const g = this._tremoloPicking;
73060
+ g.alignTremoloPickingGlyph(BeamDirection.Up, this._internalGetNoteY(NoteYPosition.TopWithStem), this._internalGetNoteY(NoteYPosition.Center), this.container.beat.duration);
73061
+ this._stemLengthExtension = g.stemExtensionHeight;
73062
+ let tremoloX = this.stemX;
73063
+ if (this.container.beat.duration < Duration.Half) {
73064
+ tremoloX = this.width / 2;
73065
+ }
73066
+ g.x = tremoloX;
73067
+ }
73068
+ paint(cx, cy, canvas) {
73069
+ super.paint(cx, cy, canvas);
73070
+ const tremolo = this._tremoloPicking;
73071
+ if (tremolo) {
73072
+ tremolo.paint(cx + this.x, cy + this.y, canvas);
73073
+ }
72777
73074
  }
72778
73075
  }
72779
73076
 
@@ -72808,15 +73105,15 @@ class SlashBeatContainerGlyph extends BeatContainerGlyph {
72808
73105
  const beat = this.beat;
72809
73106
  const isGrace = beat.graceType !== GraceType.None;
72810
73107
  if (sr.hasFlag(beat)) {
72811
- const direction = this.renderer.getBeatDirection(beat);
73108
+ const direction = sr.getBeatDirection(beat);
72812
73109
  const scale = isGrace ? EngravingSettings.GraceScale : 1;
72813
73110
  const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
72814
- const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
73111
+ const flagWidth = sr.smuflMetrics.glyphWidths.get(symbol) * scale;
72815
73112
  this._flagStretch = flagWidth;
72816
73113
  }
72817
73114
  else if (isGrace) {
72818
73115
  // always use flag size as spacing on grace notes
72819
- const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
73116
+ const graceSpacing = sr.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72820
73117
  this._flagStretch = graceSpacing;
72821
73118
  }
72822
73119
  super.doLayout();
@@ -72903,45 +73200,26 @@ class SlashBarRenderer extends LineBarRenderer {
72903
73200
  getNoteLine(_note) {
72904
73201
  return 0;
72905
73202
  }
72906
- getFlagTopY(beat, _direction) {
72907
- let slashY = this.getLineY(0);
72908
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72909
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72910
- slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
72911
- if (!beat.isRest) {
72912
- slashY -= this.smuflMetrics.standardStemLength + scale;
73203
+ getFlagTopY(beat, direction) {
73204
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
73205
+ if (beat.notes.length > 0) {
73206
+ return this.getNoteY(beat.notes[0], position);
72913
73207
  }
72914
- return slashY;
72915
- }
72916
- getFlagBottomY(beat, _direction) {
72917
- let slashY = this.getLineY(0);
72918
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72919
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72920
- slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
72921
- return slashY;
72922
- }
72923
- getBeamDirection(_helper) {
72924
- return BeamDirection.Up;
72925
- }
72926
- getNoteY(note, requestedPosition) {
72927
- let y = super.getNoteY(note, requestedPosition);
72928
- if (Number.isNaN(y)) {
72929
- y = this.getLineY(0);
73208
+ else {
73209
+ return this.getRestY(beat, position);
72930
73210
  }
72931
- return y;
72932
73211
  }
72933
- calculateBeamYWithDirection(h, x, direction) {
72934
- if (h.beats.length === 0) {
72935
- return direction === BeamDirection.Up
72936
- ? this.getFlagTopY(h.beats[0], direction)
72937
- : this.getFlagBottomY(h.beats[0], direction);
73212
+ getFlagBottomY(beat, direction) {
73213
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
73214
+ if (beat.notes.length > 0) {
73215
+ return this.getNoteY(beat.notes[0], position);
73216
+ }
73217
+ else {
73218
+ return this.getRestY(beat, position);
72938
73219
  }
72939
- this.ensureBeamDrawingInfo(h, direction);
72940
- return h.drawingInfos.get(direction).calcY(x);
72941
73220
  }
72942
- getBarLineStart(_beat, _direction) {
72943
- const noteHeadHeight = this.smuflMetrics.glyphHeights.get(MusicFontSymbol.NoteheadSlashWhiteHalf);
72944
- return this.getLineY(0) - noteHeadHeight / 2;
73221
+ getBeamDirection(_helper) {
73222
+ return BeamDirection.Up;
72945
73223
  }
72946
73224
  createLinePreBeatGlyphs() {
72947
73225
  // Key signature
@@ -72985,6 +73263,9 @@ class SlashBarRenderer extends LineBarRenderer {
72985
73263
  }
72986
73264
  this.calculateBeamingOverflows(rendererTop, rendererBottom);
72987
73265
  }
73266
+ shouldPaintBeamingHelper(h) {
73267
+ return super.shouldPaintBeamingHelper(h) && h.voice.index === 0;
73268
+ }
72988
73269
  paintBeamingStem(beat, _cy, x, topY, bottomY, canvas) {
72989
73270
  const _ = ElementStyleHelper.beat(canvas, BeatSubElement.SlashStem, beat);
72990
73271
  try {
@@ -73031,6 +73312,15 @@ class NoteNumberGlyph extends Glyph {
73031
73312
  super(x, y);
73032
73313
  this._note = note;
73033
73314
  }
73315
+ get _padding() {
73316
+ return this.renderer.lineSpacing * 0.25;
73317
+ }
73318
+ getBoundingBoxTop() {
73319
+ return this.y - this.height / 2 - this._padding;
73320
+ }
73321
+ getBoundingBoxBottom() {
73322
+ return this.y + this.height / 2;
73323
+ }
73034
73324
  doLayout() {
73035
73325
  const n = this._note;
73036
73326
  let fret = n.fret - n.beat.voice.bar.staff.transpositionPitch;
@@ -73105,11 +73395,12 @@ class NoteNumberGlyph extends Glyph {
73105
73395
  return;
73106
73396
  }
73107
73397
  const textWidth = this.noteStringWidth + this._trillNoteStringWidth;
73108
- const x = (cx + this.x + (this.width - textWidth) / 2);
73109
- this.paintTrill(x, cy, canvas);
73398
+ const x = cx + this.x + (this.width - textWidth) / 2;
73399
+ const y = cy + this.y;
73400
+ this.paintTrill(x, y, canvas);
73110
73401
  const _ = ElementStyleHelper.note(canvas, NoteSubElement.GuitarTabFretNumber, this._note);
73111
73402
  try {
73112
- canvas.fillText(this._noteString, x, cy + this.y);
73403
+ canvas.fillText(this._noteString, x, y);
73113
73404
  }
73114
73405
  finally {
73115
73406
  _?.[Symbol.dispose]?.();
@@ -73120,7 +73411,7 @@ class NoteNumberGlyph extends Glyph {
73120
73411
  try {
73121
73412
  const prevFont = this.renderer.scoreRenderer.canvas.font;
73122
73413
  this.renderer.scoreRenderer.canvas.font = this.renderer.resources.graceFont;
73123
- canvas.fillText(this._trillNoteString, x + this.noteStringWidth, cy + this.y);
73414
+ canvas.fillText(this._trillNoteString, x + this.noteStringWidth, cy);
73124
73415
  this.renderer.scoreRenderer.canvas.font = prevFont;
73125
73416
  }
73126
73417
  finally {
@@ -73179,11 +73470,11 @@ class TabNoteChordGlyph extends Glyph {
73179
73470
  }
73180
73471
  return 0;
73181
73472
  }
73182
- getLowestNoteY() {
73183
- return this.maxStringNote ? this.getNoteY(this.maxStringNote, NoteYPosition.Center) : 0;
73473
+ getLowestNoteY(requestedPosition) {
73474
+ return this.maxStringNote ? this.getNoteY(this.maxStringNote, requestedPosition) : 0;
73184
73475
  }
73185
- getHighestNoteY() {
73186
- return this.minStringNote ? this.getNoteY(this.minStringNote, NoteYPosition.Center) : 0;
73476
+ getHighestNoteY(requestedPosition) {
73477
+ return this.minStringNote ? this.getNoteY(this.minStringNote, requestedPosition) : 0;
73187
73478
  }
73188
73479
  getNoteY(note, requestedPosition) {
73189
73480
  if (this.notesPerString.has(note.string)) {
@@ -73191,23 +73482,44 @@ class TabNoteChordGlyph extends Glyph {
73191
73482
  let pos = this.y + n.y;
73192
73483
  switch (requestedPosition) {
73193
73484
  case NoteYPosition.Top:
73194
- case NoteYPosition.TopWithStem:
73195
73485
  pos -= n.height / 2;
73196
73486
  break;
73487
+ case NoteYPosition.StemUp:
73488
+ pos = this.y + n.getBoundingBoxTop();
73489
+ break;
73197
73490
  case NoteYPosition.Center:
73198
73491
  break;
73199
73492
  case NoteYPosition.Bottom:
73200
- case NoteYPosition.BottomWithStem:
73201
73493
  pos += n.height / 2;
73202
73494
  break;
73203
- case NoteYPosition.StemUp:
73204
73495
  case NoteYPosition.StemDown:
73496
+ pos = this.y + n.getBoundingBoxBottom();
73497
+ break;
73498
+ case NoteYPosition.TopWithStem:
73499
+ pos = -this.renderer.settings.notation.rhythmHeight;
73500
+ pos -= this.calculateTremoloHeightForStem();
73501
+ break;
73502
+ case NoteYPosition.BottomWithStem:
73503
+ pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight;
73504
+ pos += this.calculateTremoloHeightForStem();
73205
73505
  break;
73206
73506
  }
73207
73507
  return pos;
73208
73508
  }
73209
73509
  return 0;
73210
73510
  }
73511
+ calculateTremoloHeightForStem() {
73512
+ const beat = this.beat;
73513
+ if (!beat.isTremolo) {
73514
+ return 0;
73515
+ }
73516
+ if (beat.duration <= Duration.Quarter) {
73517
+ return 0;
73518
+ }
73519
+ const symbol = TremoloPickingGlyph._getSymbol(beat.tremoloPicking);
73520
+ const smufl = this.renderer.smuflMetrics;
73521
+ return smufl.glyphHeights.has(symbol) ? smufl.glyphHeights.get(symbol) : 0;
73522
+ }
73211
73523
  doLayout() {
73212
73524
  let w = 0;
73213
73525
  if (this.beat.deadSlapped) {
@@ -73333,6 +73645,20 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73333
73645
  return BeatSubElement.GuitarTabEffects;
73334
73646
  }
73335
73647
  getNoteX(note, requestedPosition) {
73648
+ if (this.slash) {
73649
+ let pos = this.slash.x;
73650
+ switch (requestedPosition) {
73651
+ case NoteXPosition.Left:
73652
+ break;
73653
+ case NoteXPosition.Center:
73654
+ pos += this.slash.width / 2;
73655
+ break;
73656
+ case NoteXPosition.Right:
73657
+ pos += this.slash.width;
73658
+ break;
73659
+ }
73660
+ return pos;
73661
+ }
73336
73662
  return this.noteNumbers ? this.noteNumbers.getNoteX(note, requestedPosition) : 0;
73337
73663
  }
73338
73664
  getNoteY(note, requestedPosition) {
@@ -73343,6 +73669,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73343
73669
  if (g) {
73344
73670
  switch (requestedPosition) {
73345
73671
  case NoteYPosition.TopWithStem:
73672
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
73346
73673
  case NoteYPosition.Top:
73347
73674
  return g.getBoundingBoxTop();
73348
73675
  case NoteYPosition.Center:
@@ -73350,17 +73677,18 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73350
73677
  case NoteYPosition.StemDown:
73351
73678
  return g.getBoundingBoxTop() + g.height / 2;
73352
73679
  case NoteYPosition.Bottom:
73680
+ return g.getBoundingBoxTop();
73353
73681
  case NoteYPosition.BottomWithStem:
73354
- return g.getBoundingBoxBottom();
73682
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
73355
73683
  }
73356
73684
  }
73357
73685
  return 0;
73358
73686
  }
73359
- getLowestNoteY() {
73360
- return this.noteNumbers ? this.noteNumbers.getLowestNoteY() : 0;
73687
+ getLowestNoteY(requestedPosition) {
73688
+ return this.noteNumbers ? this.noteNumbers.getLowestNoteY(requestedPosition) : 0;
73361
73689
  }
73362
- getHighestNoteY() {
73363
- return this.noteNumbers ? this.noteNumbers.getHighestNoteY() : 0;
73690
+ getHighestNoteY(requestedPosition) {
73691
+ return this.noteNumbers ? this.noteNumbers.getHighestNoteY(requestedPosition) : 0;
73364
73692
  }
73365
73693
  buildBoundingsLookup(beatBounds, cx, cy) {
73366
73694
  if (this.noteNumbers) {
@@ -73378,7 +73706,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73378
73706
  if (this.container.beat.slashed && !this.container.beat.notes.some(x => x.isTieDestination)) {
73379
73707
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
73380
73708
  const slashY = tabRenderer.getLineY(line);
73381
- const slashNoteHead = new SlashNoteHeadGlyph(0, slashY, this.container.beat.duration, isGrace, this.container.beat);
73709
+ const slashNoteHead = new SlashNoteHeadGlyph(0, slashY, this.container.beat);
73382
73710
  slashNoteHead.noteHeadElement = NoteSubElement.GuitarTabFretNumber;
73383
73711
  slashNoteHead.effectElement = BeatSubElement.GuitarTabEffects;
73384
73712
  this.slash = slashNoteHead;
@@ -73401,8 +73729,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73401
73729
  //
73402
73730
  // Tremolo Picking
73403
73731
  if (this.container.beat.isTremolo && !beatEffects.has('tremolo')) {
73404
- const speed = this.container.beat.tremoloSpeed;
73405
- const glyph = new TremoloPickingGlyph(0, 0, speed);
73732
+ const glyph = new TremoloPickingGlyph(0, 0, this.container.beat.tremoloPicking);
73406
73733
  glyph.offsetY = this.renderer.smuflMetrics.glyphTop.get(glyph.symbol);
73407
73734
  beatEffects.set('tremolo', glyph);
73408
73735
  centeredEffectGlyphs.push(glyph);
@@ -73411,7 +73738,7 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73411
73738
  // Note dots
73412
73739
  //
73413
73740
  if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== TabRhythmMode.Hidden) {
73414
- const y = tabRenderer.getFlagAndBarPos();
73741
+ const y = this.getNoteY(this.container.beat.maxNote, NoteYPosition.BottomWithStem);
73415
73742
  for (let i = 0; i < this.container.beat.dots; i++) {
73416
73743
  this.addEffect(new AugmentationDotGlyph(0, y));
73417
73744
  }
@@ -73473,8 +73800,8 @@ class TabBeatGlyph extends BeatOnNoteGlyphBase {
73473
73800
  noteNumberGlyph.renderer = this.renderer;
73474
73801
  noteNumberGlyph.doLayout();
73475
73802
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
73476
- const topY = noteNumberGlyph.y - noteNumberGlyph.height / 2;
73477
- const bottomY = topY + noteNumberGlyph.height;
73803
+ const topY = noteNumberGlyph.getBoundingBoxTop();
73804
+ const bottomY = noteNumberGlyph.getBoundingBoxBottom();
73478
73805
  this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
73479
73806
  const minString = tr.minString;
73480
73807
  const maxString = tr.maxString;
@@ -74001,30 +74328,6 @@ class TabBarRenderer extends LineBarRenderer {
74001
74328
  }
74002
74329
  }
74003
74330
  }
74004
- adjustSizes() {
74005
- if (this.rhythmMode !== TabRhythmMode.Hidden) {
74006
- let shortestTremolo = Duration.Whole;
74007
- for (const b of this.helpers.beamHelpers) {
74008
- for (const h of b) {
74009
- if (h.tremoloDuration && (!shortestTremolo || shortestTremolo < h.tremoloDuration)) {
74010
- shortestTremolo = h.tremoloDuration;
74011
- }
74012
- }
74013
- }
74014
- switch (shortestTremolo) {
74015
- case Duration.Eighth:
74016
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo1);
74017
- break;
74018
- case Duration.Sixteenth:
74019
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo2);
74020
- break;
74021
- case Duration.ThirtySecond:
74022
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo3);
74023
- break;
74024
- }
74025
- this.registerOverflowBottom(this.settings.notation.rhythmHeight);
74026
- }
74027
- }
74028
74331
  doLayout() {
74029
74332
  const hasStandardNotation = this.bar.staff.showStandardNotation && this.scoreRenderer.layout.profile.has(ScoreBarRenderer.StaffId);
74030
74333
  if (!hasStandardNotation) {
@@ -74105,32 +74408,29 @@ class TabBarRenderer extends LineBarRenderer {
74105
74408
  drawBeamHelperAsFlags(h) {
74106
74409
  return super.drawBeamHelperAsFlags(h) || this.rhythmMode === TabRhythmMode.ShowWithBeams;
74107
74410
  }
74108
- getFlagTopY(beat, _direction) {
74109
- const container = this.getBeatContainer(beat);
74110
- if (!container || !beat.minStringNote || beat.duration === Duration.Half) {
74111
- return this.height - this.settings.notation.rhythmHeight - this.tupletSize;
74411
+ getFlagTopY(beat, direction) {
74412
+ const maxNote = beat.maxStringNote;
74413
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
74414
+ if (maxNote) {
74415
+ return this.getNoteY(maxNote, position);
74416
+ }
74417
+ else {
74418
+ return this.getRestY(beat, position);
74112
74419
  }
74113
- return container.getNoteY(beat.minStringNote, NoteYPosition.Bottom) + this.smuflMetrics.staffLineThickness;
74114
- }
74115
- getFlagBottomY(_beat, _direction) {
74116
- return this.getFlagAndBarPos();
74117
- }
74118
- getFlagStemSize(_duration, _forceMinStem = false) {
74119
- return 0; // fixed size via getFlagBottomY
74120
74420
  }
74121
- getBarLineStart(beat, direction) {
74122
- return this.getFlagTopY(beat, direction);
74421
+ getFlagBottomY(beat, direction) {
74422
+ const maxNote = beat.minStringNote;
74423
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
74424
+ if (maxNote) {
74425
+ return this.getNoteY(maxNote, position);
74426
+ }
74427
+ else {
74428
+ return this.getRestY(beat, position);
74429
+ }
74123
74430
  }
74124
74431
  getBeamDirection(_helper) {
74125
74432
  return BeamDirection.Down;
74126
74433
  }
74127
- getFlagAndBarPos() {
74128
- return this.height + this.settings.notation.rhythmHeight - (this._hasTuplets ? this.tupletSize / 2 : 0);
74129
- }
74130
- calculateBeamYWithDirection(_h, _x, _direction) {
74131
- // currently only used for duplets
74132
- return this.getFlagAndBarPos();
74133
- }
74134
74434
  shouldPaintFlag(beat) {
74135
74435
  if (!super.shouldPaintFlag(beat)) {
74136
74436
  return false;
@@ -74153,19 +74453,20 @@ class TabBarRenderer extends LineBarRenderer {
74153
74453
  holes = this.helpers.collisionHelper.reservedLayoutAreasByDisplayTime.get(beat.displayStart).slots.slice();
74154
74454
  holes.sort((a, b) => a.topY - b.topY);
74155
74455
  }
74156
- let y = bottomY;
74157
- while (y > topY) {
74158
- let lineY = topY;
74159
- // draw until next hole (if hole reaches into line)
74160
- if (holes.length > 0 && holes[holes.length - 1].bottomY > lineY) {
74161
- const bottomHole = holes.pop();
74162
- lineY = cy + bottomHole.bottomY;
74163
- canvas.fillRect(x, lineY, this.smuflMetrics.stemThickness, y - lineY);
74164
- y = cy + bottomHole.topY;
74165
- }
74166
- else {
74167
- canvas.fillRect(x, lineY, this.smuflMetrics.stemThickness, y - lineY);
74168
- break;
74456
+ // fast path -> single note == full line
74457
+ if (holes.length === 1) {
74458
+ canvas.fillRect(x, topY, this.smuflMetrics.stemThickness, bottomY - topY);
74459
+ return;
74460
+ }
74461
+ const bottomYRelative = bottomY - cy;
74462
+ // slow path -> multiple notes == lines between notes
74463
+ const bottomHole = holes[holes.length - 1];
74464
+ canvas.fillRect(x, cy + bottomHole.bottomY, this.smuflMetrics.stemThickness, bottomYRelative - bottomHole.bottomY);
74465
+ for (let i = holes.length - 1; i > 0; i--) {
74466
+ const bottomHoleY = holes[i].topY;
74467
+ const topHoleY = holes[i - 1].bottomY;
74468
+ if (topHoleY < bottomHoleY) {
74469
+ canvas.fillRect(x, cy + topHoleY, this.smuflMetrics.stemThickness, bottomHoleY - topHoleY);
74169
74470
  }
74170
74471
  }
74171
74472
  }
@@ -74173,6 +74474,15 @@ class TabBarRenderer extends LineBarRenderer {
74173
74474
  _?.[Symbol.dispose]?.();
74174
74475
  }
74175
74476
  }
74477
+ calculateOverflows(rendererTop, rendererBottom) {
74478
+ super.calculateOverflows(rendererTop, rendererBottom);
74479
+ if (this.bar.isEmpty) {
74480
+ return;
74481
+ }
74482
+ if (this.rhythmMode !== TabRhythmMode.Hidden) {
74483
+ this.calculateBeamingOverflows(rendererTop, rendererBottom);
74484
+ }
74485
+ }
74176
74486
  }
74177
74487
 
74178
74488
  /**
@@ -76350,16 +76660,17 @@ class GpifWriter {
76350
76660
  beatNode.addElement('Fadding').innerText = FadeType[beat.fade];
76351
76661
  }
76352
76662
  if (beat.isTremolo) {
76353
- switch (beat.tremoloSpeed) {
76354
- case Duration.Eighth:
76663
+ switch (beat.tremoloPicking.marks) {
76664
+ case 1:
76355
76665
  beatNode.addElement('Tremolo').innerText = '1/2';
76356
76666
  break;
76357
- case Duration.Sixteenth:
76667
+ case 2:
76358
76668
  beatNode.addElement('Tremolo').innerText = '1/4';
76359
76669
  break;
76360
- case Duration.ThirtySecond:
76670
+ case 3:
76361
76671
  beatNode.addElement('Tremolo').innerText = '1/8';
76362
76672
  break;
76673
+ // NOTE: guitar pro does not support other tremolos
76363
76674
  }
76364
76675
  }
76365
76676
  if (beat.hasChord) {
@@ -79356,6 +79667,8 @@ const _barrel$2 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty(
79356
79667
  get TrackNamePolicy () { return TrackNamePolicy; },
79357
79668
  TrackStyle,
79358
79669
  get TrackSubElement () { return TrackSubElement; },
79670
+ TremoloPickingEffect,
79671
+ get TremoloPickingStyle () { return TremoloPickingStyle; },
79359
79672
  get TripletFeel () { return TripletFeel; },
79360
79673
  Tuning,
79361
79674
  TupletGroup,