@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.
package/dist/alphaTab.js CHANGED
@@ -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
@@ -209,9 +209,9 @@
209
209
  * @internal
210
210
  */
211
211
  class VersionInfo {
212
- static version = '1.8.0-alpha.1660';
213
- static date = '2025-12-30T02:22:19.477Z';
214
- static commit = 'adca157f6f531fbf0e712c814f0c9c627bf33b74';
212
+ static version = '1.8.0-alpha.1667';
213
+ static date = '2026-01-06T02:24:13.312Z';
214
+ static commit = '52bfad1512fe6b0ca977ecefc2e3b5943729385d';
215
215
  static print(print) {
216
216
  print(`alphaTab ${VersionInfo.version}`);
217
217
  print(`commit: ${VersionInfo.commit}`);
@@ -4646,9 +4646,12 @@
4646
4646
  MusicFontSymbol[MusicFontSymbol["TextTupletBracketStartLongStem"] = 57857] = "TextTupletBracketStartLongStem";
4647
4647
  MusicFontSymbol[MusicFontSymbol["TextTuplet3LongStem"] = 57858] = "TextTuplet3LongStem";
4648
4648
  MusicFontSymbol[MusicFontSymbol["TextTupletBracketEndLongStem"] = 57859] = "TextTupletBracketEndLongStem";
4649
- MusicFontSymbol[MusicFontSymbol["Tremolo3"] = 57890] = "Tremolo3";
4650
- MusicFontSymbol[MusicFontSymbol["Tremolo2"] = 57889] = "Tremolo2";
4651
4649
  MusicFontSymbol[MusicFontSymbol["Tremolo1"] = 57888] = "Tremolo1";
4650
+ MusicFontSymbol[MusicFontSymbol["Tremolo2"] = 57889] = "Tremolo2";
4651
+ MusicFontSymbol[MusicFontSymbol["Tremolo3"] = 57890] = "Tremolo3";
4652
+ MusicFontSymbol[MusicFontSymbol["Tremolo4"] = 57891] = "Tremolo4";
4653
+ MusicFontSymbol[MusicFontSymbol["Tremolo5"] = 57892] = "Tremolo5";
4654
+ MusicFontSymbol[MusicFontSymbol["BuzzRoll"] = 57898] = "BuzzRoll";
4652
4655
  MusicFontSymbol[MusicFontSymbol["Flag8thUp"] = 57920] = "Flag8thUp";
4653
4656
  MusicFontSymbol[MusicFontSymbol["Flag8thDown"] = 57921] = "Flag8thDown";
4654
4657
  MusicFontSymbol[MusicFontSymbol["Flag16thUp"] = 57922] = "Flag16thUp";
@@ -6955,6 +6958,61 @@
6955
6958
  Rasgueado[Rasgueado["Peami"] = 18] = "Peami";
6956
6959
  })(Rasgueado || (Rasgueado = {}));
6957
6960
 
6961
+ /**
6962
+ * The style of tremolo affecting mainly the display of the effect.
6963
+ * @public
6964
+ */
6965
+ var TremoloPickingStyle;
6966
+ (function (TremoloPickingStyle) {
6967
+ /**
6968
+ * A classic tremolo expressed by diagonal bars on the stem.
6969
+ */
6970
+ TremoloPickingStyle[TremoloPickingStyle["Default"] = 0] = "Default";
6971
+ /**
6972
+ * A buzz roll tremolo expressed by a 'z' shaped symbol.
6973
+ */
6974
+ TremoloPickingStyle[TremoloPickingStyle["BuzzRoll"] = 1] = "BuzzRoll";
6975
+ })(TremoloPickingStyle || (TremoloPickingStyle = {}));
6976
+ /**
6977
+ * Describes a tremolo picking effect.
6978
+ * @json
6979
+ * @json_strict
6980
+ * @cloneable
6981
+ * @public
6982
+ */
6983
+ class TremoloPickingEffect {
6984
+ /**
6985
+ * The minimum number of marks for the tremolo picking effect to be valid.
6986
+ */
6987
+ static minMarks = 0;
6988
+ /**
6989
+ * The max number of marks for the tremolo picking effect to be valid.
6990
+ */
6991
+ static maxMarks = 5;
6992
+ /**
6993
+ * The number of marks for the tremolo.
6994
+ * A mark is equal to a single bar shown for a default tremolos.
6995
+ */
6996
+ marks = 0;
6997
+ /**
6998
+ * The style of the tremolo picking.
6999
+ */
7000
+ style = TremoloPickingStyle.Default;
7001
+ /**
7002
+ * The number of marks define the note value of the note repetition.
7003
+ * e.g. a single mark is an 8th note.
7004
+ */
7005
+ get duration() {
7006
+ let marks = this.marks;
7007
+ if (marks < 1) {
7008
+ marks = 1;
7009
+ }
7010
+ const baseDuration = Duration.Eighth;
7011
+ const actualDuration = baseDuration * Math.pow(2, marks);
7012
+ return actualDuration;
7013
+ }
7014
+ }
7015
+
6958
7016
  /**
6959
7017
  * Lists the different modes on how beaming for a beat should be done.
6960
7018
  * @public
@@ -7377,13 +7435,59 @@
7377
7435
  * Gets or sets the pickstroke applied on this beat.
7378
7436
  */
7379
7437
  pickStroke = PickStroke.None;
7438
+ /**
7439
+ * Whether this beat has a tremolo picking effect.
7440
+ */
7380
7441
  get isTremolo() {
7381
- return !!this.tremoloSpeed;
7442
+ return this.tremoloPicking !== undefined;
7443
+ }
7444
+ /**
7445
+ * The tremolo picking effect.
7446
+ */
7447
+ tremoloPicking;
7448
+ /**
7449
+ * The speed of the tremolo.
7450
+ * @deprecated Set {@link tremoloPicking} instead.
7451
+ */
7452
+ get tremoloSpeed() {
7453
+ const tremolo = this.tremoloPicking;
7454
+ if (tremolo) {
7455
+ return tremolo.duration;
7456
+ }
7457
+ return null;
7382
7458
  }
7383
7459
  /**
7384
- * Gets or sets the speed of the tremolo effect.
7460
+ * The speed of the tremolo.
7461
+ * @deprecated Set {@link tremoloPicking} instead.
7385
7462
  */
7386
- tremoloSpeed = null;
7463
+ set tremoloSpeed(value) {
7464
+ if (value === null) {
7465
+ this.tremoloPicking = undefined;
7466
+ return;
7467
+ }
7468
+ let effect = this.tremoloPicking;
7469
+ if (effect === undefined) {
7470
+ effect = new TremoloPickingEffect();
7471
+ this.tremoloPicking = effect;
7472
+ }
7473
+ switch (value) {
7474
+ case Duration.Eighth:
7475
+ effect.marks = 1;
7476
+ break;
7477
+ case Duration.Sixteenth:
7478
+ effect.marks = 2;
7479
+ break;
7480
+ case Duration.ThirtySecond:
7481
+ effect.marks = 3;
7482
+ break;
7483
+ case Duration.SixtyFourth:
7484
+ effect.marks = 4;
7485
+ break;
7486
+ case Duration.OneHundredTwentyEighth:
7487
+ effect.marks = 5;
7488
+ break;
7489
+ }
7490
+ }
7387
7491
  /**
7388
7492
  * Gets or sets whether a crescendo/decrescendo is applied on this beat.
7389
7493
  */
@@ -7678,6 +7782,12 @@
7678
7782
  if (this.brushType === BrushType.None) {
7679
7783
  this.brushDuration = 0;
7680
7784
  }
7785
+ const tremolo = this.tremoloPicking;
7786
+ if (tremolo !== undefined) {
7787
+ if (tremolo.marks < TremoloPickingEffect.minMarks || tremolo.marks > TremoloPickingEffect.maxMarks) {
7788
+ this.tremoloPicking = undefined;
7789
+ }
7790
+ }
7681
7791
  const displayMode = !settings ? exports.NotationMode.GuitarPro : settings.notation.notationMode;
7682
7792
  let isGradual = this.text === 'grad' || this.text === 'grad.';
7683
7793
  if (isGradual && displayMode === exports.NotationMode.SongBook) {
@@ -8086,6 +8196,23 @@
8086
8196
  }
8087
8197
  }
8088
8198
 
8199
+ // <auto-generated>
8200
+ // This code was auto-generated.
8201
+ // Changes to this file may cause incorrect behavior and will be lost if
8202
+ // the code is regenerated.
8203
+ // </auto-generated>
8204
+ /**
8205
+ * @internal
8206
+ */
8207
+ class TremoloPickingEffectCloner {
8208
+ static clone(original) {
8209
+ const clone = new TremoloPickingEffect();
8210
+ clone.marks = original.marks;
8211
+ clone.style = original.style;
8212
+ return clone;
8213
+ }
8214
+ }
8215
+
8089
8216
  // <auto-generated>
8090
8217
  // This code was auto-generated.
8091
8218
  // Changes to this file may cause incorrect behavior and will be lost if
@@ -8138,7 +8265,7 @@
8138
8265
  clone.chordId = original.chordId;
8139
8266
  clone.graceType = original.graceType;
8140
8267
  clone.pickStroke = original.pickStroke;
8141
- clone.tremoloSpeed = original.tremoloSpeed;
8268
+ clone.tremoloPicking = original.tremoloPicking ? TremoloPickingEffectCloner.clone(original.tremoloPicking) : undefined;
8142
8269
  clone.crescendo = original.crescendo;
8143
8270
  clone.displayStart = original.displayStart;
8144
8271
  clone.playbackStart = original.playbackStart;
@@ -8521,6 +8648,11 @@
8521
8648
  ['dadoublecoda', 18]
8522
8649
  ]);
8523
8650
  static directionReversed = AlphaTex1EnumMappings._reverse(AlphaTex1EnumMappings.direction);
8651
+ static tremoloPickingStyle = new Map([
8652
+ ['default', 0],
8653
+ ['buzzroll', 1]
8654
+ ]);
8655
+ static tremoloPickingStyleReversed = AlphaTex1EnumMappings._reverse(AlphaTex1EnumMappings.tremoloPickingStyle);
8524
8656
  static keySignaturesMinorReversed = new Map([
8525
8657
  [-7, 'abminor'],
8526
8658
  [-6, 'ebminor'],
@@ -9281,7 +9413,15 @@
9281
9413
  ],
9282
9414
  ['volume', [[[[16], 0]]]],
9283
9415
  ['balance', [[[[16], 0]]]],
9284
- ['tp', [[[[16], 0, ['8', '16', '32']]]]],
9416
+ [
9417
+ 'tp',
9418
+ [
9419
+ [
9420
+ [[16], 0],
9421
+ [[10, 17], 1, ['default', 'buzzroll']]
9422
+ ]
9423
+ ]
9424
+ ],
9285
9425
  [
9286
9426
  'barre',
9287
9427
  [
@@ -15199,28 +15339,45 @@
15199
15339
  beat.automations.push(balanceAutomation);
15200
15340
  return ApplyNodeResult.Applied;
15201
15341
  case 'tp':
15202
- beat.tremoloSpeed = Duration.Eighth;
15342
+ const tremolo = new TremoloPickingEffect();
15343
+ beat.tremoloPicking = tremolo;
15203
15344
  if (p.arguments && p.arguments.arguments.length > 0) {
15204
- const tremoloSpeedValue = p.arguments.arguments[0].value;
15205
- switch (tremoloSpeedValue) {
15206
- case 8:
15207
- beat.tremoloSpeed = Duration.Eighth;
15208
- break;
15209
- case 16:
15210
- beat.tremoloSpeed = Duration.Sixteenth;
15211
- break;
15212
- case 32:
15213
- beat.tremoloSpeed = Duration.ThirtySecond;
15214
- break;
15215
- default:
15216
- importer.addSemanticDiagnostic({
15217
- code: AlphaTexDiagnosticCode.AT209,
15218
- message: `Unexpected tremolo speed value '${tremoloSpeedValue}, expected: 8, 16 or 32`,
15219
- severity: AlphaTexDiagnosticsSeverity.Error,
15220
- start: p.arguments.arguments[0].start,
15221
- end: p.arguments.arguments[0].end
15222
- });
15345
+ if (p.arguments.arguments.length > 0) {
15346
+ const tremoloMarks = p.arguments.arguments[0].value;
15347
+ if (tremoloMarks >= TremoloPickingEffect.minMarks &&
15348
+ tremoloMarks <= TremoloPickingEffect.maxMarks) {
15349
+ tremolo.marks = tremoloMarks;
15350
+ }
15351
+ else {
15352
+ switch (tremoloMarks) {
15353
+ // backwards compatibility
15354
+ case 8:
15355
+ tremolo.marks = 1;
15356
+ break;
15357
+ case 16:
15358
+ tremolo.marks = 2;
15359
+ break;
15360
+ case 32:
15361
+ tremolo.marks = 3;
15362
+ break;
15363
+ default:
15364
+ importer.addSemanticDiagnostic({
15365
+ code: AlphaTexDiagnosticCode.AT209,
15366
+ message: `Unexpected tremolo marks value '${tremoloMarks}, expected: ${TremoloPickingEffect.minMarks}-${TremoloPickingEffect.maxMarks}, or legacy: 8, 16 or 32`,
15367
+ severity: AlphaTexDiagnosticsSeverity.Error,
15368
+ start: p.arguments.arguments[0].start,
15369
+ end: p.arguments.arguments[0].end
15370
+ });
15371
+ return ApplyNodeResult.NotAppliedSemanticError;
15372
+ }
15373
+ }
15374
+ }
15375
+ if (p.arguments.arguments.length > 1) {
15376
+ const tremoloStyle = AlphaTex1LanguageHandler._parseEnumValue(importer, p.arguments, 'tremolo picking style', AlphaTex1EnumMappings.tremoloPickingStyle, 1);
15377
+ if (tremoloStyle === undefined) {
15223
15378
  return ApplyNodeResult.NotAppliedSemanticError;
15379
+ }
15380
+ tremolo.style = tremoloStyle;
15224
15381
  }
15225
15382
  }
15226
15383
  return ApplyNodeResult.Applied;
@@ -16427,7 +16584,11 @@
16427
16584
  : Atnf.identValue(AlphaTex1EnumMappings.graceTypeReversed.get(beat.graceType)));
16428
16585
  }
16429
16586
  if (beat.isTremolo) {
16430
- Atnf.prop(properties, 'tp', Atnf.numberValue(beat.tremoloSpeed));
16587
+ const values = [Atnf.number(beat.tremoloPicking.marks)];
16588
+ if (beat.tremoloPicking.style !== TremoloPickingStyle.Default) {
16589
+ values.push(Atnf.ident(TremoloPickingStyle[beat.tremoloPicking.style]));
16590
+ }
16591
+ Atnf.prop(properties, 'tp', Atnf.args(values));
16431
16592
  }
16432
16593
  switch (beat.crescendo) {
16433
16594
  case CrescendoType.Crescendo:
@@ -16828,9 +16989,6 @@
16828
16989
  // even start translating when we have parser errors
16829
16990
  // as long we have some nodes, we can already start semantically
16830
16991
  // validating and using them
16831
- if (scoreNode.bars.length === 0) {
16832
- throw new UnsupportedFormatError('No alphaTex data found');
16833
- }
16834
16992
  this._bars(scoreNode);
16835
16993
  if (this.semanticDiagnostics.hasErrors) {
16836
16994
  if (this._state.hasAnyProperData) {
@@ -21451,18 +21609,9 @@
21451
21609
  graceBeat.addNote(graceNote);
21452
21610
  }
21453
21611
  readTremoloPicking(beat) {
21454
- const speed = this.data.readByte();
21455
- switch (speed) {
21456
- case 1:
21457
- beat.tremoloSpeed = Duration.Eighth;
21458
- break;
21459
- case 2:
21460
- beat.tremoloSpeed = Duration.Sixteenth;
21461
- break;
21462
- case 3:
21463
- beat.tremoloSpeed = Duration.ThirtySecond;
21464
- break;
21465
- }
21612
+ const effect = new TremoloPickingEffect();
21613
+ beat.tremoloPicking = effect;
21614
+ effect.marks = this.data.readByte();
21466
21615
  }
21467
21616
  readSlide(note) {
21468
21617
  if (this._versionNumber >= 500) {
@@ -23709,15 +23858,17 @@
23709
23858
  }
23710
23859
  break;
23711
23860
  case 'Tremolo':
23861
+ const tremolo = new TremoloPickingEffect();
23862
+ beat.tremoloPicking = tremolo;
23712
23863
  switch (c.innerText) {
23713
23864
  case '1/2':
23714
- beat.tremoloSpeed = Duration.Eighth;
23865
+ tremolo.marks = 1;
23715
23866
  break;
23716
23867
  case '1/4':
23717
- beat.tremoloSpeed = Duration.Sixteenth;
23868
+ tremolo.marks = 2;
23718
23869
  break;
23719
23870
  case '1/8':
23720
- beat.tremoloSpeed = Duration.ThirtySecond;
23871
+ tremolo.marks = 3;
23721
23872
  break;
23722
23873
  }
23723
23874
  break;
@@ -28785,16 +28936,12 @@
28785
28936
  break;
28786
28937
  // case 'schleifer': Not supported
28787
28938
  case 'tremolo':
28788
- switch (c.innerText) {
28789
- case '1':
28790
- note.beat.tremoloSpeed = Duration.Eighth;
28791
- break;
28792
- case '2':
28793
- note.beat.tremoloSpeed = Duration.Sixteenth;
28794
- break;
28795
- case '3':
28796
- note.beat.tremoloSpeed = Duration.ThirtySecond;
28797
- break;
28939
+ const tremolo = new TremoloPickingEffect();
28940
+ note.beat.tremoloPicking = tremolo;
28941
+ tremolo.marks = Number.parseInt(c.innerText, 10);
28942
+ if ((c.getAttribute('type', '') === 'unmeasured' && tremolo.marks === 0) ||
28943
+ c.getAttribute('smufl', '') === 'buzzRoll') {
28944
+ tremolo.style = TremoloPickingStyle.BuzzRoll;
28798
28945
  }
28799
28946
  break;
28800
28947
  // case 'haydn': Not supported
@@ -37905,6 +38052,38 @@
37905
38052
  }
37906
38053
  }
37907
38054
 
38055
+ /**
38056
+ * @internal
38057
+ */
38058
+ class TremoloPickingEffectSerializer {
38059
+ static fromJson(obj, m) {
38060
+ if (!m) {
38061
+ return;
38062
+ }
38063
+ JsonHelper.forEach(m, (v, k) => TremoloPickingEffectSerializer.setProperty(obj, k, v));
38064
+ }
38065
+ static toJson(obj) {
38066
+ if (!obj) {
38067
+ return null;
38068
+ }
38069
+ const o = new Map();
38070
+ o.set("marks", obj.marks);
38071
+ o.set("style", obj.style);
38072
+ return o;
38073
+ }
38074
+ static setProperty(obj, property, v) {
38075
+ switch (property) {
38076
+ case "marks":
38077
+ obj.marks = v;
38078
+ return true;
38079
+ case "style":
38080
+ obj.style = JsonHelper.parseEnum(v, TremoloPickingStyle);
38081
+ return true;
38082
+ }
38083
+ return false;
38084
+ }
38085
+ }
38086
+
37908
38087
  /**
37909
38088
  * @internal
37910
38089
  */
@@ -37987,7 +38166,9 @@
37987
38166
  o.set("chordid", obj.chordId);
37988
38167
  o.set("gracetype", obj.graceType);
37989
38168
  o.set("pickstroke", obj.pickStroke);
37990
- o.set("tremolospeed", obj.tremoloSpeed);
38169
+ if (obj.tremoloPicking) {
38170
+ o.set("tremolopicking", TremoloPickingEffectSerializer.toJson(obj.tremoloPicking));
38171
+ }
37991
38172
  o.set("crescendo", obj.crescendo);
37992
38173
  o.set("displaystart", obj.displayStart);
37993
38174
  o.set("playbackstart", obj.playbackStart);
@@ -38113,8 +38294,14 @@
38113
38294
  case "pickstroke":
38114
38295
  obj.pickStroke = JsonHelper.parseEnum(v, PickStroke);
38115
38296
  return true;
38116
- case "tremolospeed":
38117
- obj.tremoloSpeed = JsonHelper.parseEnum(v, Duration) ?? null;
38297
+ case "tremolopicking":
38298
+ if (v) {
38299
+ obj.tremoloPicking = new TremoloPickingEffect();
38300
+ TremoloPickingEffectSerializer.fromJson(obj.tremoloPicking, v);
38301
+ }
38302
+ else {
38303
+ obj.tremoloPicking = undefined;
38304
+ }
38118
38305
  return true;
38119
38306
  case "crescendo":
38120
38307
  obj.crescendo = JsonHelper.parseEnum(v, CrescendoType);
@@ -39428,6 +39615,7 @@
39428
39615
  clone.tuningGlyphStringRowPadding = original.tuningGlyphStringRowPadding;
39429
39616
  clone.directionsScale = original.directionsScale;
39430
39617
  clone.multiVoiceDisplacedNoteHeadSpacing = original.multiVoiceDisplacedNoteHeadSpacing;
39618
+ clone.stemFlagHeight = new Map(original.stemFlagHeight);
39431
39619
  return clone;
39432
39620
  }
39433
39621
  }
@@ -39739,6 +39927,20 @@
39739
39927
  this.stemFlagOffsets.set(Duration.SixtyFourth, 0);
39740
39928
  this.stemFlagOffsets.set(Duration.OneHundredTwentyEighth, 0);
39741
39929
  this.stemFlagOffsets.set(Duration.TwoHundredFiftySixth, 0);
39930
+ // Workaround for: https://github.com/w3c/smufl/issues/203
39931
+ // There is no clear anchor for the height of flags on the stem side yet.
39932
+ // These aproximations are tested with bravura
39933
+ this.stemFlagHeight.set(Duration.QuadrupleWhole, 0);
39934
+ this.stemFlagHeight.set(Duration.DoubleWhole, 0);
39935
+ this.stemFlagHeight.set(Duration.Whole, 0);
39936
+ this.stemFlagHeight.set(Duration.Half, 0);
39937
+ this.stemFlagHeight.set(Duration.Quarter, 0);
39938
+ this.stemFlagHeight.set(Duration.Eighth, 1 * this.oneStaffSpace);
39939
+ this.stemFlagHeight.set(Duration.Sixteenth, 1.5 * this.oneStaffSpace);
39940
+ this.stemFlagHeight.set(Duration.ThirtySecond, 2 * this.oneStaffSpace);
39941
+ this.stemFlagHeight.set(Duration.SixtyFourth, 3 * this.oneStaffSpace);
39942
+ this.stemFlagHeight.set(Duration.OneHundredTwentyEighth, 3.5 * this.oneStaffSpace);
39943
+ this.stemFlagHeight.set(Duration.TwoHundredFiftySixth, 4.2 * this.oneStaffSpace);
39742
39944
  for (const [g, v] of Object.entries(smufl.glyphsWithAnchors)) {
39743
39945
  const symbol = EngravingSettings._smuflNameToMusicFontSymbol(g);
39744
39946
  if (symbol) {
@@ -40085,6 +40287,19 @@
40085
40287
  * in case of multi-voice note head overlaps.
40086
40288
  */
40087
40289
  multiVoiceDisplacedNoteHeadSpacing = 0;
40290
+ /**
40291
+ * Calculates the stem height for a note of the given duration.
40292
+ * @param duration The duration to calculate the height respecting flag sizes.
40293
+ * @param hasFlag True if we need to respect flags, false if we have beams.
40294
+ * @returns The total stem height
40295
+ */
40296
+ getStemLength(duration, hasFlag) {
40297
+ return this.standardStemLength + (hasFlag ? this.stemFlagOffsets.get(duration) : 0);
40298
+ }
40299
+ /**
40300
+ * The space needed by flags on the stem-side from top to bottom to place.
40301
+ */
40302
+ stemFlagHeight = new Map();
40088
40303
  // Idea: maybe we can encode and pack this large metadata into a more compact format (e.g. BSON or a custom binary blob?)
40089
40304
  // This metadata below is updated automatically from the bravura_metadata.json via npm script
40090
40305
  static bravuraMetadata =
@@ -40217,6 +40432,10 @@
40217
40432
  bBoxNE: [1.876, 1.18],
40218
40433
  bBoxSW: [0, 0]
40219
40434
  },
40435
+ buzzRoll: {
40436
+ bBoxNE: [0.624, 0.464],
40437
+ bBoxSW: [-0.62, -0.464]
40438
+ },
40220
40439
  cClef: {
40221
40440
  bBoxNE: [2.796, 2.024],
40222
40441
  bBoxSW: [0, -2.024]
@@ -41173,6 +41392,14 @@
41173
41392
  bBoxNE: [0.6, 1.112],
41174
41393
  bBoxSW: [-0.6, -1.12]
41175
41394
  },
41395
+ tremolo4: {
41396
+ bBoxNE: [0.6, 1.496],
41397
+ bBoxSW: [-0.6, -1.48]
41398
+ },
41399
+ tremolo5: {
41400
+ bBoxNE: [0.6, 1.88],
41401
+ bBoxSW: [-0.604, -1.84]
41402
+ },
41176
41403
  tuplet0: {
41177
41404
  bBoxNE: [1.2731041262817027, 1.5],
41178
41405
  bBoxSW: [-0.001204330173715796, -0.032]
@@ -41820,6 +42047,13 @@
41820
42047
  o.set("tuningglyphstringrowpadding", obj.tuningGlyphStringRowPadding);
41821
42048
  o.set("directionsscale", obj.directionsScale);
41822
42049
  o.set("multivoicedisplacednoteheadspacing", obj.multiVoiceDisplacedNoteHeadSpacing);
42050
+ {
42051
+ const m = new Map();
42052
+ o.set("stemflagheight", m);
42053
+ for (const [k, v] of obj.stemFlagHeight) {
42054
+ m.set(k.toString(), v);
42055
+ }
42056
+ }
41823
42057
  return o;
41824
42058
  }
41825
42059
  static setProperty(obj, property, v) {
@@ -42101,6 +42335,12 @@
42101
42335
  case "multivoicedisplacednoteheadspacing":
42102
42336
  obj.multiVoiceDisplacedNoteHeadSpacing = v;
42103
42337
  return true;
42338
+ case "stemflagheight":
42339
+ obj.stemFlagHeight = new Map();
42340
+ JsonHelper.forEach(v, (v, k) => {
42341
+ obj.stemFlagHeight.set(JsonHelper.parseEnum(k, Duration), v);
42342
+ });
42343
+ return true;
42104
42344
  }
42105
42345
  return false;
42106
42346
  }
@@ -48322,7 +48562,12 @@
48322
48562
  }
48323
48563
  _generateTremoloPicking(note, noteStart, noteDuration, noteKey, dynamicValue, channel) {
48324
48564
  const track = note.beat.voice.bar.staff.track;
48325
- let tpLength = MidiUtils.toTicks(note.beat.tremoloSpeed);
48565
+ const marks = note.beat.tremoloPicking.marks;
48566
+ if (marks === 0) {
48567
+ return;
48568
+ }
48569
+ // the marks represent the duration
48570
+ let tpLength = MidiUtils.toTicks(note.beat.tremoloPicking.duration);
48326
48571
  let tick = noteStart;
48327
48572
  const end = noteStart + noteDuration.untilTieOrSlideEnd;
48328
48573
  while (tick + 10 < end) {
@@ -48748,6 +48993,12 @@
48748
48993
  beat;
48749
48994
  preNotes;
48750
48995
  onNotes;
48996
+ getLowestNoteY(requestedPosition) {
48997
+ return this.onNotes.getLowestNoteY(requestedPosition);
48998
+ }
48999
+ getHighestNoteY(requestedPosition) {
49000
+ return this.onNotes.getHighestNoteY(requestedPosition);
49001
+ }
48751
49002
  get beatId() {
48752
49003
  return this.beat.id;
48753
49004
  }
@@ -48811,7 +49062,7 @@
48811
49062
  return helper.hasFlag(false, undefined);
48812
49063
  }
48813
49064
  get postBeatStretch() {
48814
- return (this.onNotes.computedWidth + this._tieWidth) - this.onNotes.onTimeX;
49065
+ return this.onNotes.computedWidth + this._tieWidth - this.onNotes.onTimeX;
48815
49066
  }
48816
49067
  registerLayoutingInfo(layoutings) {
48817
49068
  const preBeatStretch = this.preNotes.computedWidth + this.onNotes.onTimeX;
@@ -60021,16 +60272,22 @@
60021
60272
  return false;
60022
60273
  }
60023
60274
  let y = 0;
60275
+ // TODO. activate padding
60276
+ // const paddingTop = this._isTopContainer ? 0 : this._renderer.settings.display.effectBandPaddingBottom;
60277
+ // const paddingBottom = this._isTopContainer ? this._renderer.settings.display.effectBandPaddingBottom : 0;
60278
+ const paddingTop = 0;
60279
+ const paddingBottom = this._renderer.settings.display.effectBandPaddingBottom;
60024
60280
  for (const slot of this._effectBandSizingInfo.slots) {
60025
60281
  slot.shared.y = y;
60026
60282
  for (const band of slot.bands) {
60283
+ y += paddingTop;
60027
60284
  band.y = y;
60028
60285
  if (finalize) {
60029
60286
  band.finalizeBand();
60030
60287
  }
60031
60288
  band.height = slot.shared.height;
60032
60289
  }
60033
- y += slot.shared.height + this._renderer.settings.display.effectBandPaddingBottom;
60290
+ y += slot.shared.height + paddingBottom;
60034
60291
  }
60035
60292
  y = Math.ceil(y);
60036
60293
  if (y !== this.height) {
@@ -60260,6 +60517,20 @@
60260
60517
  }
60261
60518
  return 0;
60262
60519
  }
60520
+ getLowestNoteY(beat, position) {
60521
+ const container = this.getBeatContainer(beat);
60522
+ if (container) {
60523
+ return container.y + container.getLowestNoteY(position);
60524
+ }
60525
+ return 0;
60526
+ }
60527
+ getHighestNoteY(beat, position) {
60528
+ const container = this.getBeatContainer(beat);
60529
+ if (container) {
60530
+ return container.y + container.getHighestNoteY(position);
60531
+ }
60532
+ return 0;
60533
+ }
60263
60534
  getNoteX(note, requestedPosition) {
60264
60535
  const container = this.getBeatContainer(note.beat);
60265
60536
  if (container) {
@@ -60272,14 +60543,14 @@
60272
60543
  if (beat) {
60273
60544
  return beat.y + beat.getNoteY(note, requestedPosition);
60274
60545
  }
60275
- return Number.NaN;
60546
+ return 0;
60276
60547
  }
60277
60548
  getRestY(beat, requestedPosition) {
60278
60549
  const container = this.getBeatContainer(beat);
60279
60550
  if (container) {
60280
60551
  return container.y + container.getRestY(requestedPosition);
60281
60552
  }
60282
- return Number.NaN;
60553
+ return 0;
60283
60554
  }
60284
60555
  getBeatContainer(beat) {
60285
60556
  if (!this._beatGlyphLookup.has(beat.id)) {
@@ -60949,16 +61220,18 @@
60949
61220
  const g = this._glyph;
60950
61221
  if (g) {
60951
61222
  switch (requestedPosition) {
60952
- case NoteYPosition.TopWithStem:
60953
61223
  case NoteYPosition.Top:
60954
61224
  return g.y;
61225
+ case NoteYPosition.TopWithStem:
61226
+ return g.y - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
60955
61227
  case NoteYPosition.Center:
60956
61228
  case NoteYPosition.StemUp:
60957
61229
  case NoteYPosition.StemDown:
60958
61230
  return g.y + g.height / 2;
60959
61231
  case NoteYPosition.Bottom:
60960
- case NoteYPosition.BottomWithStem:
60961
61232
  return g.y + g.height;
61233
+ case NoteYPosition.BottomWithStem:
61234
+ return g.y + g.height + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
60962
61235
  }
60963
61236
  }
60964
61237
  return 0;
@@ -60966,6 +61239,12 @@
60966
61239
  getNoteY(_note, requestedPosition) {
60967
61240
  return this.getRestY(requestedPosition);
60968
61241
  }
61242
+ getHighestNoteY(position) {
61243
+ return this.getRestY(position);
61244
+ }
61245
+ getLowestNoteY(position) {
61246
+ return this.getRestY(position);
61247
+ }
60969
61248
  getNoteX(_note, requestedPosition) {
60970
61249
  const g = this._glyph;
60971
61250
  if (g) {
@@ -61060,7 +61339,6 @@
61060
61339
  voice = null;
61061
61340
  beats = [];
61062
61341
  shortestDuration = Duration.QuadrupleWhole;
61063
- tremoloDuration;
61064
61342
  /**
61065
61343
  * an indicator whether any beat has a tuplet on it.
61066
61344
  */
@@ -61103,43 +61381,9 @@
61103
61381
  this.drawingInfos.clear();
61104
61382
  }
61105
61383
  }
61106
- direction = BeamDirection.Up;
61107
61384
  finish() {
61108
- this.direction = this._calculateDirection();
61109
61385
  this._renderer.completeBeamingHelper(this);
61110
61386
  }
61111
- _calculateDirection() {
61112
- // no proper voice (should not happen usually)
61113
- if (!this.voice) {
61114
- return BeamDirection.Up;
61115
- }
61116
- // we have a preferred direction
61117
- if (this.preferredBeamDirection !== null) {
61118
- return this.preferredBeamDirection;
61119
- }
61120
- // on multi-voice setups secondary voices are always down
61121
- if (this.voice.index > 0) {
61122
- return this._invert(BeamDirection.Down);
61123
- }
61124
- // on multi-voice setups primary voices are always up
61125
- if (this.voice.bar.isMultiVoice) {
61126
- return this._invert(BeamDirection.Up);
61127
- }
61128
- // grace notes are always up
61129
- if (this.beats[0].graceType !== GraceType.None) {
61130
- return this._invert(BeamDirection.Up);
61131
- }
61132
- // the average line is used for determination
61133
- // key lowerequal than middle line -> up
61134
- // key higher than middle line -> down
61135
- if (this.highestNoteInHelper && this.lowestNoteInHelper) {
61136
- const highestNotePosition = this._renderer.getNoteY(this.highestNoteInHelper, NoteYPosition.Center);
61137
- const lowestNotePosition = this._renderer.getNoteY(this.lowestNoteInHelper, NoteYPosition.Center);
61138
- const avg = (highestNotePosition + lowestNotePosition) / 2;
61139
- return this._invert(this._renderer.middleYPosition < avg ? BeamDirection.Up : BeamDirection.Down);
61140
- }
61141
- return this._invert(BeamDirection.Up);
61142
- }
61143
61387
  static computeLineHeightsForRest(duration) {
61144
61388
  switch (duration) {
61145
61389
  case Duration.QuadrupleWhole:
@@ -61167,18 +61411,6 @@
61167
61411
  }
61168
61412
  return [0, 0];
61169
61413
  }
61170
- _invert(direction) {
61171
- if (!this.invertBeamDirection) {
61172
- return direction;
61173
- }
61174
- switch (direction) {
61175
- case BeamDirection.Down:
61176
- return BeamDirection.Up;
61177
- // case BeamDirection.Up:
61178
- default:
61179
- return BeamDirection.Down;
61180
- }
61181
- }
61182
61414
  checkBeat(beat) {
61183
61415
  if (beat.invertBeamDirection) {
61184
61416
  this.invertBeamDirection = true;
@@ -61212,11 +61444,6 @@
61212
61444
  if (beat.hasTuplet) {
61213
61445
  this.hasTuplet = true;
61214
61446
  }
61215
- if (beat.isTremolo) {
61216
- if (!this.tremoloDuration || this.tremoloDuration < beat.tremoloSpeed) {
61217
- this.tremoloDuration = beat.tremoloSpeed;
61218
- }
61219
- }
61220
61447
  if (beat.graceType !== GraceType.None) {
61221
61448
  this.graceType = beat.graceType;
61222
61449
  }
@@ -62115,9 +62342,6 @@
62115
62342
  }
62116
62343
  completeBeamingHelper(_helper) {
62117
62344
  }
62118
- getBeatDirection(beat) {
62119
- return this.helpers.getBeamingHelperForBeat(beat)?.direction ?? BeamDirection.Up;
62120
- }
62121
62345
  }
62122
62346
 
62123
62347
  /**
@@ -67200,11 +67424,11 @@
67200
67424
  beatBounds.addNote(noteBounds);
67201
67425
  }
67202
67426
  }
67203
- getLowestNoteY() {
67204
- return this._internalGetNoteY(NoteYPosition.Center);
67427
+ getLowestNoteY(requestedPosition) {
67428
+ return this._internalGetNoteY(requestedPosition);
67205
67429
  }
67206
- getHighestNoteY() {
67207
- return this._internalGetNoteY(NoteYPosition.Center);
67430
+ getHighestNoteY(requestedPosition) {
67431
+ return this._internalGetNoteY(requestedPosition);
67208
67432
  }
67209
67433
  getNoteY(_note, requestedPosition) {
67210
67434
  return this._internalGetNoteY(requestedPosition);
@@ -67653,6 +67877,12 @@
67653
67877
  get isLastOfVoice() {
67654
67878
  return false;
67655
67879
  }
67880
+ getLowestNoteY(_requestedPosition) {
67881
+ return 0;
67882
+ }
67883
+ getHighestNoteY(_requestedPosition) {
67884
+ return 0;
67885
+ }
67656
67886
  getNoteY(_note, _requestedPosition) {
67657
67887
  return 0;
67658
67888
  }
@@ -67804,6 +68034,11 @@
67804
68034
  constructor(x, y, duration, direction, isGrace) {
67805
68035
  super(x, y, isGrace ? EngravingSettings.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
67806
68036
  }
68037
+ paint(cx, cy, canvas) {
68038
+ const c = canvas.color;
68039
+ super.paint(cx, cy, canvas);
68040
+ canvas.color = c;
68041
+ }
67807
68042
  static getSymbol(duration, direction, isGrace) {
67808
68043
  if (isGrace) {
67809
68044
  duration = Duration.Eighth;
@@ -68008,9 +68243,17 @@
68008
68243
  }
68009
68244
  }
68010
68245
  }
68246
+ getBeatDirection(beat) {
68247
+ const helper = this.helpers.getBeamingHelperForBeat(beat);
68248
+ return helper ? this.getBeamDirection(helper) : BeamDirection.Up;
68249
+ }
68011
68250
  getTupletBeamDirection(helper) {
68012
68251
  return this.getBeamDirection(helper);
68013
68252
  }
68253
+ calculateBeamYWithDirection(h, x, direction) {
68254
+ this.ensureBeamDrawingInfo(h, direction);
68255
+ return h.drawingInfos.get(direction).calcY(x);
68256
+ }
68014
68257
  _paintTupletHelper(cx, cy, canvas, h, beatElement, bracketsAsArcs) {
68015
68258
  const res = this.resources;
68016
68259
  const oldAlign = canvas.textAlign;
@@ -68319,26 +68562,6 @@
68319
68562
  }
68320
68563
  }
68321
68564
  }
68322
- getFlagStemSize(duration, forceMinStem = false) {
68323
- let size = 0;
68324
- switch (duration) {
68325
- case Duration.QuadrupleWhole:
68326
- case Duration.Half:
68327
- case Duration.Quarter:
68328
- case Duration.Eighth:
68329
- case Duration.Sixteenth:
68330
- case Duration.ThirtySecond:
68331
- case Duration.SixtyFourth:
68332
- case Duration.OneHundredTwentyEighth:
68333
- case Duration.TwoHundredFiftySixth:
68334
- size = this.smuflMetrics.standardStemLength + this.smuflMetrics.stemFlagOffsets.get(duration);
68335
- break;
68336
- default:
68337
- size = forceMinStem ? this.smuflMetrics.standardStemLength : 0;
68338
- break;
68339
- }
68340
- return size;
68341
- }
68342
68565
  recreatePreBeatGlyphs() {
68343
68566
  this._startSpacing = false;
68344
68567
  super.recreatePreBeatGlyphs();
@@ -68350,6 +68573,9 @@
68350
68573
  super.createPreBeatGlyphs();
68351
68574
  this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
68352
68575
  this.createLinePreBeatGlyphs();
68576
+ if (this.index === 0) {
68577
+ this.createStartSpacing();
68578
+ }
68353
68579
  this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
68354
68580
  }
68355
68581
  createPostBeatGlyphs() {
@@ -68376,7 +68602,13 @@
68376
68602
  continue;
68377
68603
  }
68378
68604
  const beatLineX = this.getBeatX(beat, BeatXPosition.Stem);
68379
- const y1 = cy + this.y + this.getBarLineStart(beat, direction);
68605
+ let y1 = cy + this.y;
68606
+ if (direction === BeamDirection.Up) {
68607
+ y1 += this.getFlagBottomY(beat, direction);
68608
+ }
68609
+ else {
68610
+ y1 += this.getFlagTopY(beat, direction);
68611
+ }
68380
68612
  // ensure we are pixel aligned on the end of the stem to avoid anti-aliasing artifacts
68381
68613
  // when combining stems and beams on sub-pixel level
68382
68614
  const y2 = (cy + this.y + this.calculateBeamY(h, beatLineX)) | 0;
@@ -68482,21 +68714,22 @@
68482
68714
  calculateBeamingOverflows(rendererTop, rendererBottom) {
68483
68715
  let maxNoteY = 0;
68484
68716
  let minNoteY = 0;
68485
- const noteOverflowPadding = this.getLineHeight(0.5);
68486
68717
  for (const v of this.helpers.beamHelpers) {
68487
68718
  for (const h of v) {
68488
68719
  if (!this.shouldPaintBeamingHelper(h)) ;
68489
68720
  else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
68490
68721
  const tupletDirection = this.getTupletBeamDirection(h);
68491
- if (h.direction === BeamDirection.Up) {
68492
- let topY = this.getFlagTopY(h.beats[0], h.direction);
68493
- if (h.hasTuplet && tupletDirection === h.direction) {
68722
+ const direction = this.getBeamDirection(h);
68723
+ const flagOverflow = this.smuflMetrics.stemFlagOffsets.get(h.beats[0].duration);
68724
+ if (direction === BeamDirection.Up) {
68725
+ let topY = this.getFlagTopY(h.beats[0], direction) - flagOverflow;
68726
+ if (h.hasTuplet && tupletDirection === direction) {
68494
68727
  topY -= this.tupletSize + this.tupletOffset;
68495
68728
  }
68496
68729
  if (topY < maxNoteY) {
68497
68730
  maxNoteY = topY;
68498
68731
  }
68499
- if (h.hasTuplet && tupletDirection !== h.direction) {
68732
+ if (h.hasTuplet && tupletDirection !== direction) {
68500
68733
  let bottomY = this.getFlagBottomY(h.beats[0], tupletDirection);
68501
68734
  bottomY += this.tupletSize + this.tupletOffset;
68502
68735
  if (bottomY > minNoteY) {
@@ -68505,14 +68738,14 @@
68505
68738
  }
68506
68739
  }
68507
68740
  else {
68508
- let bottomY = this.getFlagBottomY(h.beats[0], h.direction);
68509
- if (h.hasTuplet && tupletDirection === h.direction) {
68741
+ let bottomY = this.getFlagBottomY(h.beats[0], direction) + flagOverflow;
68742
+ if (h.hasTuplet && tupletDirection === direction) {
68510
68743
  bottomY += this.tupletSize + this.tupletOffset;
68511
68744
  }
68512
68745
  if (bottomY > minNoteY) {
68513
68746
  minNoteY = bottomY;
68514
68747
  }
68515
- if (h.hasTuplet && tupletDirection !== h.direction) {
68748
+ if (h.hasTuplet && tupletDirection !== direction) {
68516
68749
  let topY = this.getFlagTopY(h.beats[0], tupletDirection);
68517
68750
  topY -= this.tupletSize + this.tupletOffset;
68518
68751
  if (topY < maxNoteY) {
@@ -68522,19 +68755,20 @@
68522
68755
  }
68523
68756
  }
68524
68757
  else {
68525
- this.ensureBeamDrawingInfo(h, h.direction);
68526
- const drawingInfo = h.drawingInfos.get(h.direction);
68758
+ const direction = this.getBeamDirection(h);
68759
+ this.ensureBeamDrawingInfo(h, direction);
68760
+ const drawingInfo = h.drawingInfos.get(direction);
68527
68761
  const tupletDirection = this.getTupletBeamDirection(h);
68528
- if (h.direction === BeamDirection.Up) {
68762
+ if (direction === BeamDirection.Up) {
68529
68763
  let topY = Math.min(drawingInfo.startY, drawingInfo.endY);
68530
- if (h.hasTuplet && tupletDirection === h.direction) {
68764
+ if (h.hasTuplet && tupletDirection === direction) {
68531
68765
  topY -= this.tupletSize + this.tupletOffset;
68532
68766
  }
68533
68767
  if (topY < maxNoteY) {
68534
68768
  maxNoteY = topY;
68535
68769
  }
68536
- let bottomY = this.getBarLineStart(h.beatOfLowestNote, h.direction) + noteOverflowPadding;
68537
- if (h.hasTuplet && tupletDirection !== h.direction) {
68770
+ let bottomY = this.voiceContainer.getLowestNoteY(h.beatOfLowestNote, NoteYPosition.Bottom);
68771
+ if (h.hasTuplet && tupletDirection !== direction) {
68538
68772
  bottomY += this.tupletSize + this.tupletOffset;
68539
68773
  }
68540
68774
  if (bottomY > minNoteY) {
@@ -68543,14 +68777,14 @@
68543
68777
  }
68544
68778
  else {
68545
68779
  let bottomY = Math.max(drawingInfo.startY, drawingInfo.endY);
68546
- if (h.hasTuplet && tupletDirection === h.direction) {
68780
+ if (h.hasTuplet && tupletDirection === direction) {
68547
68781
  bottomY += this.tupletSize + this.tupletOffset;
68548
68782
  }
68549
68783
  if (bottomY > minNoteY) {
68550
68784
  minNoteY = bottomY;
68551
68785
  }
68552
- let topY = this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
68553
- if (h.hasTuplet && tupletDirection !== h.direction) {
68786
+ let topY = this.voiceContainer.getHighestNoteY(h.beatOfHighestNote, NoteYPosition.Top);
68787
+ if (h.hasTuplet && tupletDirection !== direction) {
68554
68788
  topY -= this.tupletSize + this.tupletOffset;
68555
68789
  }
68556
68790
  if (topY < maxNoteY) {
@@ -68627,38 +68861,11 @@
68627
68861
  // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
68628
68862
  const drawingInfo = this.initializeBeamDrawingInfo(h, direction);
68629
68863
  h.drawingInfos.set(direction, drawingInfo);
68630
- const isRest = h.isRestBeamHelper;
68631
- const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
68632
68864
  const barCount = ModelUtils.getIndex(h.shortestDuration) - 2;
68633
68865
  // 3. adjust beam drawing order
68634
68866
  // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
68635
68867
  // here we shift accordingly
68636
- let barDrawingShift = 0;
68637
- if (barCount > 2 && !isRest) {
68638
- const beamSpacing = this.beamSpacing * scale;
68639
- const beamThickness = this.beamThickness * scale;
68640
- const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
68641
- if (direction === BeamDirection.Up) {
68642
- const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
68643
- const barTopY = bottomBarY - totalBarsHeight;
68644
- const diff = drawingInfo.startY - barTopY;
68645
- if (diff > 0) {
68646
- barDrawingShift = diff * -1;
68647
- drawingInfo.startY -= diff;
68648
- drawingInfo.endY -= diff;
68649
- }
68650
- }
68651
- else {
68652
- const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
68653
- const barBottomY = topBarY + totalBarsHeight;
68654
- const diff = barBottomY - drawingInfo.startY;
68655
- if (diff > 0) {
68656
- barDrawingShift = diff;
68657
- drawingInfo.startY += diff;
68658
- drawingInfo.endY += diff;
68659
- }
68660
- }
68661
- }
68868
+ const barDrawingShift = this.applyBarShift(h, direction, drawingInfo, barCount);
68662
68869
  // 4. let middle elements shift up/down
68663
68870
  if (h.beats.length > 1) {
68664
68871
  // check if highest note shifts bar up or down
@@ -68714,9 +68921,9 @@
68714
68921
  if (h.slashBeats.length > 0) {
68715
68922
  for (const b of h.slashBeats) {
68716
68923
  const yGivenByCurrentValues = drawingInfo.calcY(this.getBeatX(b, BeatXPosition.Stem));
68717
- const yNeededForSlash = h.direction === BeamDirection.Up
68718
- ? this.getFlagTopY(b, h.direction)
68719
- : this.getFlagBottomY(b, h.direction);
68924
+ const yNeededForSlash = direction === BeamDirection.Up
68925
+ ? this.getFlagTopY(b, direction)
68926
+ : this.getFlagBottomY(b, direction);
68720
68927
  const diff = yNeededForSlash - yGivenByCurrentValues;
68721
68928
  if (diff > 0) {
68722
68929
  drawingInfo.startY += diff;
@@ -68726,6 +68933,37 @@
68726
68933
  }
68727
68934
  }
68728
68935
  }
68936
+ applyBarShift(h, direction, drawingInfo, barCount) {
68937
+ let barDrawingShift = 0;
68938
+ const isRest = h.isRestBeamHelper;
68939
+ const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
68940
+ if (barCount > 2 && !isRest) {
68941
+ const beamSpacing = this.beamSpacing * scale;
68942
+ const beamThickness = this.beamThickness * scale;
68943
+ const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;
68944
+ if (direction === BeamDirection.Up) {
68945
+ const bottomBarY = drawingInfo.startY + 2 * beamThickness + beamSpacing;
68946
+ const barTopY = bottomBarY - totalBarsHeight;
68947
+ const diff = drawingInfo.startY - barTopY;
68948
+ if (diff > 0) {
68949
+ barDrawingShift = diff * -1;
68950
+ drawingInfo.startY -= diff;
68951
+ drawingInfo.endY -= diff;
68952
+ }
68953
+ }
68954
+ else {
68955
+ const topBarY = drawingInfo.startY - 2 * beamThickness + beamSpacing;
68956
+ const barBottomY = topBarY + totalBarsHeight;
68957
+ const diff = barBottomY - drawingInfo.startY;
68958
+ if (diff > 0) {
68959
+ barDrawingShift = diff;
68960
+ drawingInfo.startY += diff;
68961
+ drawingInfo.endY += diff;
68962
+ }
68963
+ }
68964
+ }
68965
+ return barDrawingShift;
68966
+ }
68729
68967
  getMinLineOfBeat(_beat) {
68730
68968
  return 0;
68731
68969
  }
@@ -68829,7 +69067,9 @@
68829
69067
  for (const additionalNumber of container.iterateAdditionalNumbers()) {
68830
69068
  barCount = additionalNumber.barCount;
68831
69069
  beatLineX =
68832
- this.beatGlyphsStart + additionalNumber.x + additionalNumber.getBeatX(BeatXPosition.PreNotes, false);
69070
+ this.beatGlyphsStart +
69071
+ additionalNumber.x +
69072
+ additionalNumber.getBeatX(BeatXPosition.PreNotes, false);
68833
69073
  for (let barIndex = 0; barIndex < barCount; barIndex++) {
68834
69074
  const barY = barStart + barIndex * barSpacing;
68835
69075
  const additionalBarEndX = this.beatGlyphsStart +
@@ -68894,44 +69134,12 @@
68894
69134
  }
68895
69135
  return this.getLineY(0);
68896
69136
  }
68897
- completeBeamingHelper(helper) {
68898
- super.completeBeamingHelper(helper);
68899
- helper.direction = BeamDirection.Down;
68900
- }
68901
69137
  getBeamDirection(_helper) {
68902
69138
  return BeamDirection.Down;
68903
69139
  }
68904
69140
  getTupletBeamDirection(_helper) {
68905
69141
  return BeamDirection.Up;
68906
69142
  }
68907
- getNoteY(note, requestedPosition) {
68908
- let y = super.getNoteY(note, requestedPosition);
68909
- if (Number.isNaN(y)) {
68910
- y = this.getLineY(0);
68911
- }
68912
- return y;
68913
- }
68914
- calculateBeamYWithDirection(h, _x, direction) {
68915
- if (h.beats.length === 0) {
68916
- return this.getLineY(0);
68917
- }
68918
- this.ensureBeamDrawingInfo(h, direction);
68919
- const info = h.drawingInfos.get(direction);
68920
- if (direction === BeamDirection.Up) {
68921
- return Math.min(info.startY, info.endY);
68922
- }
68923
- else {
68924
- return Math.max(info.startY, info.endY);
68925
- }
68926
- }
68927
- getBarLineStart(beat, _direction) {
68928
- // NOTE: this is only for the overflow calculation, this renderer has a custom bar drawing logic
68929
- const container = this.voiceContainer.getBeatContainer(beat);
68930
- if (!container) {
68931
- return this.voiceContainer.getBoundingBoxTop();
68932
- }
68933
- return container.getBoundingBoxTop();
68934
- }
68935
69143
  createPreBeatGlyphs() {
68936
69144
  this.wasFirstOfStaff = this.isFirstOfStaff;
68937
69145
  if (this.index === 0 || (this.bar.masterBar.isRepeatStart && this._isOnlyNumbered)) {
@@ -69014,6 +69222,19 @@
69014
69222
  super.paintBeamHelper(cx, cy, canvas, h, flagsElement, beamsElement);
69015
69223
  }
69016
69224
  }
69225
+ applyBarShift(_h, _direction, _drawingInfo, _barCount) {
69226
+ return 0;
69227
+ }
69228
+ calculateBeamYWithDirection(h, _x, direction) {
69229
+ this.ensureBeamDrawingInfo(h, direction);
69230
+ const info = h.drawingInfos.get(direction);
69231
+ if (direction === BeamDirection.Up) {
69232
+ return Math.min(info.startY, info.endY);
69233
+ }
69234
+ else {
69235
+ return Math.max(info.startY, info.endY);
69236
+ }
69237
+ }
69017
69238
  }
69018
69239
 
69019
69240
  /**
@@ -69182,132 +69403,6 @@
69182
69403
  }
69183
69404
  }
69184
69405
 
69185
- /**
69186
- * @internal
69187
- */
69188
- class NoteHeadGlyphBase extends MusicFontGlyph {
69189
- centerOnStem = false;
69190
- constructor(x, y, isGrace, symbol) {
69191
- super(x, y, isGrace ? EngravingSettings.GraceScale : 1, symbol);
69192
- }
69193
- paint(cx, cy, canvas) {
69194
- if (this.centerOnStem) {
69195
- this.center = true;
69196
- }
69197
- super.paint(cx, cy, canvas);
69198
- }
69199
- }
69200
- /**
69201
- * @internal
69202
- */
69203
- class NoteHeadGlyph extends NoteHeadGlyphBase {
69204
- constructor(x, y, duration, isGrace) {
69205
- super(x, y, isGrace, NoteHeadGlyph.getSymbol(duration));
69206
- }
69207
- static getSymbol(duration) {
69208
- switch (duration) {
69209
- case Duration.QuadrupleWhole:
69210
- return MusicFontSymbol.NoteheadDoubleWholeSquare;
69211
- case Duration.DoubleWhole:
69212
- return MusicFontSymbol.NoteheadDoubleWhole;
69213
- case Duration.Whole:
69214
- return MusicFontSymbol.NoteheadWhole;
69215
- case Duration.Half:
69216
- return MusicFontSymbol.NoteheadHalf;
69217
- default:
69218
- return MusicFontSymbol.NoteheadBlack;
69219
- }
69220
- }
69221
- }
69222
-
69223
- /**
69224
- * @internal
69225
- */
69226
- class SlashNoteHeadGlyph extends NoteHeadGlyphBase {
69227
- beatEffects = new Map();
69228
- noteHeadElement = NoteSubElement.SlashNoteHead;
69229
- effectElement = BeatSubElement.SlashEffects;
69230
- _symbol;
69231
- stemX = 0;
69232
- constructor(x, y, duration, isGrace, beat) {
69233
- super(x, y, isGrace, SlashNoteHeadGlyph.getSymbol(duration));
69234
- this._symbol = SlashNoteHeadGlyph.getSymbol(duration);
69235
- this.beat = beat;
69236
- }
69237
- paint(cx, cy, canvas) {
69238
- const _ = this.beat.notes.length === 0
69239
- ? undefined
69240
- : ElementStyleHelper.note(canvas, this.noteHeadElement, this.beat.notes[0]);
69241
- try {
69242
- super.paint(cx, cy, canvas);
69243
- this._paintEffects(cx, cy, canvas);
69244
- }
69245
- finally {
69246
- _?.[Symbol.dispose]?.();
69247
- }
69248
- }
69249
- _paintEffects(cx, cy, canvas) {
69250
- const _ = ElementStyleHelper.beat(canvas, this.effectElement, this.beat);
69251
- try {
69252
- for (const g of this.beatEffects.values()) {
69253
- g.paint(cx + this.x, cy + this.y, canvas);
69254
- }
69255
- }
69256
- finally {
69257
- _?.[Symbol.dispose]?.();
69258
- }
69259
- }
69260
- doLayout() {
69261
- super.doLayout();
69262
- const effectSpacing = this.renderer.smuflMetrics.onNoteEffectPadding;
69263
- let effectY = this.renderer.smuflMetrics.glyphHeights.get(this._symbol);
69264
- let minEffectY = Number.NaN;
69265
- let maxEffectY = Number.NaN;
69266
- for (const g of this.beatEffects.values()) {
69267
- g.y += effectY;
69268
- g.x += this.width / 2;
69269
- g.renderer = this.renderer;
69270
- effectY += g.height + effectSpacing;
69271
- g.doLayout();
69272
- if (Number.isNaN(minEffectY) || minEffectY > effectY) {
69273
- minEffectY = effectY;
69274
- }
69275
- if (Number.isNaN(maxEffectY) || maxEffectY < effectY) {
69276
- maxEffectY = effectY;
69277
- }
69278
- }
69279
- if (!Number.isNaN(minEffectY)) {
69280
- this.renderer.registerBeatEffectOverflows(minEffectY, maxEffectY);
69281
- }
69282
- const direction = this.renderer.getBeatDirection(this.beat);
69283
- const symbol = this._symbol;
69284
- if (direction === BeamDirection.Up) {
69285
- const stemInfoUp = this.renderer.smuflMetrics.stemUp.has(symbol)
69286
- ? this.renderer.smuflMetrics.stemUp.get(symbol).x
69287
- : 0;
69288
- this.stemX = stemInfoUp;
69289
- }
69290
- else {
69291
- const stemInfoDown = this.renderer.smuflMetrics.stemDown.has(symbol)
69292
- ? this.renderer.smuflMetrics.stemDown.get(symbol).x
69293
- : 0;
69294
- this.stemX = stemInfoDown;
69295
- }
69296
- }
69297
- static getSymbol(duration) {
69298
- switch (duration) {
69299
- case Duration.QuadrupleWhole:
69300
- case Duration.DoubleWhole:
69301
- case Duration.Whole:
69302
- return MusicFontSymbol.NoteheadSlashWhiteWhole;
69303
- case Duration.Half:
69304
- return MusicFontSymbol.NoteheadSlashWhiteHalf;
69305
- default:
69306
- return MusicFontSymbol.NoteheadSlashHorizontalEnds;
69307
- }
69308
- }
69309
- }
69310
-
69311
69406
  /**
69312
69407
  * @internal
69313
69408
  */
@@ -69357,6 +69452,44 @@
69357
69452
  }
69358
69453
  }
69359
69454
 
69455
+ /**
69456
+ * @internal
69457
+ */
69458
+ class NoteHeadGlyphBase extends MusicFontGlyph {
69459
+ centerOnStem = false;
69460
+ constructor(x, y, isGrace, symbol) {
69461
+ super(x, y, isGrace ? EngravingSettings.GraceScale : 1, symbol);
69462
+ }
69463
+ paint(cx, cy, canvas) {
69464
+ if (this.centerOnStem) {
69465
+ this.center = true;
69466
+ }
69467
+ super.paint(cx, cy, canvas);
69468
+ }
69469
+ }
69470
+ /**
69471
+ * @internal
69472
+ */
69473
+ class NoteHeadGlyph extends NoteHeadGlyphBase {
69474
+ constructor(x, y, duration, isGrace) {
69475
+ super(x, y, isGrace, NoteHeadGlyph.getSymbol(duration));
69476
+ }
69477
+ static getSymbol(duration) {
69478
+ switch (duration) {
69479
+ case Duration.QuadrupleWhole:
69480
+ return MusicFontSymbol.NoteheadDoubleWholeSquare;
69481
+ case Duration.DoubleWhole:
69482
+ return MusicFontSymbol.NoteheadDoubleWhole;
69483
+ case Duration.Whole:
69484
+ return MusicFontSymbol.NoteheadWhole;
69485
+ case Duration.Half:
69486
+ return MusicFontSymbol.NoteheadHalf;
69487
+ default:
69488
+ return MusicFontSymbol.NoteheadBlack;
69489
+ }
69490
+ }
69491
+ }
69492
+
69360
69493
  /**
69361
69494
  * @internal
69362
69495
  */
@@ -69720,8 +69853,8 @@
69720
69853
  _infos = [];
69721
69854
  _noteHeadInfo;
69722
69855
  noteGroup;
69723
- minNote = null;
69724
- maxNote = null;
69856
+ minStepsNote = null;
69857
+ maxStepsNote = null;
69725
69858
  get stemX() {
69726
69859
  if (!this.noteGroup) {
69727
69860
  return 0;
@@ -69734,25 +69867,19 @@
69734
69867
  super(0, 0);
69735
69868
  }
69736
69869
  getBoundingBoxTop() {
69737
- return this.minNote ? this.minNote.glyph.getBoundingBoxTop() : this.y;
69870
+ return this.minStepsNote ? this.minStepsNote.glyph.getBoundingBoxTop() : this.y;
69738
69871
  }
69739
69872
  getBoundingBoxBottom() {
69740
- return this.maxNote ? this.maxNote.glyph.getBoundingBoxBottom() : this.y + this.height;
69741
- }
69742
- getLowestNoteY() {
69743
- return this.maxNote ? this.renderer.getScoreY(this.maxNote.steps) : 0;
69744
- }
69745
- getHighestNoteY() {
69746
- return this.minNote ? this.renderer.getScoreY(this.minNote.steps) : 0;
69873
+ return this.maxStepsNote ? this.maxStepsNote.glyph.getBoundingBoxBottom() : this.y + this.height;
69747
69874
  }
69748
69875
  add(noteGlyph, noteSteps) {
69749
69876
  const info = { glyph: noteGlyph, steps: noteSteps };
69750
69877
  this._infos.push(info);
69751
- if (!this.minNote || this.minNote.steps > info.steps) {
69752
- this.minNote = info;
69878
+ if (!this.minStepsNote || this.minStepsNote.steps > info.steps) {
69879
+ this.minStepsNote = info;
69753
69880
  }
69754
- if (!this.maxNote || this.maxNote.steps < info.steps) {
69755
- this.maxNote = info;
69881
+ if (!this.maxStepsNote || this.maxStepsNote.steps < info.steps) {
69882
+ this.maxStepsNote = info;
69756
69883
  }
69757
69884
  }
69758
69885
  _prepareForLayout(info) {
@@ -69988,7 +70115,7 @@
69988
70115
  }
69989
70116
  }
69990
70117
  _paintLedgerLines(cx, cy, canvas) {
69991
- if (!this.minNote) {
70118
+ if (!this.minStepsNote) {
69992
70119
  return;
69993
70120
  }
69994
70121
  const scoreRenderer = this.renderer;
@@ -70000,8 +70127,8 @@
70000
70127
  const lineSpacing = scoreRenderer.getLineHeight(1);
70001
70128
  const firstTopLedgerY = scoreRenderer.getLineY(-1);
70002
70129
  const firstBottomLedgerY = scoreRenderer.getLineY(scoreRenderer.drawnLineCount);
70003
- const minNoteLineY = scoreRenderer.getLineY(this.minNote.steps / 2);
70004
- const maxNoteLineY = scoreRenderer.getLineY(this.maxNote.steps / 2);
70130
+ const minNoteLineY = scoreRenderer.getLineY(this.minStepsNote.steps / 2);
70131
+ const maxNoteLineY = scoreRenderer.getLineY(this.maxStepsNote.steps / 2);
70005
70132
  const lineYOffset = (this.renderer.smuflMetrics.legerLineThickness * scale) / 2;
70006
70133
  let y = firstTopLedgerY;
70007
70134
  while (y >= minNoteLineY) {
@@ -70024,21 +70151,87 @@
70024
70151
  * @internal
70025
70152
  */
70026
70153
  class TremoloPickingGlyph extends MusicFontGlyph {
70027
- constructor(x, y, duration) {
70028
- super(x, y, 1, TremoloPickingGlyph._getSymbol(duration));
70154
+ constructor(x, y, effect) {
70155
+ super(x, y, 1, TremoloPickingGlyph._getSymbol(effect));
70029
70156
  }
70030
- static _getSymbol(duration) {
70031
- switch (duration) {
70032
- case Duration.ThirtySecond:
70033
- return MusicFontSymbol.Tremolo3;
70034
- case Duration.Sixteenth:
70035
- return MusicFontSymbol.Tremolo2;
70036
- case Duration.Eighth:
70037
- return MusicFontSymbol.Tremolo1;
70038
- default:
70039
- return MusicFontSymbol.None;
70157
+ static _getSymbol(effect) {
70158
+ if (effect.style === TremoloPickingStyle.BuzzRoll) {
70159
+ return MusicFontSymbol.BuzzRoll;
70160
+ }
70161
+ else {
70162
+ switch (effect.marks) {
70163
+ case 1:
70164
+ return MusicFontSymbol.Tremolo1;
70165
+ case 2:
70166
+ return MusicFontSymbol.Tremolo2;
70167
+ case 3:
70168
+ return MusicFontSymbol.Tremolo3;
70169
+ case 4:
70170
+ return MusicFontSymbol.Tremolo4;
70171
+ case 5:
70172
+ return MusicFontSymbol.Tremolo5;
70173
+ default:
70174
+ return MusicFontSymbol.None;
70175
+ }
70040
70176
  }
70041
70177
  }
70178
+ stemExtensionHeight = 0;
70179
+ alignTremoloPickingGlyph(direction, flagEnd, firstNoteY, duration) {
70180
+ const lr = this.renderer;
70181
+ const smufl = lr.smuflMetrics;
70182
+ let tremoloY = 0;
70183
+ const tremoloOverlap = smufl.glyphHeights.get(MusicFontSymbol.Tremolo1) / 2;
70184
+ const tremoloCenterOffset = this.height / 2;
70185
+ // whether the center or top bar should be aligned with a staff line
70186
+ const forceAlignWithStaffLine = this.symbol === MusicFontSymbol.Tremolo1;
70187
+ const lineSpacing = lr.lineSpacing;
70188
+ const spacing = forceAlignWithStaffLine ? lineSpacing : lineSpacing / 2;
70189
+ if (direction === BeamDirection.Up) {
70190
+ // start at note
70191
+ let flagBottom = flagEnd;
70192
+ // to bottom of stem
70193
+ flagBottom += smufl.stemFlagHeight.get(duration);
70194
+ flagBottom -= smufl.stemFlagOffsets.get(duration);
70195
+ // align with closest step line
70196
+ tremoloY = spacing * Math.ceil(flagBottom / spacing);
70197
+ // ensure at least 1 staff space distance between note and tremolo bottom bar
70198
+ const tremoloBottomY = tremoloY + tremoloCenterOffset;
70199
+ const minSpacingY = firstNoteY - lineSpacing;
70200
+ if (minSpacingY < tremoloBottomY) {
70201
+ tremoloY = minSpacingY - tremoloCenterOffset;
70202
+ }
70203
+ // reserve the additional space needed in the stem height
70204
+ flagBottom += tremoloOverlap;
70205
+ const tremoloTop = tremoloY - tremoloCenterOffset;
70206
+ if (flagBottom > tremoloTop) {
70207
+ this.stemExtensionHeight = flagBottom - tremoloTop;
70208
+ }
70209
+ else {
70210
+ this.stemExtensionHeight = 0;
70211
+ }
70212
+ }
70213
+ else {
70214
+ // same logic as above but inverted
70215
+ let flagTop = flagEnd;
70216
+ flagTop -= smufl.stemFlagHeight.get(duration);
70217
+ flagTop += smufl.stemFlagOffsets.get(duration);
70218
+ tremoloY = spacing * Math.floor(flagTop / spacing);
70219
+ const tremoloTopY = tremoloY - tremoloCenterOffset;
70220
+ const minSpacingY = firstNoteY + lineSpacing;
70221
+ if (minSpacingY > tremoloTopY) {
70222
+ tremoloY = minSpacingY + tremoloCenterOffset;
70223
+ }
70224
+ flagTop -= tremoloOverlap;
70225
+ const tremoloBottom = tremoloY + tremoloCenterOffset;
70226
+ if (flagTop < tremoloBottom) {
70227
+ this.stemExtensionHeight = tremoloBottom - flagTop;
70228
+ }
70229
+ else {
70230
+ this.stemExtensionHeight = 0;
70231
+ }
70232
+ }
70233
+ this.y = tremoloY;
70234
+ }
70042
70235
  }
70043
70236
 
70044
70237
  /**
@@ -70049,6 +70242,7 @@
70049
70242
  _notes = [];
70050
70243
  _deadSlapped = null;
70051
70244
  _tremoloPicking = null;
70245
+ _stemLengthExtension = 0;
70052
70246
  aboveBeatEffects = new Map();
70053
70247
  belowBeatEffects = new Map();
70054
70248
  beat;
@@ -70070,7 +70264,7 @@
70070
70264
  return new ScoreChordNoteHeadInfo(this.direction);
70071
70265
  }
70072
70266
  const staff = this.beat.voice.bar.staff;
70073
- const key = `score.noteheads.${staff.track.index}.${staff.index}.${this.beat.absoluteDisplayStart}`;
70267
+ const key = `score.noteheads.${staff.track.index}.${staff.index}.${this.beat.voice.bar.index}.${this.beat.absoluteDisplayStart}`;
70074
70268
  let existing = this.renderer.staff.getSharedLayoutData(key, undefined);
70075
70269
  if (!existing) {
70076
70270
  existing = new ScoreChordNoteHeadInfo(this.direction);
@@ -70103,19 +70297,26 @@
70103
70297
  }
70104
70298
  return 0;
70105
70299
  }
70300
+ getLowestNoteY(requestedPosition) {
70301
+ return this.maxStepsNote ? this._internalGetNoteY(this.maxStepsNote.glyph, requestedPosition) : 0;
70302
+ }
70303
+ getHighestNoteY(requestedPosition) {
70304
+ return this.minStepsNote ? this._internalGetNoteY(this.minStepsNote.glyph, requestedPosition) : 0;
70305
+ }
70106
70306
  _internalGetNoteY(n, requestedPosition) {
70107
70307
  let pos = this.y + n.y;
70308
+ const sr = this.renderer;
70108
70309
  const scale = this.beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
70109
70310
  switch (requestedPosition) {
70110
70311
  case NoteYPosition.TopWithStem:
70111
70312
  // stem start
70112
70313
  pos -=
70113
- (this.renderer.smuflMetrics.stemUp.has(n.symbol)
70114
- ? this.renderer.smuflMetrics.stemUp.get(n.symbol).bottomY
70115
- : 0) * scale;
70314
+ (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol).bottomY : 0) * scale;
70116
70315
  // stem size according to duration
70117
- pos -= this.renderer.smuflMetrics.standardStemLength * scale;
70118
- const topCenterY = this.renderer.centerStaffStemY(this.direction);
70316
+ pos -= sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
70317
+ pos -= this._stemLengthExtension;
70318
+ let topCenterY = sr.centerStaffStemY(this.direction);
70319
+ topCenterY -= this._stemLengthExtension;
70119
70320
  return Math.min(topCenterY, pos);
70120
70321
  case NoteYPosition.Top:
70121
70322
  pos -= n.height / 2;
@@ -70131,20 +70332,20 @@
70131
70332
  ? this.renderer.smuflMetrics.stemDown.get(n.symbol).topY
70132
70333
  : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70133
70334
  // stem size according to duration
70134
- pos += this.renderer.smuflMetrics.standardStemLength * scale;
70135
- const bottomCenterY = this.renderer.centerStaffStemY(this.direction);
70335
+ pos += sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
70336
+ pos += this._stemLengthExtension;
70337
+ let bottomCenterY = sr.centerStaffStemY(this.direction);
70338
+ bottomCenterY += this._stemLengthExtension;
70136
70339
  return Math.max(bottomCenterY, pos);
70137
70340
  case NoteYPosition.StemUp:
70138
70341
  pos -=
70139
- (this.renderer.smuflMetrics.stemUp.has(n.symbol)
70140
- ? this.renderer.smuflMetrics.stemUp.get(n.symbol).bottomY
70141
- : 0) * scale;
70342
+ (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol).bottomY : 0) * scale;
70142
70343
  break;
70143
70344
  case NoteYPosition.StemDown:
70144
70345
  pos -=
70145
- (this.renderer.smuflMetrics.stemDown.has(n.symbol)
70146
- ? this.renderer.smuflMetrics.stemDown.get(n.symbol).topY
70147
- : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70346
+ (sr.smuflMetrics.stemDown.has(n.symbol)
70347
+ ? sr.smuflMetrics.stemDown.get(n.symbol).topY
70348
+ : -sr.smuflMetrics.glyphHeights.get(n.symbol) / 2) * scale;
70148
70349
  break;
70149
70350
  }
70150
70351
  return pos;
@@ -70176,14 +70377,15 @@
70176
70377
  }
70177
70378
  else {
70178
70379
  if (this.direction === BeamDirection.Up) {
70179
- belowBeatEffectsY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.Bottom) + effectSpacing;
70380
+ belowBeatEffectsY =
70381
+ this._internalGetNoteY(this.maxStepsNote.glyph, NoteYPosition.Bottom) + effectSpacing;
70180
70382
  aboveBeatEffectsY =
70181
- this._internalGetNoteY(this.minNote.glyph, NoteYPosition.TopWithStem) - effectSpacing;
70383
+ this._internalGetNoteY(this.minStepsNote.glyph, NoteYPosition.TopWithStem) - effectSpacing;
70182
70384
  }
70183
70385
  else {
70184
70386
  belowBeatEffectsY =
70185
- this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.BottomWithStem) + effectSpacing;
70186
- aboveBeatEffectsY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.Top) - effectSpacing;
70387
+ this._internalGetNoteY(this.maxStepsNote.glyph, NoteYPosition.BottomWithStem) + effectSpacing;
70388
+ aboveBeatEffectsY = this._internalGetNoteY(this.minStepsNote.glyph, NoteYPosition.Top) - effectSpacing;
70187
70389
  }
70188
70390
  }
70189
70391
  let minEffectY = null;
@@ -70217,27 +70419,27 @@
70217
70419
  scoreRenderer.registerBeatEffectOverflows(minEffectY, maxEffectY ?? 0);
70218
70420
  }
70219
70421
  if (this.beat.isTremolo && !this.beat.deadSlapped) {
70220
- const direction = this.direction;
70221
- let tremoloY = 0;
70222
- if (direction === BeamDirection.Up) {
70223
- const topY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.TopWithStem);
70224
- const bottomY = this._internalGetNoteY(this.minNote.glyph, NoteYPosition.StemUp);
70225
- tremoloY = (topY + bottomY) / 2;
70226
- }
70227
- else {
70228
- const topY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.StemDown);
70229
- const bottomY = this._internalGetNoteY(this.maxNote.glyph, NoteYPosition.BottomWithStem);
70230
- tremoloY = (topY + bottomY) / 2;
70231
- }
70232
- let tremoloX = this.stemX;
70233
- const speed = this.beat.tremoloSpeed;
70234
- if (this.beat.duration < Duration.Half) {
70235
- tremoloX = this.width / 2;
70236
- }
70237
- this._tremoloPicking = new TremoloPickingGlyph(tremoloX, tremoloY, speed);
70422
+ this._tremoloPicking = new TremoloPickingGlyph(0, 0, this.beat.tremoloPicking);
70238
70423
  this._tremoloPicking.renderer = this.renderer;
70239
70424
  this._tremoloPicking.doLayout();
70425
+ this._alignTremoloPickingGlyph();
70426
+ }
70427
+ }
70428
+ _alignTremoloPickingGlyph() {
70429
+ const g = this._tremoloPicking;
70430
+ const direction = this.direction;
70431
+ if (direction === BeamDirection.Up) {
70432
+ g.alignTremoloPickingGlyph(direction, this.getHighestNoteY(NoteYPosition.TopWithStem), this.getHighestNoteY(NoteYPosition.Center), this.beat.duration);
70433
+ }
70434
+ else {
70435
+ g.alignTremoloPickingGlyph(direction, this.getLowestNoteY(NoteYPosition.BottomWithStem), this.getLowestNoteY(NoteYPosition.Center), this.beat.duration);
70436
+ }
70437
+ this._stemLengthExtension = g.stemExtensionHeight;
70438
+ let tremoloX = this.stemX;
70439
+ if (this.beat.duration < Duration.Half) {
70440
+ tremoloX = this.width / 2;
70240
70441
  }
70442
+ g.x = tremoloX;
70241
70443
  }
70242
70444
  buildBoundingsLookup(beatBounds, cx, cy) {
70243
70445
  for (const note of this._notes) {
@@ -70486,17 +70688,17 @@
70486
70688
  if (!endGlyph) {
70487
70689
  return false;
70488
70690
  }
70489
- return !!endGlyph.minNote && !!endGlyph.maxNote;
70691
+ return !!endGlyph.minStepsNote && !!endGlyph.maxStepsNote;
70490
70692
  }
70491
70693
  getBoundingBoxTop() {
70492
- if (this._endGlyph?.minNote) {
70493
- return this._endGlyph.minNote.glyph.getBoundingBoxTop();
70694
+ if (this._endGlyph?.minStepsNote) {
70695
+ return this._endGlyph.minStepsNote.glyph.getBoundingBoxTop();
70494
70696
  }
70495
70697
  return super.getBoundingBoxTop();
70496
70698
  }
70497
70699
  getBoundingBoxBottom() {
70498
- if (this._endGlyph?.maxNote) {
70499
- return this._endGlyph.maxNote.glyph.getBoundingBoxBottom();
70700
+ if (this._endGlyph?.maxStepsNote) {
70701
+ return this._endGlyph.maxStepsNote.glyph.getBoundingBoxBottom();
70500
70702
  }
70501
70703
  return super.getBoundingBoxBottom();
70502
70704
  }
@@ -70729,6 +70931,89 @@
70729
70931
  }
70730
70932
  }
70731
70933
 
70934
+ /**
70935
+ * @internal
70936
+ */
70937
+ class SlashNoteHeadGlyph extends NoteHeadGlyphBase {
70938
+ beatEffects = new Map();
70939
+ noteHeadElement = NoteSubElement.SlashNoteHead;
70940
+ effectElement = BeatSubElement.SlashEffects;
70941
+ stemX = 0;
70942
+ constructor(x, y, beat) {
70943
+ super(x, y, beat.graceType !== GraceType.None, SlashNoteHeadGlyph.getSymbol(beat.duration));
70944
+ this.beat = beat;
70945
+ }
70946
+ paint(cx, cy, canvas) {
70947
+ const _ = this.beat.notes.length === 0
70948
+ ? undefined
70949
+ : ElementStyleHelper.note(canvas, this.noteHeadElement, this.beat.notes[0]);
70950
+ try {
70951
+ super.paint(cx, cy, canvas);
70952
+ this._paintEffects(cx, cy, canvas);
70953
+ }
70954
+ finally {
70955
+ _?.[Symbol.dispose]?.();
70956
+ }
70957
+ }
70958
+ _paintEffects(cx, cy, canvas) {
70959
+ const _ = ElementStyleHelper.beat(canvas, this.effectElement, this.beat);
70960
+ try {
70961
+ for (const g of this.beatEffects.values()) {
70962
+ g.paint(cx + this.x, cy + this.y, canvas);
70963
+ }
70964
+ }
70965
+ finally {
70966
+ _?.[Symbol.dispose]?.();
70967
+ }
70968
+ }
70969
+ doLayout() {
70970
+ super.doLayout();
70971
+ const lr = this.renderer;
70972
+ const effectSpacing = lr.smuflMetrics.onNoteEffectPadding;
70973
+ let effectY = lr.smuflMetrics.glyphHeights.get(this.symbol);
70974
+ let minEffectY = Number.NaN;
70975
+ let maxEffectY = Number.NaN;
70976
+ for (const g of this.beatEffects.values()) {
70977
+ g.y += effectY;
70978
+ g.x += this.width / 2;
70979
+ g.renderer = lr;
70980
+ effectY += g.height + effectSpacing;
70981
+ g.doLayout();
70982
+ if (Number.isNaN(minEffectY) || minEffectY > effectY) {
70983
+ minEffectY = effectY;
70984
+ }
70985
+ if (Number.isNaN(maxEffectY) || maxEffectY < effectY) {
70986
+ maxEffectY = effectY;
70987
+ }
70988
+ }
70989
+ if (!Number.isNaN(minEffectY)) {
70990
+ lr.registerBeatEffectOverflows(minEffectY, maxEffectY);
70991
+ }
70992
+ const direction = lr.getBeatDirection(this.beat);
70993
+ const symbol = this.symbol;
70994
+ if (direction === BeamDirection.Up) {
70995
+ const stemInfoUp = lr.smuflMetrics.stemUp.has(symbol) ? lr.smuflMetrics.stemUp.get(symbol).x : 0;
70996
+ this.stemX = stemInfoUp;
70997
+ }
70998
+ else {
70999
+ const stemInfoDown = lr.smuflMetrics.stemDown.has(symbol) ? lr.smuflMetrics.stemDown.get(symbol).x : 0;
71000
+ this.stemX = stemInfoDown;
71001
+ }
71002
+ }
71003
+ static getSymbol(duration) {
71004
+ switch (duration) {
71005
+ case Duration.QuadrupleWhole:
71006
+ case Duration.DoubleWhole:
71007
+ case Duration.Whole:
71008
+ return MusicFontSymbol.NoteheadSlashWhiteWhole;
71009
+ case Duration.Half:
71010
+ return MusicFontSymbol.NoteheadSlashWhiteHalf;
71011
+ default:
71012
+ return MusicFontSymbol.NoteheadSlashHorizontalEnds;
71013
+ }
71014
+ }
71015
+ }
71016
+
70732
71017
  /**
70733
71018
  * @internal
70734
71019
  */
@@ -70767,9 +71052,6 @@
70767
71052
  get effectElement() {
70768
71053
  return BeatSubElement.StandardNotationEffects;
70769
71054
  }
70770
- getNoteX(note, requestedPosition) {
70771
- return this.noteHeads ? this.noteHeads.getNoteX(note, requestedPosition) : 0;
70772
- }
70773
71055
  buildBoundingsLookup(beatBounds, cx, cy) {
70774
71056
  if (this.noteHeads) {
70775
71057
  this.noteHeads.buildBoundingsLookup(beatBounds, cx + this.x, cy + this.y);
@@ -70801,20 +71083,34 @@
70801
71083
  }
70802
71084
  return y;
70803
71085
  }
70804
- getLowestNoteY() {
70805
- return this.noteHeads ? this.noteHeads.getLowestNoteY() : 0;
71086
+ getLowestNoteY(requestedPosition) {
71087
+ // NOTE: slash handled automatically
71088
+ return this.noteHeads ? this.noteHeads.getLowestNoteY(requestedPosition) : 0;
70806
71089
  }
70807
- getHighestNoteY() {
70808
- return this.noteHeads ? this.noteHeads.getHighestNoteY() : 0;
71090
+ getHighestNoteY(requestedPosition) {
71091
+ // NOTE: slash handled automatically
71092
+ return this.noteHeads ? this.noteHeads.getHighestNoteY(requestedPosition) : 0;
70809
71093
  }
70810
71094
  getNoteY(note, requestedPosition) {
71095
+ // for slashed beats always lookup first note
71096
+ if (note.beat.slashed) {
71097
+ note = note.beat.notes[0];
71098
+ }
70811
71099
  return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
70812
71100
  }
71101
+ getNoteX(note, requestedPosition) {
71102
+ // for slashed beats always lookup first note
71103
+ if (note.beat.slashed) {
71104
+ note = note.beat.notes[0];
71105
+ }
71106
+ return this.noteHeads ? this.noteHeads.getNoteX(note, requestedPosition) : 0;
71107
+ }
70813
71108
  getRestY(requestedPosition) {
70814
71109
  const g = this.restGlyph;
70815
71110
  if (g) {
70816
71111
  switch (requestedPosition) {
70817
71112
  case NoteYPosition.TopWithStem:
71113
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
70818
71114
  case NoteYPosition.Top:
70819
71115
  return g.getBoundingBoxTop();
70820
71116
  case NoteYPosition.Center:
@@ -70822,8 +71118,9 @@
70822
71118
  case NoteYPosition.StemDown:
70823
71119
  return g.getBoundingBoxTop() + g.height / 2;
70824
71120
  case NoteYPosition.Bottom:
70825
- case NoteYPosition.BottomWithStem:
70826
71121
  return g.getBoundingBoxBottom();
71122
+ case NoteYPosition.BottomWithStem:
71123
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
70827
71124
  }
70828
71125
  }
70829
71126
  return 0;
@@ -70906,10 +71203,18 @@
70906
71203
  noteHeads.beat = this.container.beat;
70907
71204
  const ghost = new GhostNoteContainerGlyph(false);
70908
71205
  ghost.renderer = this.renderer;
70909
- for (const note of this.container.beat.notes) {
70910
- if (note.isVisible && (!note.beat.slashed || note.index === 0)) {
70911
- this._createNoteGlyph(note);
70912
- ghost.addParenthesis(note);
71206
+ if (this.container.beat.slashed) {
71207
+ const steps = sr.heightLineCount - 1;
71208
+ const slash = new SlashNoteHeadGlyph(0, sr.getScoreY(steps), this.container.beat);
71209
+ slash.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationNoteHead, this.container.beat.notes[0]);
71210
+ this.noteHeads.addMainNoteGlyph(slash, this.container.beat.notes[0], steps);
71211
+ }
71212
+ else {
71213
+ for (const note of this.container.beat.notes) {
71214
+ if (note.isVisible) {
71215
+ this._createNoteGlyph(note);
71216
+ ghost.addParenthesis(note);
71217
+ }
70913
71218
  }
70914
71219
  }
70915
71220
  this.addNormal(noteHeads);
@@ -70938,6 +71243,24 @@
70938
71243
  this.addEffect(group);
70939
71244
  }
70940
71245
  }
71246
+ if (this.renderer.bar.isMultiVoice) {
71247
+ let highestNotePosition = 0;
71248
+ let lowestNotePosition = 0;
71249
+ const direction = sr.getBeatDirection(this.container.beat);
71250
+ let offset = 0;
71251
+ if (this.container.beat.hasTuplet) {
71252
+ offset += sr.tupletOffset + sr.tupletSize;
71253
+ }
71254
+ if (direction === BeamDirection.Up) {
71255
+ highestNotePosition = this.getHighestNoteY(NoteYPosition.TopWithStem) - offset;
71256
+ lowestNotePosition = this.getLowestNoteY(NoteYPosition.Bottom);
71257
+ }
71258
+ else {
71259
+ highestNotePosition = this.getHighestNoteY(NoteYPosition.Top);
71260
+ lowestNotePosition = this.getLowestNoteY(NoteYPosition.BottomWithStem) + offset;
71261
+ }
71262
+ this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, highestNotePosition, lowestNotePosition);
71263
+ }
70941
71264
  }
70942
71265
  _createRestGlyphs() {
70943
71266
  const sr = this.renderer;
@@ -71004,9 +71327,6 @@
71004
71327
  }
71005
71328
  Logger.warning('Rendering', `No articulation found for percussion instrument ${n.percussionArticulation}`);
71006
71329
  }
71007
- if (n.beat.slashed) {
71008
- return new SlashNoteHeadGlyph(0, 0, n.beat.duration, isGrace, n.beat);
71009
- }
71010
71330
  if (n.isDead) {
71011
71331
  return new DeadNoteHeadGlyph(0, 0, isGrace);
71012
71332
  }
@@ -71026,13 +71346,7 @@
71026
71346
  const noteHeadGlyph = this._createNoteHeadGlyph(n);
71027
71347
  noteHeadGlyph.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationNoteHead, n);
71028
71348
  // calculate y position
71029
- let steps;
71030
- if (n.beat.slashed) {
71031
- steps = sr.heightLineCount - 1;
71032
- }
71033
- else {
71034
- steps = sr.getNoteSteps(n);
71035
- }
71349
+ let steps = sr.getNoteSteps(n);
71036
71350
  noteHeadGlyph.y = sr.getScoreY(steps);
71037
71351
  this.noteHeads.addMainNoteGlyph(noteHeadGlyph, n, steps);
71038
71352
  if (!n.beat.slashed && n.harmonicType !== HarmonicType.None && n.harmonicType !== HarmonicType.Natural) {
@@ -71046,7 +71360,7 @@
71046
71360
  }
71047
71361
  const belowBeatEffects = this.noteHeads.belowBeatEffects;
71048
71362
  const aboveBeatEffects = this.noteHeads.aboveBeatEffects;
71049
- const outsideBeatEffects = this.renderer.getBeatDirection(this.container.beat) === BeamDirection.Up
71363
+ const outsideBeatEffects = sr.getBeatDirection(this.container.beat) === BeamDirection.Up
71050
71364
  ? this.noteHeads.belowBeatEffects
71051
71365
  : this.noteHeads.aboveBeatEffects;
71052
71366
  if (n.isStaccato && !belowBeatEffects.has('Staccato')) {
@@ -71363,16 +71677,16 @@
71363
71677
  switch (note.bendType) {
71364
71678
  case BendType.Bend:
71365
71679
  case BendType.PrebendBend:
71366
- endY = this._endNoteGlyph.minNote.glyph.getBoundingBoxTop();
71680
+ endY = this._endNoteGlyph.minStepsNote.glyph.getBoundingBoxTop();
71367
71681
  endX = width;
71368
71682
  break;
71369
71683
  case BendType.BendRelease:
71370
- endY = this._middleNoteGlyph.minNote.glyph.getBoundingBoxTop();
71684
+ endY = this._middleNoteGlyph.minStepsNote.glyph.getBoundingBoxTop();
71371
71685
  endX = width / 2;
71372
71686
  break;
71373
71687
  case BendType.Release:
71374
71688
  case BendType.PrebendRelease:
71375
- endY = this._endNoteGlyph.maxNote.glyph.getBoundingBoxTop();
71689
+ endY = this._endNoteGlyph.maxStepsNote.glyph.getBoundingBoxTop();
71376
71690
  endX = width;
71377
71691
  break;
71378
71692
  }
@@ -72062,15 +72376,15 @@
72062
72376
  const beat = this.beat;
72063
72377
  const isGrace = beat.graceType !== GraceType.None;
72064
72378
  if (sr.hasFlag(beat)) {
72065
- const direction = this.renderer.getBeatDirection(beat);
72379
+ const direction = sr.getBeatDirection(beat);
72066
72380
  const scale = isGrace ? EngravingSettings.GraceScale : 1;
72067
72381
  const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
72068
- const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
72382
+ const flagWidth = sr.smuflMetrics.glyphWidths.get(symbol) * scale;
72069
72383
  this._flagStretch = flagWidth;
72070
72384
  }
72071
72385
  else if (isGrace) {
72072
72386
  // always use flag size as spacing on grace notes
72073
- const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72387
+ const graceSpacing = sr.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72074
72388
  this._flagStretch = graceSpacing;
72075
72389
  }
72076
72390
  super.doLayout();
@@ -72252,73 +72566,26 @@
72252
72566
  get tupletSubElement() {
72253
72567
  return BeatSubElement.StandardNotationTuplet;
72254
72568
  }
72255
- _getSlashFlagY() {
72256
- const line = (this.heightLineCount - 1) / 2;
72257
- const slashY = this.getLineY(line);
72258
- return slashY;
72259
- }
72260
72569
  getFlagTopY(beat, direction) {
72261
- if (beat.slashed) {
72262
- let slashY = this._getSlashFlagY();
72263
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72264
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72265
- if (direction === BeamDirection.Down) {
72266
- slashY -= this.smuflMetrics.stemDown.has(symbol)
72267
- ? this.smuflMetrics.stemDown.get(symbol).topY * scale
72268
- : 0;
72269
- }
72270
- else {
72271
- slashY -= this.smuflMetrics.stemUp.has(symbol)
72272
- ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
72273
- : 0;
72274
- if (!beat.isRest) {
72275
- slashY -= this.smuflMetrics.standardStemLength + scale;
72276
- }
72277
- }
72278
- return slashY;
72279
- }
72280
- const minNote = this.accidentalHelper.getMinStepsNote(beat);
72281
- if (minNote) {
72282
- return this.getNoteY(minNote, direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown);
72570
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
72571
+ if (beat.isRest) {
72572
+ return this.getRestY(beat, position);
72283
72573
  }
72284
- let y = this.getScoreY(this.accidentalHelper.getMinSteps(beat));
72285
- if (direction === BeamDirection.Up && !beat.isRest) {
72286
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72287
- y -= this.smuflMetrics.standardStemLength * scale;
72574
+ else {
72575
+ return this.voiceContainer.getHighestNoteY(beat, position);
72288
72576
  }
72289
- return y;
72290
72577
  }
72291
72578
  getFlagBottomY(beat, direction) {
72292
- if (beat.slashed) {
72293
- let slashY = this._getSlashFlagY();
72294
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72295
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72296
- if (direction === BeamDirection.Down) {
72297
- slashY -= this.smuflMetrics.stemDown.has(symbol)
72298
- ? this.smuflMetrics.stemDown.get(symbol).topY * scale
72299
- : 0;
72300
- slashY += this.smuflMetrics.standardStemLength + scale;
72301
- }
72302
- else {
72303
- slashY -= this.smuflMetrics.stemUp.has(symbol)
72304
- ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale
72305
- : 0;
72306
- }
72307
- return slashY;
72308
- }
72309
- const maxNote = this.accidentalHelper.getMaxStepsNote(beat);
72310
- if (maxNote) {
72311
- return this.getNoteY(maxNote, direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem);
72579
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
72580
+ if (beat.isRest) {
72581
+ return this.getRestY(beat, position);
72312
72582
  }
72313
- let y = this.getScoreY(this.accidentalHelper.getMaxSteps(beat));
72314
- if (direction === BeamDirection.Down) {
72315
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72316
- y += this.smuflMetrics.standardStemLength * scale;
72583
+ else {
72584
+ return this.voiceContainer.getLowestNoteY(beat, position);
72317
72585
  }
72318
- return y;
72319
72586
  }
72320
72587
  getBeamDirection(helper) {
72321
- return helper.direction;
72588
+ return this._beamDirections.has(helper) ? this._beamDirections.get(helper) : BeamDirection.Up;
72322
72589
  }
72323
72590
  centerStaffStemY(direction) {
72324
72591
  const isStandardFive = this.bar.staff.standardNotationLineCount === Staff.DefaultStandardNotationLineCount;
@@ -72332,50 +72599,9 @@
72332
72599
  }
72333
72600
  return this.getScoreY(0);
72334
72601
  }
72335
- getStemBottomY(_beamingHelper) {
72336
- throw new Error('Method not implemented.');
72337
- }
72338
72602
  get middleYPosition() {
72339
72603
  return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
72340
72604
  }
72341
- getNoteY(note, requestedPosition) {
72342
- if (note.beat.slashed) {
72343
- const line = (this.heightLineCount - 1) / 2;
72344
- return this.getLineY(line);
72345
- }
72346
- let y = super.getNoteY(note, requestedPosition);
72347
- if (Number.isNaN(y)) {
72348
- // NOTE: some might request the note position before the glyphs have been created
72349
- // e.g. the beaming helper, for these we just need a rough
72350
- // estimate on the position
72351
- const steps = AccidentalHelper.computeStepsWithoutAccidentals(this.bar, note);
72352
- y = this.getScoreY(steps);
72353
- const scale = note.beat.graceType === GraceType.None ? 1 : EngravingSettings.GraceScale;
72354
- const stemHeight = this.smuflMetrics.standardStemLength * scale;
72355
- const noteHeadHeight = this.smuflMetrics.glyphHeights.get(NoteHeadGlyph.getSymbol(note.beat.duration)) * scale;
72356
- switch (requestedPosition) {
72357
- case NoteYPosition.TopWithStem:
72358
- y -= stemHeight;
72359
- break;
72360
- case NoteYPosition.Top:
72361
- y -= noteHeadHeight / 2;
72362
- break;
72363
- case NoteYPosition.Center:
72364
- break;
72365
- case NoteYPosition.Bottom:
72366
- y += noteHeadHeight / 2;
72367
- break;
72368
- case NoteYPosition.BottomWithStem:
72369
- y += stemHeight;
72370
- break;
72371
- case NoteYPosition.StemUp:
72372
- break;
72373
- case NoteYPosition.StemDown:
72374
- break;
72375
- }
72376
- }
72377
- return y;
72378
- }
72379
72605
  applyLayoutingInfo() {
72380
72606
  const result = super.applyLayoutingInfo();
72381
72607
  if (result && this.bar.isMultiVoice) {
@@ -72392,34 +72618,6 @@
72392
72618
  }
72393
72619
  return result;
72394
72620
  }
72395
- calculateBeamYWithDirection(h, x, direction) {
72396
- if (h.beats.length === 0) {
72397
- return direction === BeamDirection.Up
72398
- ? this.getFlagTopY(h.beats[0], direction)
72399
- : this.getFlagBottomY(h.beats[0], direction);
72400
- }
72401
- this.ensureBeamDrawingInfo(h, direction);
72402
- return h.drawingInfos.get(direction).calcY(x);
72403
- }
72404
- getBarLineStart(beat, direction) {
72405
- if (beat.slashed) {
72406
- return direction === BeamDirection.Down
72407
- ? this.getFlagTopY(beat, direction)
72408
- : this.getFlagBottomY(beat, direction);
72409
- }
72410
- if (direction === BeamDirection.Up) {
72411
- const maxNote = this.accidentalHelper.getMaxStepsNote(beat);
72412
- if (maxNote) {
72413
- return this.getNoteY(maxNote, NoteYPosition.StemUp);
72414
- }
72415
- return this.getScoreY(this.accidentalHelper.getMaxSteps(beat));
72416
- }
72417
- const minNote = this.accidentalHelper.getMinStepsNote(beat);
72418
- if (minNote) {
72419
- return this.getNoteY(minNote, NoteYPosition.StemDown);
72420
- }
72421
- return this.getScoreY(this.accidentalHelper.getMinSteps(beat));
72422
- }
72423
72621
  getMinLineOfBeat(beat) {
72424
72622
  return this.accidentalHelper.getMinSteps(beat) / 2;
72425
72623
  }
@@ -72561,27 +72759,62 @@
72561
72759
  getNoteSteps(n) {
72562
72760
  return this.accidentalHelper.getNoteSteps(n);
72563
72761
  }
72762
+ _beamDirections = new Map();
72564
72763
  completeBeamingHelper(helper) {
72565
- // for multi-voice bars we need to register the positions
72566
- // for multi-voice rest displacement to avoid collisions
72567
- if (this.bar.isMultiVoice && helper.highestNoteInHelper && helper.lowestNoteInHelper) {
72568
- let highestNotePosition = 0;
72569
- let lowestNotePosition = 0;
72570
- let offset = 0;
72571
- if (helper.hasTuplet) {
72572
- offset += this.resources.effectFont.size * 2;
72573
- }
72574
- if (helper.direction === BeamDirection.Up) {
72575
- highestNotePosition = this.getNoteY(helper.highestNoteInHelper, NoteYPosition.TopWithStem) - offset;
72576
- lowestNotePosition = this.getNoteY(helper.lowestNoteInHelper, NoteYPosition.Bottom);
72577
- }
72578
- else {
72579
- highestNotePosition = this.getNoteY(helper.highestNoteInHelper, NoteYPosition.Top);
72580
- lowestNotePosition = this.getNoteY(helper.lowestNoteInHelper, NoteYPosition.BottomWithStem) + offset;
72581
- }
72582
- for (const beat of helper.beats) {
72583
- this.helpers.collisionHelper.reserveBeatSlot(beat, highestNotePosition, lowestNotePosition);
72584
- }
72764
+ const direction = this._calculateBeamDirection(helper);
72765
+ this._beamDirections.set(helper, direction);
72766
+ }
72767
+ _calculateBeamDirection(helper) {
72768
+ // no proper voice (should not happen usually)
72769
+ if (!helper.voice) {
72770
+ return BeamDirection.Up;
72771
+ }
72772
+ // we have a preferred direction
72773
+ if (helper.preferredBeamDirection !== null) {
72774
+ return helper.preferredBeamDirection;
72775
+ }
72776
+ // on multi-voice setups secondary voices are always down
72777
+ if (helper.voice.index > 0) {
72778
+ return this._invertBeamDirection(helper, BeamDirection.Down);
72779
+ }
72780
+ // on multi-voice setups primary voices are always up
72781
+ if (helper.voice.bar.isMultiVoice) {
72782
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72783
+ }
72784
+ // grace notes are always up
72785
+ if (helper.beats[0].graceType !== GraceType.None) {
72786
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72787
+ }
72788
+ if (helper.beats.length === 1 && helper.beats[0].slashed) {
72789
+ return this._invertBeamDirection(helper, BeamDirection.Down);
72790
+ }
72791
+ // the average line is used for determination
72792
+ // key lowerequal than middle line -> up
72793
+ // key higher than middle line -> down
72794
+ if (helper.highestNoteInHelper && helper.lowestNoteInHelper) {
72795
+ // NOTE: This is the only place where we need the locations before we have positioned the notes
72796
+ // TODO: we should first register all note-heads and calculate the accidentals+steps
72797
+ const highestNotePosition = this._getNoteCenterYBeforeLayouting(helper.highestNoteInHelper);
72798
+ const lowestNotePosition = this._getNoteCenterYBeforeLayouting(helper.lowestNoteInHelper);
72799
+ const avg = (highestNotePosition + lowestNotePosition) / 2;
72800
+ return this._invertBeamDirection(helper, this.middleYPosition < avg ? BeamDirection.Up : BeamDirection.Down);
72801
+ }
72802
+ return this._invertBeamDirection(helper, BeamDirection.Up);
72803
+ }
72804
+ _getNoteCenterYBeforeLayouting(note) {
72805
+ const steps = AccidentalHelper.computeStepsWithoutAccidentals(this.bar, note);
72806
+ return this.getScoreY(steps);
72807
+ }
72808
+ _invertBeamDirection(helper, direction) {
72809
+ if (!helper.invertBeamDirection) {
72810
+ return direction;
72811
+ }
72812
+ switch (direction) {
72813
+ case BeamDirection.Down:
72814
+ return BeamDirection.Up;
72815
+ // case BeamDirection.Up:
72816
+ default:
72817
+ return BeamDirection.Down;
72585
72818
  }
72586
72819
  }
72587
72820
  paintBeamingStem(beat, _cy, x, topY, bottomY, canvas) {
@@ -72624,6 +72857,8 @@
72624
72857
  * @internal
72625
72858
  */
72626
72859
  class SlashBeatGlyph extends BeatOnNoteGlyphBase {
72860
+ _tremoloPicking;
72861
+ _stemLengthExtension = 0;
72627
72862
  noteHeads = null;
72628
72863
  deadSlapped = null;
72629
72864
  restGlyph = null;
@@ -72666,17 +72901,18 @@
72666
72901
  beatBounds.addNote(noteBounds);
72667
72902
  }
72668
72903
  }
72669
- getLowestNoteY() {
72670
- return this.noteHeads ? this.noteHeads.y : 0;
72904
+ getLowestNoteY(requestedPosition) {
72905
+ return this._internalGetNoteY(requestedPosition);
72671
72906
  }
72672
- getHighestNoteY() {
72673
- return this.noteHeads ? this.noteHeads.y : 0;
72907
+ getHighestNoteY(requestedPosition) {
72908
+ return this._internalGetNoteY(requestedPosition);
72674
72909
  }
72675
72910
  getRestY(requestedPosition) {
72676
72911
  const g = this.restGlyph;
72677
72912
  if (g) {
72678
72913
  switch (requestedPosition) {
72679
72914
  case NoteYPosition.TopWithStem:
72915
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
72680
72916
  case NoteYPosition.Top:
72681
72917
  return g.getBoundingBoxTop();
72682
72918
  case NoteYPosition.Center:
@@ -72684,35 +72920,70 @@
72684
72920
  case NoteYPosition.StemDown:
72685
72921
  return g.getBoundingBoxTop() + g.height / 2;
72686
72922
  case NoteYPosition.Bottom:
72687
- case NoteYPosition.BottomWithStem:
72688
72923
  return g.getBoundingBoxBottom();
72924
+ case NoteYPosition.BottomWithStem:
72925
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
72689
72926
  }
72690
72927
  }
72691
72928
  return 0;
72692
72929
  }
72693
- getNoteY(note, requestedPosition) {
72930
+ getNoteY(_note, requestedPosition) {
72931
+ return this._internalGetNoteY(requestedPosition);
72932
+ }
72933
+ _internalGetNoteY(requestedPosition) {
72694
72934
  let g = null;
72695
72935
  let symbol = MusicFontSymbol.None;
72936
+ let hasStem = false;
72696
72937
  if (this.noteHeads) {
72697
72938
  g = this.noteHeads;
72698
- symbol = SlashNoteHeadGlyph.getSymbol(note.beat.duration);
72939
+ symbol = SlashNoteHeadGlyph.getSymbol(this.container.beat.duration);
72940
+ hasStem = true;
72699
72941
  }
72700
72942
  else if (this.deadSlapped) {
72701
72943
  g = this.deadSlapped;
72702
72944
  }
72703
72945
  if (g) {
72704
72946
  let pos = this.y + g.y;
72947
+ const sr = this.renderer;
72948
+ const beat = this.container.beat;
72949
+ const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72705
72950
  switch (requestedPosition) {
72706
- case NoteYPosition.Top:
72707
72951
  case NoteYPosition.TopWithStem:
72952
+ if (hasStem) {
72953
+ // stem start
72954
+ pos -=
72955
+ (sr.smuflMetrics.stemUp.has(symbol) ? sr.smuflMetrics.stemUp.get(symbol).bottomY : 0) *
72956
+ scale;
72957
+ // stem size according to duration
72958
+ pos -= sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale;
72959
+ pos -= this._stemLengthExtension;
72960
+ }
72961
+ else {
72962
+ pos -= g.height / 2;
72963
+ }
72964
+ return pos;
72965
+ case NoteYPosition.Top:
72708
72966
  pos -= g.height / 2;
72709
72967
  break;
72710
72968
  case NoteYPosition.Center:
72711
72969
  break;
72712
72970
  case NoteYPosition.Bottom:
72713
- case NoteYPosition.BottomWithStem:
72714
72971
  pos += g.height / 2;
72715
72972
  break;
72973
+ case NoteYPosition.BottomWithStem:
72974
+ if (hasStem) {
72975
+ pos -=
72976
+ (sr.smuflMetrics.stemDown.has(symbol)
72977
+ ? sr.smuflMetrics.stemDown.get(symbol).topY
72978
+ : -sr.smuflMetrics.glyphHeights.get(symbol) / 2) * scale;
72979
+ // stem size according to duration
72980
+ pos += sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale;
72981
+ pos += this._stemLengthExtension;
72982
+ }
72983
+ else {
72984
+ pos += g.height / 2;
72985
+ }
72986
+ return pos;
72716
72987
  case NoteYPosition.StemUp:
72717
72988
  pos -= this.renderer.smuflMetrics.stemUp.has(symbol)
72718
72989
  ? this.renderer.smuflMetrics.stemUp.get(symbol).bottomY
@@ -72741,11 +73012,16 @@
72741
73012
  }
72742
73013
  else if (!this.container.beat.isEmpty) {
72743
73014
  if (!this.container.beat.isRest) {
72744
- const isGrace = this.container.beat.graceType !== GraceType.None;
72745
- const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat.duration, isGrace, this.container.beat);
73015
+ const noteHeadGlyph = new SlashNoteHeadGlyph(0, glyphY, this.container.beat);
72746
73016
  this.noteHeads = noteHeadGlyph;
72747
73017
  noteHeadGlyph.beat = this.container.beat;
72748
73018
  this.addNormal(noteHeadGlyph);
73019
+ if (this.container.beat.isTremolo) {
73020
+ this._tremoloPicking = new TremoloPickingGlyph(0, 0, this.container.beat.tremoloPicking);
73021
+ this._tremoloPicking.renderer = this.renderer;
73022
+ this._tremoloPicking.doLayout();
73023
+ this._alignTremoloPickingGlyph();
73024
+ }
72749
73025
  }
72750
73026
  else {
72751
73027
  const restGlyph = new SlashRestGlyph(0, glyphY, this.container.beat.duration);
@@ -72780,6 +73056,27 @@
72780
73056
  this.stemX = this.onTimeX;
72781
73057
  }
72782
73058
  this.middleX = this.onTimeX;
73059
+ const tremolo = this._tremoloPicking;
73060
+ if (tremolo) {
73061
+ tremolo.x = this.container.beat.duration < Duration.Half ? this.width / 2 : this.stemX;
73062
+ }
73063
+ }
73064
+ _alignTremoloPickingGlyph() {
73065
+ const g = this._tremoloPicking;
73066
+ g.alignTremoloPickingGlyph(BeamDirection.Up, this._internalGetNoteY(NoteYPosition.TopWithStem), this._internalGetNoteY(NoteYPosition.Center), this.container.beat.duration);
73067
+ this._stemLengthExtension = g.stemExtensionHeight;
73068
+ let tremoloX = this.stemX;
73069
+ if (this.container.beat.duration < Duration.Half) {
73070
+ tremoloX = this.width / 2;
73071
+ }
73072
+ g.x = tremoloX;
73073
+ }
73074
+ paint(cx, cy, canvas) {
73075
+ super.paint(cx, cy, canvas);
73076
+ const tremolo = this._tremoloPicking;
73077
+ if (tremolo) {
73078
+ tremolo.paint(cx + this.x, cy + this.y, canvas);
73079
+ }
72783
73080
  }
72784
73081
  }
72785
73082
 
@@ -72814,15 +73111,15 @@
72814
73111
  const beat = this.beat;
72815
73112
  const isGrace = beat.graceType !== GraceType.None;
72816
73113
  if (sr.hasFlag(beat)) {
72817
- const direction = this.renderer.getBeatDirection(beat);
73114
+ const direction = sr.getBeatDirection(beat);
72818
73115
  const scale = isGrace ? EngravingSettings.GraceScale : 1;
72819
73116
  const symbol = FlagGlyph.getSymbol(beat.duration, direction, isGrace);
72820
- const flagWidth = this.renderer.smuflMetrics.glyphWidths.get(symbol) * scale;
73117
+ const flagWidth = sr.smuflMetrics.glyphWidths.get(symbol) * scale;
72821
73118
  this._flagStretch = flagWidth;
72822
73119
  }
72823
73120
  else if (isGrace) {
72824
73121
  // always use flag size as spacing on grace notes
72825
- const graceSpacing = this.renderer.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
73122
+ const graceSpacing = sr.smuflMetrics.glyphWidths.get(MusicFontSymbol.Flag8thUp) * EngravingSettings.GraceScale;
72826
73123
  this._flagStretch = graceSpacing;
72827
73124
  }
72828
73125
  super.doLayout();
@@ -72909,45 +73206,26 @@
72909
73206
  getNoteLine(_note) {
72910
73207
  return 0;
72911
73208
  }
72912
- getFlagTopY(beat, _direction) {
72913
- let slashY = this.getLineY(0);
72914
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72915
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72916
- slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
72917
- if (!beat.isRest) {
72918
- slashY -= this.smuflMetrics.standardStemLength + scale;
73209
+ getFlagTopY(beat, direction) {
73210
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
73211
+ if (beat.notes.length > 0) {
73212
+ return this.getNoteY(beat.notes[0], position);
72919
73213
  }
72920
- return slashY;
72921
- }
72922
- getFlagBottomY(beat, _direction) {
72923
- let slashY = this.getLineY(0);
72924
- const symbol = SlashNoteHeadGlyph.getSymbol(beat.duration);
72925
- const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
72926
- slashY -= this.smuflMetrics.stemUp.has(symbol) ? this.smuflMetrics.stemUp.get(symbol).bottomY * scale : 0;
72927
- return slashY;
72928
- }
72929
- getBeamDirection(_helper) {
72930
- return BeamDirection.Up;
72931
- }
72932
- getNoteY(note, requestedPosition) {
72933
- let y = super.getNoteY(note, requestedPosition);
72934
- if (Number.isNaN(y)) {
72935
- y = this.getLineY(0);
73214
+ else {
73215
+ return this.getRestY(beat, position);
72936
73216
  }
72937
- return y;
72938
73217
  }
72939
- calculateBeamYWithDirection(h, x, direction) {
72940
- if (h.beats.length === 0) {
72941
- return direction === BeamDirection.Up
72942
- ? this.getFlagTopY(h.beats[0], direction)
72943
- : this.getFlagBottomY(h.beats[0], direction);
73218
+ getFlagBottomY(beat, direction) {
73219
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
73220
+ if (beat.notes.length > 0) {
73221
+ return this.getNoteY(beat.notes[0], position);
73222
+ }
73223
+ else {
73224
+ return this.getRestY(beat, position);
72944
73225
  }
72945
- this.ensureBeamDrawingInfo(h, direction);
72946
- return h.drawingInfos.get(direction).calcY(x);
72947
73226
  }
72948
- getBarLineStart(_beat, _direction) {
72949
- const noteHeadHeight = this.smuflMetrics.glyphHeights.get(MusicFontSymbol.NoteheadSlashWhiteHalf);
72950
- return this.getLineY(0) - noteHeadHeight / 2;
73227
+ getBeamDirection(_helper) {
73228
+ return BeamDirection.Up;
72951
73229
  }
72952
73230
  createLinePreBeatGlyphs() {
72953
73231
  // Key signature
@@ -72991,6 +73269,9 @@
72991
73269
  }
72992
73270
  this.calculateBeamingOverflows(rendererTop, rendererBottom);
72993
73271
  }
73272
+ shouldPaintBeamingHelper(h) {
73273
+ return super.shouldPaintBeamingHelper(h) && h.voice.index === 0;
73274
+ }
72994
73275
  paintBeamingStem(beat, _cy, x, topY, bottomY, canvas) {
72995
73276
  const _ = ElementStyleHelper.beat(canvas, BeatSubElement.SlashStem, beat);
72996
73277
  try {
@@ -73037,6 +73318,15 @@
73037
73318
  super(x, y);
73038
73319
  this._note = note;
73039
73320
  }
73321
+ get _padding() {
73322
+ return this.renderer.lineSpacing * 0.25;
73323
+ }
73324
+ getBoundingBoxTop() {
73325
+ return this.y - this.height / 2 - this._padding;
73326
+ }
73327
+ getBoundingBoxBottom() {
73328
+ return this.y + this.height / 2;
73329
+ }
73040
73330
  doLayout() {
73041
73331
  const n = this._note;
73042
73332
  let fret = n.fret - n.beat.voice.bar.staff.transpositionPitch;
@@ -73111,11 +73401,12 @@
73111
73401
  return;
73112
73402
  }
73113
73403
  const textWidth = this.noteStringWidth + this._trillNoteStringWidth;
73114
- const x = (cx + this.x + (this.width - textWidth) / 2);
73115
- this.paintTrill(x, cy, canvas);
73404
+ const x = cx + this.x + (this.width - textWidth) / 2;
73405
+ const y = cy + this.y;
73406
+ this.paintTrill(x, y, canvas);
73116
73407
  const _ = ElementStyleHelper.note(canvas, NoteSubElement.GuitarTabFretNumber, this._note);
73117
73408
  try {
73118
- canvas.fillText(this._noteString, x, cy + this.y);
73409
+ canvas.fillText(this._noteString, x, y);
73119
73410
  }
73120
73411
  finally {
73121
73412
  _?.[Symbol.dispose]?.();
@@ -73126,7 +73417,7 @@
73126
73417
  try {
73127
73418
  const prevFont = this.renderer.scoreRenderer.canvas.font;
73128
73419
  this.renderer.scoreRenderer.canvas.font = this.renderer.resources.graceFont;
73129
- canvas.fillText(this._trillNoteString, x + this.noteStringWidth, cy + this.y);
73420
+ canvas.fillText(this._trillNoteString, x + this.noteStringWidth, cy);
73130
73421
  this.renderer.scoreRenderer.canvas.font = prevFont;
73131
73422
  }
73132
73423
  finally {
@@ -73185,11 +73476,11 @@
73185
73476
  }
73186
73477
  return 0;
73187
73478
  }
73188
- getLowestNoteY() {
73189
- return this.maxStringNote ? this.getNoteY(this.maxStringNote, NoteYPosition.Center) : 0;
73479
+ getLowestNoteY(requestedPosition) {
73480
+ return this.maxStringNote ? this.getNoteY(this.maxStringNote, requestedPosition) : 0;
73190
73481
  }
73191
- getHighestNoteY() {
73192
- return this.minStringNote ? this.getNoteY(this.minStringNote, NoteYPosition.Center) : 0;
73482
+ getHighestNoteY(requestedPosition) {
73483
+ return this.minStringNote ? this.getNoteY(this.minStringNote, requestedPosition) : 0;
73193
73484
  }
73194
73485
  getNoteY(note, requestedPosition) {
73195
73486
  if (this.notesPerString.has(note.string)) {
@@ -73197,23 +73488,44 @@
73197
73488
  let pos = this.y + n.y;
73198
73489
  switch (requestedPosition) {
73199
73490
  case NoteYPosition.Top:
73200
- case NoteYPosition.TopWithStem:
73201
73491
  pos -= n.height / 2;
73202
73492
  break;
73493
+ case NoteYPosition.StemUp:
73494
+ pos = this.y + n.getBoundingBoxTop();
73495
+ break;
73203
73496
  case NoteYPosition.Center:
73204
73497
  break;
73205
73498
  case NoteYPosition.Bottom:
73206
- case NoteYPosition.BottomWithStem:
73207
73499
  pos += n.height / 2;
73208
73500
  break;
73209
- case NoteYPosition.StemUp:
73210
73501
  case NoteYPosition.StemDown:
73502
+ pos = this.y + n.getBoundingBoxBottom();
73503
+ break;
73504
+ case NoteYPosition.TopWithStem:
73505
+ pos = -this.renderer.settings.notation.rhythmHeight;
73506
+ pos -= this.calculateTremoloHeightForStem();
73507
+ break;
73508
+ case NoteYPosition.BottomWithStem:
73509
+ pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight;
73510
+ pos += this.calculateTremoloHeightForStem();
73211
73511
  break;
73212
73512
  }
73213
73513
  return pos;
73214
73514
  }
73215
73515
  return 0;
73216
73516
  }
73517
+ calculateTremoloHeightForStem() {
73518
+ const beat = this.beat;
73519
+ if (!beat.isTremolo) {
73520
+ return 0;
73521
+ }
73522
+ if (beat.duration <= Duration.Quarter) {
73523
+ return 0;
73524
+ }
73525
+ const symbol = TremoloPickingGlyph._getSymbol(beat.tremoloPicking);
73526
+ const smufl = this.renderer.smuflMetrics;
73527
+ return smufl.glyphHeights.has(symbol) ? smufl.glyphHeights.get(symbol) : 0;
73528
+ }
73217
73529
  doLayout() {
73218
73530
  let w = 0;
73219
73531
  if (this.beat.deadSlapped) {
@@ -73339,6 +73651,20 @@
73339
73651
  return BeatSubElement.GuitarTabEffects;
73340
73652
  }
73341
73653
  getNoteX(note, requestedPosition) {
73654
+ if (this.slash) {
73655
+ let pos = this.slash.x;
73656
+ switch (requestedPosition) {
73657
+ case NoteXPosition.Left:
73658
+ break;
73659
+ case NoteXPosition.Center:
73660
+ pos += this.slash.width / 2;
73661
+ break;
73662
+ case NoteXPosition.Right:
73663
+ pos += this.slash.width;
73664
+ break;
73665
+ }
73666
+ return pos;
73667
+ }
73342
73668
  return this.noteNumbers ? this.noteNumbers.getNoteX(note, requestedPosition) : 0;
73343
73669
  }
73344
73670
  getNoteY(note, requestedPosition) {
@@ -73349,6 +73675,7 @@
73349
73675
  if (g) {
73350
73676
  switch (requestedPosition) {
73351
73677
  case NoteYPosition.TopWithStem:
73678
+ return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
73352
73679
  case NoteYPosition.Top:
73353
73680
  return g.getBoundingBoxTop();
73354
73681
  case NoteYPosition.Center:
@@ -73356,17 +73683,18 @@
73356
73683
  case NoteYPosition.StemDown:
73357
73684
  return g.getBoundingBoxTop() + g.height / 2;
73358
73685
  case NoteYPosition.Bottom:
73686
+ return g.getBoundingBoxTop();
73359
73687
  case NoteYPosition.BottomWithStem:
73360
- return g.getBoundingBoxBottom();
73688
+ return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
73361
73689
  }
73362
73690
  }
73363
73691
  return 0;
73364
73692
  }
73365
- getLowestNoteY() {
73366
- return this.noteNumbers ? this.noteNumbers.getLowestNoteY() : 0;
73693
+ getLowestNoteY(requestedPosition) {
73694
+ return this.noteNumbers ? this.noteNumbers.getLowestNoteY(requestedPosition) : 0;
73367
73695
  }
73368
- getHighestNoteY() {
73369
- return this.noteNumbers ? this.noteNumbers.getHighestNoteY() : 0;
73696
+ getHighestNoteY(requestedPosition) {
73697
+ return this.noteNumbers ? this.noteNumbers.getHighestNoteY(requestedPosition) : 0;
73370
73698
  }
73371
73699
  buildBoundingsLookup(beatBounds, cx, cy) {
73372
73700
  if (this.noteNumbers) {
@@ -73384,7 +73712,7 @@
73384
73712
  if (this.container.beat.slashed && !this.container.beat.notes.some(x => x.isTieDestination)) {
73385
73713
  const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2);
73386
73714
  const slashY = tabRenderer.getLineY(line);
73387
- const slashNoteHead = new SlashNoteHeadGlyph(0, slashY, this.container.beat.duration, isGrace, this.container.beat);
73715
+ const slashNoteHead = new SlashNoteHeadGlyph(0, slashY, this.container.beat);
73388
73716
  slashNoteHead.noteHeadElement = NoteSubElement.GuitarTabFretNumber;
73389
73717
  slashNoteHead.effectElement = BeatSubElement.GuitarTabEffects;
73390
73718
  this.slash = slashNoteHead;
@@ -73407,8 +73735,7 @@
73407
73735
  //
73408
73736
  // Tremolo Picking
73409
73737
  if (this.container.beat.isTremolo && !beatEffects.has('tremolo')) {
73410
- const speed = this.container.beat.tremoloSpeed;
73411
- const glyph = new TremoloPickingGlyph(0, 0, speed);
73738
+ const glyph = new TremoloPickingGlyph(0, 0, this.container.beat.tremoloPicking);
73412
73739
  glyph.offsetY = this.renderer.smuflMetrics.glyphTop.get(glyph.symbol);
73413
73740
  beatEffects.set('tremolo', glyph);
73414
73741
  centeredEffectGlyphs.push(glyph);
@@ -73417,7 +73744,7 @@
73417
73744
  // Note dots
73418
73745
  //
73419
73746
  if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== exports.TabRhythmMode.Hidden) {
73420
- const y = tabRenderer.getFlagAndBarPos();
73747
+ const y = this.getNoteY(this.container.beat.maxNote, NoteYPosition.BottomWithStem);
73421
73748
  for (let i = 0; i < this.container.beat.dots; i++) {
73422
73749
  this.addEffect(new AugmentationDotGlyph(0, y));
73423
73750
  }
@@ -73479,8 +73806,8 @@
73479
73806
  noteNumberGlyph.renderer = this.renderer;
73480
73807
  noteNumberGlyph.doLayout();
73481
73808
  this.noteNumbers.addNoteGlyph(noteNumberGlyph, n);
73482
- const topY = noteNumberGlyph.y - noteNumberGlyph.height / 2;
73483
- const bottomY = topY + noteNumberGlyph.height;
73809
+ const topY = noteNumberGlyph.getBoundingBoxTop();
73810
+ const bottomY = noteNumberGlyph.getBoundingBoxBottom();
73484
73811
  this.renderer.collisionHelper.reserveBeatSlot(this.container.beat, topY, bottomY);
73485
73812
  const minString = tr.minString;
73486
73813
  const maxString = tr.maxString;
@@ -74007,30 +74334,6 @@
74007
74334
  }
74008
74335
  }
74009
74336
  }
74010
- adjustSizes() {
74011
- if (this.rhythmMode !== exports.TabRhythmMode.Hidden) {
74012
- let shortestTremolo = Duration.Whole;
74013
- for (const b of this.helpers.beamHelpers) {
74014
- for (const h of b) {
74015
- if (h.tremoloDuration && (!shortestTremolo || shortestTremolo < h.tremoloDuration)) {
74016
- shortestTremolo = h.tremoloDuration;
74017
- }
74018
- }
74019
- }
74020
- switch (shortestTremolo) {
74021
- case Duration.Eighth:
74022
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo1);
74023
- break;
74024
- case Duration.Sixteenth:
74025
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo2);
74026
- break;
74027
- case Duration.ThirtySecond:
74028
- this.height += this.smuflMetrics.glyphHeights.get(MusicFontSymbol.Tremolo3);
74029
- break;
74030
- }
74031
- this.registerOverflowBottom(this.settings.notation.rhythmHeight);
74032
- }
74033
- }
74034
74337
  doLayout() {
74035
74338
  const hasStandardNotation = this.bar.staff.showStandardNotation && this.scoreRenderer.layout.profile.has(ScoreBarRenderer.StaffId);
74036
74339
  if (!hasStandardNotation) {
@@ -74111,32 +74414,29 @@
74111
74414
  drawBeamHelperAsFlags(h) {
74112
74415
  return super.drawBeamHelperAsFlags(h) || this.rhythmMode === exports.TabRhythmMode.ShowWithBeams;
74113
74416
  }
74114
- getFlagTopY(beat, _direction) {
74115
- const container = this.getBeatContainer(beat);
74116
- if (!container || !beat.minStringNote || beat.duration === Duration.Half) {
74117
- return this.height - this.settings.notation.rhythmHeight - this.tupletSize;
74417
+ getFlagTopY(beat, direction) {
74418
+ const maxNote = beat.maxStringNote;
74419
+ const position = direction === BeamDirection.Up ? NoteYPosition.TopWithStem : NoteYPosition.StemDown;
74420
+ if (maxNote) {
74421
+ return this.getNoteY(maxNote, position);
74422
+ }
74423
+ else {
74424
+ return this.getRestY(beat, position);
74118
74425
  }
74119
- return container.getNoteY(beat.minStringNote, NoteYPosition.Bottom) + this.smuflMetrics.staffLineThickness;
74120
- }
74121
- getFlagBottomY(_beat, _direction) {
74122
- return this.getFlagAndBarPos();
74123
- }
74124
- getFlagStemSize(_duration, _forceMinStem = false) {
74125
- return 0; // fixed size via getFlagBottomY
74126
74426
  }
74127
- getBarLineStart(beat, direction) {
74128
- return this.getFlagTopY(beat, direction);
74427
+ getFlagBottomY(beat, direction) {
74428
+ const maxNote = beat.minStringNote;
74429
+ const position = direction === BeamDirection.Up ? NoteYPosition.StemUp : NoteYPosition.BottomWithStem;
74430
+ if (maxNote) {
74431
+ return this.getNoteY(maxNote, position);
74432
+ }
74433
+ else {
74434
+ return this.getRestY(beat, position);
74435
+ }
74129
74436
  }
74130
74437
  getBeamDirection(_helper) {
74131
74438
  return BeamDirection.Down;
74132
74439
  }
74133
- getFlagAndBarPos() {
74134
- return this.height + this.settings.notation.rhythmHeight - (this._hasTuplets ? this.tupletSize / 2 : 0);
74135
- }
74136
- calculateBeamYWithDirection(_h, _x, _direction) {
74137
- // currently only used for duplets
74138
- return this.getFlagAndBarPos();
74139
- }
74140
74440
  shouldPaintFlag(beat) {
74141
74441
  if (!super.shouldPaintFlag(beat)) {
74142
74442
  return false;
@@ -74159,19 +74459,20 @@
74159
74459
  holes = this.helpers.collisionHelper.reservedLayoutAreasByDisplayTime.get(beat.displayStart).slots.slice();
74160
74460
  holes.sort((a, b) => a.topY - b.topY);
74161
74461
  }
74162
- let y = bottomY;
74163
- while (y > topY) {
74164
- let lineY = topY;
74165
- // draw until next hole (if hole reaches into line)
74166
- if (holes.length > 0 && holes[holes.length - 1].bottomY > lineY) {
74167
- const bottomHole = holes.pop();
74168
- lineY = cy + bottomHole.bottomY;
74169
- canvas.fillRect(x, lineY, this.smuflMetrics.stemThickness, y - lineY);
74170
- y = cy + bottomHole.topY;
74171
- }
74172
- else {
74173
- canvas.fillRect(x, lineY, this.smuflMetrics.stemThickness, y - lineY);
74174
- break;
74462
+ // fast path -> single note == full line
74463
+ if (holes.length === 1) {
74464
+ canvas.fillRect(x, topY, this.smuflMetrics.stemThickness, bottomY - topY);
74465
+ return;
74466
+ }
74467
+ const bottomYRelative = bottomY - cy;
74468
+ // slow path -> multiple notes == lines between notes
74469
+ const bottomHole = holes[holes.length - 1];
74470
+ canvas.fillRect(x, cy + bottomHole.bottomY, this.smuflMetrics.stemThickness, bottomYRelative - bottomHole.bottomY);
74471
+ for (let i = holes.length - 1; i > 0; i--) {
74472
+ const bottomHoleY = holes[i].topY;
74473
+ const topHoleY = holes[i - 1].bottomY;
74474
+ if (topHoleY < bottomHoleY) {
74475
+ canvas.fillRect(x, cy + topHoleY, this.smuflMetrics.stemThickness, bottomHoleY - topHoleY);
74175
74476
  }
74176
74477
  }
74177
74478
  }
@@ -74179,6 +74480,15 @@
74179
74480
  _?.[Symbol.dispose]?.();
74180
74481
  }
74181
74482
  }
74483
+ calculateOverflows(rendererTop, rendererBottom) {
74484
+ super.calculateOverflows(rendererTop, rendererBottom);
74485
+ if (this.bar.isEmpty) {
74486
+ return;
74487
+ }
74488
+ if (this.rhythmMode !== exports.TabRhythmMode.Hidden) {
74489
+ this.calculateBeamingOverflows(rendererTop, rendererBottom);
74490
+ }
74491
+ }
74182
74492
  }
74183
74493
 
74184
74494
  /**
@@ -76356,16 +76666,17 @@
76356
76666
  beatNode.addElement('Fadding').innerText = FadeType[beat.fade];
76357
76667
  }
76358
76668
  if (beat.isTremolo) {
76359
- switch (beat.tremoloSpeed) {
76360
- case Duration.Eighth:
76669
+ switch (beat.tremoloPicking.marks) {
76670
+ case 1:
76361
76671
  beatNode.addElement('Tremolo').innerText = '1/2';
76362
76672
  break;
76363
- case Duration.Sixteenth:
76673
+ case 2:
76364
76674
  beatNode.addElement('Tremolo').innerText = '1/4';
76365
76675
  break;
76366
- case Duration.ThirtySecond:
76676
+ case 3:
76367
76677
  beatNode.addElement('Tremolo').innerText = '1/8';
76368
76678
  break;
76679
+ // NOTE: guitar pro does not support other tremolos
76369
76680
  }
76370
76681
  }
76371
76682
  if (beat.hasChord) {
@@ -79362,6 +79673,8 @@
79362
79673
  get TrackNamePolicy () { return TrackNamePolicy; },
79363
79674
  TrackStyle,
79364
79675
  get TrackSubElement () { return TrackSubElement; },
79676
+ TremoloPickingEffect,
79677
+ get TremoloPickingStyle () { return TremoloPickingStyle; },
79365
79678
  get TripletFeel () { return TripletFeel; },
79366
79679
  Tuning,
79367
79680
  TupletGroup,