@coderline/alphatab 1.9.0-alpha.1768 → 1.9.0-alpha.1803
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/README.md +1 -3
- package/dist/alphaTab.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +837 -107
- package/dist/alphaTab.d.ts +50 -62
- package/dist/alphaTab.js +837 -107
- package/dist/alphaTab.min.js +2 -2
- package/dist/alphaTab.min.mjs +1 -1
- package/dist/alphaTab.mjs +1 -1
- package/dist/alphaTab.worker.min.mjs +1 -1
- package/dist/alphaTab.worker.mjs +1 -1
- package/dist/alphaTab.worklet.min.mjs +1 -1
- package/dist/alphaTab.worklet.mjs +1 -1
- package/package.json +10 -15
package/dist/alphaTab.core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.9.0-alpha.
|
|
2
|
+
* alphaTab v1.9.0-alpha.1803 (develop, build 1803)
|
|
3
3
|
*
|
|
4
4
|
* Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
|
|
5
5
|
*
|
|
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
|
|
|
203
203
|
* @internal
|
|
204
204
|
*/
|
|
205
205
|
class VersionInfo {
|
|
206
|
-
static version = '1.9.0-alpha.
|
|
207
|
-
static date = '2026-
|
|
208
|
-
static commit = '
|
|
206
|
+
static version = '1.9.0-alpha.1803';
|
|
207
|
+
static date = '2026-05-15T04:13:04.606Z';
|
|
208
|
+
static commit = 'a87a8635a0a0306cdab1c7fe2e9e72576ed6f795';
|
|
209
209
|
static print(print) {
|
|
210
210
|
print(`alphaTab ${VersionInfo.version}`);
|
|
211
211
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -992,6 +992,38 @@ var SlideOutType;
|
|
|
992
992
|
SlideOutType[SlideOutType["PickSlideUp"] = 6] = "PickSlideUp";
|
|
993
993
|
})(SlideOutType || (SlideOutType = {}));
|
|
994
994
|
|
|
995
|
+
/**
|
|
996
|
+
* A slur arc spanning two notes, optionally with inner articulation
|
|
997
|
+
* segments. Corresponds conceptually to a MusicXML `<slur>` element
|
|
998
|
+
* plus the technique spans inside it.
|
|
999
|
+
*
|
|
1000
|
+
* For this PR only effect slurs (hammer-pull + legato-slide chains)
|
|
1001
|
+
* are derived in `Note.finish()`. Phrase and legato slurs may join
|
|
1002
|
+
* this type in a future PR; a discriminator will be added at that
|
|
1003
|
+
* point.
|
|
1004
|
+
* @internal
|
|
1005
|
+
*/
|
|
1006
|
+
class Slur {
|
|
1007
|
+
originNote;
|
|
1008
|
+
destinationNote;
|
|
1009
|
+
segments = [];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Articulation kind for an inner span of a {@link Slur}.
|
|
1014
|
+
*
|
|
1015
|
+
* Drives the renderer's font selection (which {@link NotationElement} to
|
|
1016
|
+
* use) and the default label text when {@link SlurSegment.text} is null.
|
|
1017
|
+
* `Note.finish()` classifies the kind once when building the slur; the
|
|
1018
|
+
* renderer never re-derives it.
|
|
1019
|
+
* @internal
|
|
1020
|
+
*/
|
|
1021
|
+
var SlurSegmentKind;
|
|
1022
|
+
(function (SlurSegmentKind) {
|
|
1023
|
+
SlurSegmentKind[SlurSegmentKind["HammerPull"] = 0] = "HammerPull";
|
|
1024
|
+
SlurSegmentKind[SlurSegmentKind["LegatoSlide"] = 1] = "LegatoSlide";
|
|
1025
|
+
})(SlurSegmentKind || (SlurSegmentKind = {}));
|
|
1026
|
+
|
|
995
1027
|
/**
|
|
996
1028
|
* This public enum lists all vibrato types that can be performed.
|
|
997
1029
|
* @public
|
|
@@ -1332,6 +1364,14 @@ var NotationElement;
|
|
|
1332
1364
|
* The slurs shown on bend effects within the score staff.
|
|
1333
1365
|
*/
|
|
1334
1366
|
NotationElement[NotationElement["ScoreBendSlur"] = 55] = "ScoreBendSlur";
|
|
1367
|
+
/**
|
|
1368
|
+
* The hammer-on pull-off text shown on slurs.
|
|
1369
|
+
*/
|
|
1370
|
+
NotationElement[NotationElement["EffectHammerOnPullOffText"] = 56] = "EffectHammerOnPullOffText";
|
|
1371
|
+
/**
|
|
1372
|
+
* The slide text shown on slurs.
|
|
1373
|
+
*/
|
|
1374
|
+
NotationElement[NotationElement["EffectSlideText"] = 57] = "EffectSlideText";
|
|
1335
1375
|
})(NotationElement || (NotationElement = {}));
|
|
1336
1376
|
/**
|
|
1337
1377
|
* The notation settings control how various music notation elements are shown and behaving
|
|
@@ -6128,6 +6168,16 @@ class Note {
|
|
|
6128
6168
|
* @json_ignore
|
|
6129
6169
|
*/
|
|
6130
6170
|
effectSlurDestination = null;
|
|
6171
|
+
/**
|
|
6172
|
+
* The {@link Slur} object whose origin is this note. Populated by
|
|
6173
|
+
* `finish()`; non-null only on the chain-origin note of an effect
|
|
6174
|
+
* slur. Carries the inner articulation segments used by the
|
|
6175
|
+
* renderer to paint H/P/sl. labels along the arc.
|
|
6176
|
+
* @clone_ignore
|
|
6177
|
+
* @json_ignore
|
|
6178
|
+
* @internal
|
|
6179
|
+
*/
|
|
6180
|
+
effectSlur = null;
|
|
6131
6181
|
/**
|
|
6132
6182
|
* The ornament applied on the note.
|
|
6133
6183
|
*/
|
|
@@ -6412,23 +6462,50 @@ class Note {
|
|
|
6412
6462
|
break;
|
|
6413
6463
|
}
|
|
6414
6464
|
let effectSlurDestination = null;
|
|
6465
|
+
let effectSlurSegmentKind = null;
|
|
6415
6466
|
if (this.isHammerPullOrigin && this.hammerPullDestination) {
|
|
6416
6467
|
effectSlurDestination = this.hammerPullDestination;
|
|
6468
|
+
effectSlurSegmentKind = SlurSegmentKind.HammerPull;
|
|
6417
6469
|
}
|
|
6418
6470
|
else if (this.slideOutType === SlideOutType.Legato && this.slideTarget) {
|
|
6419
6471
|
effectSlurDestination = this.slideTarget;
|
|
6472
|
+
effectSlurSegmentKind = SlurSegmentKind.LegatoSlide;
|
|
6420
6473
|
}
|
|
6421
6474
|
if (effectSlurDestination) {
|
|
6422
6475
|
this.hasEffectSlur = true;
|
|
6423
6476
|
if (this.effectSlurOrigin && this.beat.pickStroke === PickStroke.None) {
|
|
6424
|
-
this.effectSlurOrigin
|
|
6425
|
-
|
|
6477
|
+
const chainOrigin = this.effectSlurOrigin;
|
|
6478
|
+
chainOrigin.effectSlurDestination = effectSlurDestination;
|
|
6479
|
+
effectSlurDestination.effectSlurOrigin = chainOrigin;
|
|
6426
6480
|
this.effectSlurOrigin = null;
|
|
6481
|
+
if (effectSlurSegmentKind !== null && chainOrigin.effectSlur !== null) {
|
|
6482
|
+
chainOrigin.effectSlur.destinationNote = effectSlurDestination;
|
|
6483
|
+
chainOrigin.effectSlur.segments.push({
|
|
6484
|
+
fromNote: this,
|
|
6485
|
+
toNote: effectSlurDestination,
|
|
6486
|
+
kind: effectSlurSegmentKind,
|
|
6487
|
+
text: null
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6427
6490
|
}
|
|
6428
6491
|
else {
|
|
6429
6492
|
this.isEffectSlurOrigin = true;
|
|
6430
6493
|
this.effectSlurDestination = effectSlurDestination;
|
|
6431
|
-
|
|
6494
|
+
effectSlurDestination.effectSlurOrigin = this;
|
|
6495
|
+
// Always allocate a fresh Slur — finish() may run twice (worker re-finish);
|
|
6496
|
+
// overwriting unconditionally keeps the derivation idempotent.
|
|
6497
|
+
const slur = new Slur();
|
|
6498
|
+
slur.originNote = this;
|
|
6499
|
+
slur.destinationNote = effectSlurDestination;
|
|
6500
|
+
if (effectSlurSegmentKind !== null) {
|
|
6501
|
+
slur.segments.push({
|
|
6502
|
+
fromNote: this,
|
|
6503
|
+
toNote: effectSlurDestination,
|
|
6504
|
+
kind: effectSlurSegmentKind,
|
|
6505
|
+
text: null
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
this.effectSlur = slur;
|
|
6432
6509
|
}
|
|
6433
6510
|
}
|
|
6434
6511
|
// try to detect what kind of bend was used and cleans unneeded points if required
|
|
@@ -7678,6 +7755,23 @@ class Beat {
|
|
|
7678
7755
|
* @json_ignore
|
|
7679
7756
|
*/
|
|
7680
7757
|
effectSlurDestination = null;
|
|
7758
|
+
/**
|
|
7759
|
+
* Convenience accessor for the {@link Slur} of this beat. Returns
|
|
7760
|
+
* the effect slur of whichever note in this beat owns it (the
|
|
7761
|
+
* chain-origin note populated during `Note.finish()`), or `null`
|
|
7762
|
+
* when no note in the beat is an effect-slur origin.
|
|
7763
|
+
* @clone_ignore
|
|
7764
|
+
* @json_ignore
|
|
7765
|
+
* @internal
|
|
7766
|
+
*/
|
|
7767
|
+
get effectSlur() {
|
|
7768
|
+
for (const n of this.notes) {
|
|
7769
|
+
if (n.effectSlur !== null) {
|
|
7770
|
+
return n.effectSlur;
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
return null;
|
|
7774
|
+
}
|
|
7681
7775
|
/**
|
|
7682
7776
|
* Gets or sets how the beaming should be done for this beat.
|
|
7683
7777
|
*/
|
|
@@ -31942,7 +32036,9 @@ class RenderingResources {
|
|
|
31942
32036
|
[NotationElement.RepeatCount, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31943
32037
|
[NotationElement.BarNumber, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31944
32038
|
[NotationElement.ScoreBendSlur, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31945
|
-
[NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)]
|
|
32039
|
+
[NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)],
|
|
32040
|
+
[NotationElement.EffectHammerOnPullOffText, RenderingResources._effectFont],
|
|
32041
|
+
[NotationElement.EffectSlideText, RenderingResources._effectFont]
|
|
31946
32042
|
]);
|
|
31947
32043
|
/**
|
|
31948
32044
|
* The name of the SMuFL Font to use for rendering music symbols.
|
|
@@ -32233,9 +32329,16 @@ class RenderingResources {
|
|
|
32233
32329
|
notationElement = NotationElement.ScoreWords;
|
|
32234
32330
|
break;
|
|
32235
32331
|
}
|
|
32332
|
+
return this.getFontForNotationElement(notationElement);
|
|
32333
|
+
}
|
|
32334
|
+
/**
|
|
32335
|
+
* @internal
|
|
32336
|
+
* @param element
|
|
32337
|
+
*/
|
|
32338
|
+
getFontForNotationElement(notationElement) {
|
|
32236
32339
|
return this.elementFonts.has(notationElement)
|
|
32237
32340
|
? this.elementFonts.get(notationElement)
|
|
32238
|
-
: RenderingResources.defaultFonts.get(
|
|
32341
|
+
: RenderingResources.defaultFonts.get(notationElement);
|
|
32239
32342
|
}
|
|
32240
32343
|
}
|
|
32241
32344
|
|
|
@@ -32628,9 +32731,16 @@ class DisplaySettings {
|
|
|
32628
32731
|
*
|
|
32629
32732
|
* The page layout does not use `displayWidth`. The use of absolute widths would break the proper alignments needed for this kind of display.
|
|
32630
32733
|
*
|
|
32631
|
-
*
|
|
32632
|
-
*
|
|
32633
|
-
*
|
|
32734
|
+
* In both modes, prefix and postfix glyphs (clef, key signature, time signature, barlines) are treated as fixed overhead: they keep their
|
|
32735
|
+
* natural size and the remaining staff width is distributed across bars by a per-bar weight. This matches the convention used by
|
|
32736
|
+
* Guitar Pro, Dorico, Finale, Sibelius and MuseScore. Bars that carry a system-start prefix or a mid-line clef/key/time-signature change
|
|
32737
|
+
* are therefore visibly wider than plain bars with the same weight. The weight source depends on the mode:
|
|
32738
|
+
*
|
|
32739
|
+
* * `Automatic` (default for `page` layout): weights come from the built-in spacing engine (the natural content width of each bar).
|
|
32740
|
+
* `displayScale` on the model is ignored.
|
|
32741
|
+
* * `UseModelLayout` (and the `parchment` layout): weights come from `bar.displayScale` / `masterBar.displayScale`. An unset
|
|
32742
|
+
* `displayScale` defaults to `1` and behaves identically to an explicit `1`, matching Guitar Pro (which omits the value when the
|
|
32743
|
+
* author hasn't customized it).
|
|
32634
32744
|
*
|
|
32635
32745
|
* ### Horizontal Layout
|
|
32636
32746
|
*
|
|
@@ -38829,14 +38939,25 @@ class StaffSystemBounds {
|
|
|
38829
38939
|
*/
|
|
38830
38940
|
boundsLookup;
|
|
38831
38941
|
/**
|
|
38832
|
-
*
|
|
38942
|
+
* Whether this system's bounds have already been scaled via `finish`. Prevents double-scaling
|
|
38943
|
+
* when the parent `BoundsLookup` is preserved across partial renders and `finish` is invoked
|
|
38944
|
+
* again on a mix of already-scaled (preserved) and newly-registered (natural-coordinate) systems.
|
|
38945
|
+
*/
|
|
38946
|
+
isFinished = false;
|
|
38947
|
+
/**
|
|
38948
|
+
* Finished the lookup for optimized access. Idempotent: once finished, further calls are no-ops
|
|
38949
|
+
* so preserved systems survive partial renders without being re-scaled.
|
|
38833
38950
|
*/
|
|
38834
38951
|
finish(scale = 1) {
|
|
38952
|
+
if (this.isFinished) {
|
|
38953
|
+
return;
|
|
38954
|
+
}
|
|
38835
38955
|
this.realBounds.scaleWith(scale);
|
|
38836
38956
|
this.visualBounds.scaleWith(scale);
|
|
38837
38957
|
for (const t of this.bars) {
|
|
38838
38958
|
t.finish(scale);
|
|
38839
38959
|
}
|
|
38960
|
+
this.isFinished = true;
|
|
38840
38961
|
}
|
|
38841
38962
|
/**
|
|
38842
38963
|
* Adds a new master bar to this lookup.
|
|
@@ -39009,6 +39130,58 @@ class BoundsLookup {
|
|
|
39009
39130
|
}
|
|
39010
39131
|
this.isFinished = true;
|
|
39011
39132
|
}
|
|
39133
|
+
/**
|
|
39134
|
+
* Re-opens the lookup for registrations without discarding previously registered bounds.
|
|
39135
|
+
* Used by the renderer when it preserves this lookup across a partial render so that new
|
|
39136
|
+
* bounds for the re-layouted range can be added while preserved systems stay intact.
|
|
39137
|
+
* @internal
|
|
39138
|
+
*/
|
|
39139
|
+
resetForPartialUpdate() {
|
|
39140
|
+
this.isFinished = false;
|
|
39141
|
+
}
|
|
39142
|
+
/**
|
|
39143
|
+
* Removes all entries belonging to the given master bar index and any bars after it.
|
|
39144
|
+
* Used before a partial render re-registers bounds for the re-layouted range, so the
|
|
39145
|
+
* preserved lookup ends up with only the unchanged entries when registration begins.
|
|
39146
|
+
*
|
|
39147
|
+
* Assumes the layout aligns its re-layouted range to system boundaries - i.e. the first
|
|
39148
|
+
* system to clear starts exactly at `masterBarIndex`. Caller is responsible for passing
|
|
39149
|
+
* the first master-bar-index of the first re-layouted system.
|
|
39150
|
+
* @internal
|
|
39151
|
+
*/
|
|
39152
|
+
clearFromMasterBar(masterBarIndex) {
|
|
39153
|
+
// drop staff systems whose bars start at or after the cleared range.
|
|
39154
|
+
let firstRemovedSystem = -1;
|
|
39155
|
+
for (let i = 0; i < this.staffSystems.length; i++) {
|
|
39156
|
+
const systemBars = this.staffSystems[i].bars;
|
|
39157
|
+
if (systemBars.length > 0 && systemBars[0].index >= masterBarIndex) {
|
|
39158
|
+
firstRemovedSystem = i;
|
|
39159
|
+
break;
|
|
39160
|
+
}
|
|
39161
|
+
}
|
|
39162
|
+
if (firstRemovedSystem !== -1) {
|
|
39163
|
+
this.staffSystems.splice(firstRemovedSystem, this.staffSystems.length - firstRemovedSystem);
|
|
39164
|
+
}
|
|
39165
|
+
// drop master bar entries at or beyond the cleared range.
|
|
39166
|
+
for (const key of Array.from(this._masterBarLookup.keys())) {
|
|
39167
|
+
if (key >= masterBarIndex) {
|
|
39168
|
+
this._masterBarLookup.delete(key);
|
|
39169
|
+
}
|
|
39170
|
+
}
|
|
39171
|
+
// drop beat entries whose beats belong to cleared bars.
|
|
39172
|
+
for (const key of Array.from(this._beatLookup.keys())) {
|
|
39173
|
+
const list = this._beatLookup.get(key);
|
|
39174
|
+
const filtered = list.filter(b => b.beat.voice.bar.index < masterBarIndex);
|
|
39175
|
+
if (filtered.length === 0) {
|
|
39176
|
+
this._beatLookup.delete(key);
|
|
39177
|
+
}
|
|
39178
|
+
else if (filtered.length !== list.length) {
|
|
39179
|
+
this._beatLookup.set(key, filtered);
|
|
39180
|
+
}
|
|
39181
|
+
}
|
|
39182
|
+
// drop the in-progress pointer - the next addStaffSystem call will replace it.
|
|
39183
|
+
this._currentStaffSystem = null;
|
|
39184
|
+
}
|
|
39012
39185
|
/**
|
|
39013
39186
|
* Adds a new staff sytem to the lookup.
|
|
39014
39187
|
* @param bounds The staff system bounds to add.
|
|
@@ -46840,7 +47013,15 @@ class ScoreRenderer {
|
|
|
46840
47013
|
Logger.warning('Rendering', 'AlphaTab skipped rendering because of width=0 (element invisible)', null);
|
|
46841
47014
|
return;
|
|
46842
47015
|
}
|
|
46843
|
-
|
|
47016
|
+
// For partial renders we preserve the existing lookup so bars outside the re-layouted
|
|
47017
|
+
// range keep their already-scaled bounds - the layout will clear the changed range
|
|
47018
|
+
// before the paint pass re-registers fresh entries for it.
|
|
47019
|
+
if (renderHints?.firstChangedMasterBar !== undefined && this.boundsLookup) {
|
|
47020
|
+
this.boundsLookup.resetForPartialUpdate();
|
|
47021
|
+
}
|
|
47022
|
+
else {
|
|
47023
|
+
this.boundsLookup = new BoundsLookup();
|
|
47024
|
+
}
|
|
46844
47025
|
this._recreateCanvas();
|
|
46845
47026
|
this.canvas.lineWidth = 1;
|
|
46846
47027
|
this.canvas.settings = this.settings;
|
|
@@ -48714,9 +48895,6 @@ class BeatTickLookup {
|
|
|
48714
48895
|
* @param beat The beat to add.
|
|
48715
48896
|
*/
|
|
48716
48897
|
highlightBeat(beat, playbackStart) {
|
|
48717
|
-
if (beat.isEmpty && !beat.voice.isEmpty) {
|
|
48718
|
-
return;
|
|
48719
|
-
}
|
|
48720
48898
|
if (!this._highlightedBeats.has(beat.id)) {
|
|
48721
48899
|
this._highlightedBeats.set(beat.id, true);
|
|
48722
48900
|
this.highlightedBeats.push(new BeatTickLookupItem(beat, playbackStart));
|
|
@@ -49086,6 +49264,21 @@ class MasterBarTickLookup {
|
|
|
49086
49264
|
}
|
|
49087
49265
|
}
|
|
49088
49266
|
|
|
49267
|
+
/**
|
|
49268
|
+
* Represents a range of the song that should be played.
|
|
49269
|
+
* @public
|
|
49270
|
+
*/
|
|
49271
|
+
class PlaybackRange {
|
|
49272
|
+
/**
|
|
49273
|
+
* The position in midi ticks from where the song should start.
|
|
49274
|
+
*/
|
|
49275
|
+
startTick = 0;
|
|
49276
|
+
/**
|
|
49277
|
+
* The position in midi ticks to where the song should be played.
|
|
49278
|
+
*/
|
|
49279
|
+
endTick = 0;
|
|
49280
|
+
}
|
|
49281
|
+
|
|
49089
49282
|
/**
|
|
49090
49283
|
* Describes how a cursor should be moving.
|
|
49091
49284
|
* @public
|
|
@@ -49246,6 +49439,13 @@ class MidiTickLookup {
|
|
|
49246
49439
|
* @internal
|
|
49247
49440
|
*/
|
|
49248
49441
|
masterBarLookup = new Map();
|
|
49442
|
+
/**
|
|
49443
|
+
* A dictionary of all beat played. The index is the id to {@link Beat.id}.
|
|
49444
|
+
* The value is the bar relative tick time at which the beat was registered during midi generation.
|
|
49445
|
+
* This lookup only contains the first time a Beat is played.
|
|
49446
|
+
* @internal
|
|
49447
|
+
*/
|
|
49448
|
+
beatLookup = new Map();
|
|
49249
49449
|
/**
|
|
49250
49450
|
* A list of all {@link MasterBarTickLookup} sorted by time.
|
|
49251
49451
|
*/
|
|
@@ -49612,10 +49812,22 @@ class MidiTickLookup {
|
|
|
49612
49812
|
* @returns The time in midi ticks at which the beat is played the first time or 0 if the beat is not contained
|
|
49613
49813
|
*/
|
|
49614
49814
|
getBeatStart(beat) {
|
|
49615
|
-
if (!this.masterBarLookup.has(beat.voice.bar.index)) {
|
|
49815
|
+
if (!this.masterBarLookup.has(beat.voice.bar.index) || !this.beatLookup.has(beat.id)) {
|
|
49616
49816
|
return 0;
|
|
49617
49817
|
}
|
|
49618
|
-
|
|
49818
|
+
const mb = this.masterBarLookup.get(beat.voice.bar.index);
|
|
49819
|
+
return mb.start + this.beatLookup.get(beat.id).startTick;
|
|
49820
|
+
}
|
|
49821
|
+
/**
|
|
49822
|
+
* Gets the playback range in midi ticks for a given beat.
|
|
49823
|
+
* @param beat The beat to find the time period for.
|
|
49824
|
+
* @returns The relative playback range within the parent masterbar at which the beat start and ends playing
|
|
49825
|
+
*/
|
|
49826
|
+
getRelativeBeatPlaybackRange(beat) {
|
|
49827
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49828
|
+
return undefined;
|
|
49829
|
+
}
|
|
49830
|
+
return this.beatLookup.get(beat.id);
|
|
49619
49831
|
}
|
|
49620
49832
|
/**
|
|
49621
49833
|
* Adds a new {@link MasterBarTickLookup} to the lookup table.
|
|
@@ -49633,6 +49845,12 @@ class MidiTickLookup {
|
|
|
49633
49845
|
}
|
|
49634
49846
|
}
|
|
49635
49847
|
addBeat(beat, start, duration) {
|
|
49848
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49849
|
+
const playbackRange = new PlaybackRange();
|
|
49850
|
+
playbackRange.startTick = start;
|
|
49851
|
+
playbackRange.endTick = start + duration;
|
|
49852
|
+
this.beatLookup.set(beat.id, playbackRange);
|
|
49853
|
+
}
|
|
49636
49854
|
const currentMasterBar = this._currentMasterBar;
|
|
49637
49855
|
if (currentMasterBar) {
|
|
49638
49856
|
// pre-beat grace notes at the start of the bar we also add the beat to the previous bar
|
|
@@ -50184,7 +50402,13 @@ class MidiFileGenerator {
|
|
|
50184
50402
|
let beatStart = beat.playbackStart;
|
|
50185
50403
|
let audioDuration = beat.playbackDuration;
|
|
50186
50404
|
const masterBarDuration = beat.voice.bar.masterBar.calculateDuration();
|
|
50187
|
-
|
|
50405
|
+
// For a bar whose voice contains a single empty beat (the typical "whole-bar rest"
|
|
50406
|
+
// placeholder inserted during score.finish), extend the beat's audio duration to cover
|
|
50407
|
+
// the full bar so cursor navigation has a beat to follow across the whole bar. Don't
|
|
50408
|
+
// apply this when the voice has multiple beats: those represent explicit rhythmic
|
|
50409
|
+
// subdivisions even when each beat is empty (e.g. a recording grid of placeholder
|
|
50410
|
+
// slots), and overriding would make every beat overlap the whole bar.
|
|
50411
|
+
if (beat.voice.bar.isEmpty && beat.voice.beats.length === 1) {
|
|
50188
50412
|
audioDuration = masterBarDuration;
|
|
50189
50413
|
}
|
|
50190
50414
|
else if (beat.voice.bar.masterBar.tripletFeel !== TripletFeel.NoTripletFeel &&
|
|
@@ -52210,21 +52434,6 @@ class ActiveBeatsChangedEventArgs {
|
|
|
52210
52434
|
}
|
|
52211
52435
|
}
|
|
52212
52436
|
|
|
52213
|
-
/**
|
|
52214
|
-
* Represents a range of the song that should be played.
|
|
52215
|
-
* @public
|
|
52216
|
-
*/
|
|
52217
|
-
class PlaybackRange {
|
|
52218
|
-
/**
|
|
52219
|
-
* The position in midi ticks from where the song should start.
|
|
52220
|
-
*/
|
|
52221
|
-
startTick = 0;
|
|
52222
|
-
/**
|
|
52223
|
-
* The position in midi ticks to where the song should be played.
|
|
52224
|
-
*/
|
|
52225
|
-
endTick = 0;
|
|
52226
|
-
}
|
|
52227
|
-
|
|
52228
52437
|
/**
|
|
52229
52438
|
* A {@link IAlphaSynth} implementation wrapping and underling other {@link IAlphaSynth}
|
|
52230
52439
|
* allowing dynamic changing of the underlying instance without loosing aspects like the
|
|
@@ -54805,7 +55014,8 @@ class AlphaTabApiBase {
|
|
|
54805
55014
|
this._isInitialBeatCursorUpdate ||
|
|
54806
55015
|
barBounds.y !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.y ||
|
|
54807
55016
|
startBeatX < previousBeatBounds.onNotesX ||
|
|
54808
|
-
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1
|
|
55017
|
+
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1 ||
|
|
55018
|
+
barBounds.h !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.h;
|
|
54809
55019
|
if (jumpCursor) {
|
|
54810
55020
|
cursorHandler.placeBeatCursor(beatCursor, beatBoundings, startBeatX);
|
|
54811
55021
|
}
|
|
@@ -55424,23 +55634,24 @@ class AlphaTabApiBase {
|
|
|
55424
55634
|
if (this._selectionStart && this._tickCache) {
|
|
55425
55635
|
// get the start and stop ticks (which consider properly repeats)
|
|
55426
55636
|
const tickCache = this._tickCache;
|
|
55427
|
-
const
|
|
55637
|
+
const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
|
|
55638
|
+
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat);
|
|
55639
|
+
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? this._selectionStart.beat.playbackStart;
|
|
55428
55640
|
// move to selection start
|
|
55429
55641
|
this._currentBeat = null; // reset current beat so it is updating the cursor
|
|
55430
55642
|
if (this._player.state === PlayerState.Paused) {
|
|
55431
|
-
this._cursorUpdateTick(
|
|
55643
|
+
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
|
|
55432
55644
|
}
|
|
55433
|
-
this.tickPosition =
|
|
55645
|
+
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55434
55646
|
// set playback range
|
|
55435
55647
|
if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
|
|
55436
|
-
const
|
|
55648
|
+
const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
|
|
55649
|
+
const endBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat);
|
|
55650
|
+
const endBeatPlaybackEnd = endBeatPlaybackRange?.endTick ??
|
|
55651
|
+
this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
|
|
55437
55652
|
const range = new PlaybackRange();
|
|
55438
|
-
range.startTick =
|
|
55439
|
-
range.endTick =
|
|
55440
|
-
realMasterBarEnd +
|
|
55441
|
-
this._selectionEnd.beat.playbackStart +
|
|
55442
|
-
this._selectionEnd.beat.playbackDuration -
|
|
55443
|
-
50;
|
|
55653
|
+
range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55654
|
+
range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
|
|
55444
55655
|
this.playbackRange = range;
|
|
55445
55656
|
}
|
|
55446
55657
|
else {
|
|
@@ -58045,8 +58256,9 @@ class AlphaTabWebWorker {
|
|
|
58045
58256
|
break;
|
|
58046
58257
|
case 'alphaTab.renderScore':
|
|
58047
58258
|
this._updateFontSizes(data.fontSizes);
|
|
58259
|
+
const renderHints = data.renderHints;
|
|
58048
58260
|
const score = data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
|
|
58049
|
-
this._renderMultiple(score, data.trackIndexes);
|
|
58261
|
+
this._renderMultiple(score, data.trackIndexes, renderHints);
|
|
58050
58262
|
break;
|
|
58051
58263
|
case 'alphaTab.updateSettings':
|
|
58052
58264
|
this._updateSettings(data.settings);
|
|
@@ -61486,6 +61698,12 @@ class TieGlyph extends Glyph {
|
|
|
61486
61698
|
_tieHeight = 0;
|
|
61487
61699
|
_boundingBox;
|
|
61488
61700
|
_shouldPaint = false;
|
|
61701
|
+
// Resolved per-label paint state. Lazily grown; re-layouts mutate
|
|
61702
|
+
// existing entries in place and update `_resolvedLabelCount` to
|
|
61703
|
+
// signal how many of them are valid this pass.
|
|
61704
|
+
_resolvedLabels = [];
|
|
61705
|
+
_resolvedLabelCount = 0;
|
|
61706
|
+
_labelBaselineOffset = 0;
|
|
61489
61707
|
get checkForOverflow() {
|
|
61490
61708
|
return this._shouldPaint && this._boundingBox !== undefined;
|
|
61491
61709
|
}
|
|
@@ -61555,16 +61773,88 @@ class TieGlyph extends Glyph {
|
|
|
61555
61773
|
}
|
|
61556
61774
|
this._boundingBox = undefined;
|
|
61557
61775
|
this.y = Math.min(this._startY, this._endY);
|
|
61776
|
+
const down = this.tieDirection === BeamDirection.Down;
|
|
61558
61777
|
let tieBoundingBox;
|
|
61778
|
+
// Bezier control points for the tie. Computed once and reused
|
|
61779
|
+
// for both the bounding box (via _calculateActualTieHeightFromCps)
|
|
61780
|
+
// and label-apex sampling further below — avoids a redundant
|
|
61781
|
+
// call to _computeBezierControlPoints (and its 14-element array
|
|
61782
|
+
// allocation) per labeled slur per layout.
|
|
61783
|
+
let cps = [];
|
|
61559
61784
|
if (this.shouldDrawBendSlur()) {
|
|
61560
61785
|
this._tieHeight = 0;
|
|
61561
|
-
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY,
|
|
61786
|
+
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY, down, this.renderer.smuflMetrics.tieHeight);
|
|
61562
61787
|
}
|
|
61563
61788
|
else {
|
|
61564
61789
|
this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
|
|
61565
|
-
|
|
61790
|
+
const tieThickness = this.renderer.smuflMetrics.tieMidpointThickness;
|
|
61791
|
+
cps = TieGlyph._computeBezierControlPoints(1, this._startX, this._startY, this._endX, this._endY, down, this._tieHeight, tieThickness);
|
|
61792
|
+
tieBoundingBox = TieGlyph._calculateActualTieHeightFromCps(cps, this._startX, this._startY, this._endX, this._endY, down, tieThickness);
|
|
61566
61793
|
}
|
|
61567
61794
|
this._boundingBox = tieBoundingBox;
|
|
61795
|
+
this._resolvedLabelCount = 0;
|
|
61796
|
+
const labels = this.getSlurLabels();
|
|
61797
|
+
if (labels !== null && labels.length > 0 && this.shouldPaintLabels()) {
|
|
61798
|
+
const res = this.renderer.settings.display.resources;
|
|
61799
|
+
const padding = this.renderer.smuflMetrics.oneStaffSpace * 0.25;
|
|
61800
|
+
let maxTextHeight = 0;
|
|
61801
|
+
// Single Y line for all labels — the outer arc apex.
|
|
61802
|
+
// Painted offset adds `padding` on the outward side, so
|
|
61803
|
+
// every label sits the same fixed distance from its arc.
|
|
61804
|
+
const labelLineY = cps.length > 0
|
|
61805
|
+
? 0.125 * cps[7] + 0.375 * cps[9] + 0.375 * cps[11] + 0.125 * cps[13]
|
|
61806
|
+
: (this._startY + this._endY) / 2;
|
|
61807
|
+
for (const label of labels) {
|
|
61808
|
+
const fromX = this.resolveLabelAnchorX(label.fromNote);
|
|
61809
|
+
const toX = this.resolveLabelAnchorX(label.toNote);
|
|
61810
|
+
if (fromX === null || toX === null) {
|
|
61811
|
+
continue;
|
|
61812
|
+
}
|
|
61813
|
+
const midX = (fromX + toX) / 2;
|
|
61814
|
+
if (midX < this._startX || midX > this._endX) {
|
|
61815
|
+
continue;
|
|
61816
|
+
}
|
|
61817
|
+
// Per-element font.size as an upper bound on glyph
|
|
61818
|
+
// height — avoids per-label measureText calls. All H/P
|
|
61819
|
+
// and sl. labels use the same _effectFont, so this is
|
|
61820
|
+
// typically computed once.
|
|
61821
|
+
const font = res.getFontForNotationElement(label.element);
|
|
61822
|
+
if (font.size > maxTextHeight) {
|
|
61823
|
+
maxTextHeight = font.size;
|
|
61824
|
+
}
|
|
61825
|
+
// grow cache lazily; mutate existing slot in place otherwise
|
|
61826
|
+
let slot;
|
|
61827
|
+
if (this._resolvedLabelCount < this._resolvedLabels.length) {
|
|
61828
|
+
slot = this._resolvedLabels[this._resolvedLabelCount];
|
|
61829
|
+
slot.x = midX;
|
|
61830
|
+
slot.y = labelLineY;
|
|
61831
|
+
slot.text = label.text;
|
|
61832
|
+
slot.element = label.element;
|
|
61833
|
+
}
|
|
61834
|
+
else {
|
|
61835
|
+
slot = {
|
|
61836
|
+
x: midX,
|
|
61837
|
+
y: labelLineY,
|
|
61838
|
+
text: label.text,
|
|
61839
|
+
element: label.element
|
|
61840
|
+
};
|
|
61841
|
+
this._resolvedLabels.push(slot);
|
|
61842
|
+
}
|
|
61843
|
+
this._resolvedLabelCount++;
|
|
61844
|
+
}
|
|
61845
|
+
if (this._resolvedLabelCount > 0) {
|
|
61846
|
+
// canvas.textBaseline is 'hanging' (TextBaseline.Top), so
|
|
61847
|
+
// fillText positions `y` at the glyph's top edge.
|
|
61848
|
+
if (this.tieDirection === BeamDirection.Up) {
|
|
61849
|
+
tieBoundingBox.y -= maxTextHeight + padding;
|
|
61850
|
+
this._labelBaselineOffset = -(maxTextHeight + padding);
|
|
61851
|
+
}
|
|
61852
|
+
else {
|
|
61853
|
+
this._labelBaselineOffset = padding;
|
|
61854
|
+
}
|
|
61855
|
+
tieBoundingBox.h += maxTextHeight + padding;
|
|
61856
|
+
}
|
|
61857
|
+
}
|
|
61568
61858
|
this.height = tieBoundingBox.h;
|
|
61569
61859
|
if (this.tieDirection === BeamDirection.Up) {
|
|
61570
61860
|
// the tie might go above `this.y` due to its shape
|
|
@@ -61580,13 +61870,77 @@ class TieGlyph extends Glyph {
|
|
|
61580
61870
|
if (!this._shouldPaint) {
|
|
61581
61871
|
return;
|
|
61582
61872
|
}
|
|
61873
|
+
const isDown = this.tieDirection === BeamDirection.Down;
|
|
61583
61874
|
if (this.shouldDrawBendSlur()) {
|
|
61584
|
-
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61875
|
+
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this.renderer.smuflMetrics.tieHeight);
|
|
61585
61876
|
}
|
|
61586
61877
|
else {
|
|
61587
|
-
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61878
|
+
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
|
|
61879
|
+
}
|
|
61880
|
+
if (this._resolvedLabelCount > 0) {
|
|
61881
|
+
const ta = canvas.textAlign;
|
|
61882
|
+
const tb = canvas.textBaseline;
|
|
61883
|
+
canvas.textAlign = TextAlign.Center;
|
|
61884
|
+
canvas.textBaseline = TextBaseline.Top;
|
|
61885
|
+
const res = this.renderer.resources;
|
|
61886
|
+
let lastElement = -1;
|
|
61887
|
+
for (let i = 0; i < this._resolvedLabelCount; i++) {
|
|
61888
|
+
const label = this._resolvedLabels[i];
|
|
61889
|
+
if (label.element !== lastElement) {
|
|
61890
|
+
canvas.font = res.getFontForNotationElement(label.element);
|
|
61891
|
+
lastElement = label.element;
|
|
61892
|
+
}
|
|
61893
|
+
canvas.fillText(label.text, cx + label.x, cy + label.y + this._labelBaselineOffset);
|
|
61894
|
+
}
|
|
61895
|
+
canvas.textAlign = ta;
|
|
61896
|
+
canvas.textBaseline = tb;
|
|
61588
61897
|
}
|
|
61589
61898
|
}
|
|
61899
|
+
/**
|
|
61900
|
+
* Returns the labels to paint along this slur, or `null` when there
|
|
61901
|
+
* are none. Override in subclasses.
|
|
61902
|
+
*/
|
|
61903
|
+
getSlurLabels() {
|
|
61904
|
+
return null;
|
|
61905
|
+
}
|
|
61906
|
+
/**
|
|
61907
|
+
* Whether label painting is enabled. Defaults to `true`. Subclasses
|
|
61908
|
+
* may override to disable labels on the bend-slur path or other
|
|
61909
|
+
* special cases.
|
|
61910
|
+
*/
|
|
61911
|
+
shouldPaintLabels() {
|
|
61912
|
+
return !this.shouldDrawBendSlur();
|
|
61913
|
+
}
|
|
61914
|
+
/**
|
|
61915
|
+
* Looks up the absolute X coordinate of an anchor note. Reuses
|
|
61916
|
+
* the start/end bar renderers already resolved by the subclass
|
|
61917
|
+
* (NoteTieGlyph) when the note's bar matches — most labels live
|
|
61918
|
+
* in the slur's start or end bar, so this avoids the double Map
|
|
61919
|
+
* lookup in `getRendererForBar` per label per layout. Returns
|
|
61920
|
+
* `null` when the note's bar is not rendered on this glyph's
|
|
61921
|
+
* staff (cross-system case).
|
|
61922
|
+
*/
|
|
61923
|
+
resolveLabelAnchorX(note) {
|
|
61924
|
+
const bar = note.beat.voice.bar;
|
|
61925
|
+
let renderer = null;
|
|
61926
|
+
const start = this.lookupStartBeatRenderer();
|
|
61927
|
+
if (start !== null && start.bar === bar) {
|
|
61928
|
+
renderer = start;
|
|
61929
|
+
}
|
|
61930
|
+
else {
|
|
61931
|
+
const end = this.lookupEndBeatRenderer();
|
|
61932
|
+
if (end !== null && end.bar === bar) {
|
|
61933
|
+
renderer = end;
|
|
61934
|
+
}
|
|
61935
|
+
else {
|
|
61936
|
+
renderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, bar);
|
|
61937
|
+
}
|
|
61938
|
+
}
|
|
61939
|
+
if (renderer === null) {
|
|
61940
|
+
return null;
|
|
61941
|
+
}
|
|
61942
|
+
return renderer.x + renderer.getNoteX(note, NoteXPosition.Center);
|
|
61943
|
+
}
|
|
61590
61944
|
getTieHeight(_startX, _startY, _endX, _endY) {
|
|
61591
61945
|
return this.renderer.smuflMetrics.tieHeight;
|
|
61592
61946
|
}
|
|
@@ -61605,11 +61959,18 @@ class TieGlyph extends Glyph {
|
|
|
61605
61959
|
}
|
|
61606
61960
|
static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
|
|
61607
61961
|
const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
|
|
61962
|
+
return TieGlyph._calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size);
|
|
61963
|
+
}
|
|
61964
|
+
/**
|
|
61965
|
+
* Derives the bounding box for a tie from already-computed control
|
|
61966
|
+
* points. Splits the bbox math from cps generation so callers that
|
|
61967
|
+
* need BOTH cps and bbox (e.g. multi-label slur layout) avoid a
|
|
61968
|
+
* second call to `_computeBezierControlPoints`.
|
|
61969
|
+
*/
|
|
61970
|
+
static _calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size) {
|
|
61608
61971
|
if (cp.length === 0) {
|
|
61609
61972
|
return new Bounds(x1, y1, x2 - x1, y2 - y1);
|
|
61610
61973
|
}
|
|
61611
|
-
// For a musical tie/slur, the extrema occur predictably near the midpoint
|
|
61612
|
-
// Evaluate at midpoint (t=0.5) and check endpoints
|
|
61613
61974
|
const p0x = cp[0];
|
|
61614
61975
|
const p0y = cp[1];
|
|
61615
61976
|
const c1x = cp[2];
|
|
@@ -61618,15 +61979,12 @@ class TieGlyph extends Glyph {
|
|
|
61618
61979
|
const c2y = cp[5];
|
|
61619
61980
|
const p1x = cp[6];
|
|
61620
61981
|
const p1y = cp[7];
|
|
61621
|
-
// Evaluate at t=0.5 for midpoint
|
|
61622
61982
|
const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
|
|
61623
61983
|
const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
|
|
61624
|
-
// Bounds are simply min/max of start, end, and midpoint
|
|
61625
61984
|
const xMin = Math.min(p0x, p1x, midX);
|
|
61626
61985
|
const xMax = Math.max(p0x, p1x, midX);
|
|
61627
61986
|
let yMin = Math.min(p0y, p1y, midY);
|
|
61628
61987
|
let yMax = Math.max(p0y, p1y, midY);
|
|
61629
|
-
// Account for thickness of the tie/slur
|
|
61630
61988
|
if (down) {
|
|
61631
61989
|
yMax += size;
|
|
61632
61990
|
}
|
|
@@ -62919,6 +63277,15 @@ class BarRendererBase {
|
|
|
62919
63277
|
}
|
|
62920
63278
|
return false;
|
|
62921
63279
|
}
|
|
63280
|
+
/**
|
|
63281
|
+
* The fixed-overhead width of this renderer: glyphs that do not stretch when
|
|
63282
|
+
* the bar is scaled (clef, key signature, time signature, barlines, courtesy
|
|
63283
|
+
* accidentals, etc). Treated as a fixed allocation by the system-level layout
|
|
63284
|
+
* before distributing remaining width across bars by {@link Bar.displayScale}.
|
|
63285
|
+
*/
|
|
63286
|
+
get fixedOverhead() {
|
|
63287
|
+
return this._preBeatGlyphs.width + this._postBeatGlyphs.width;
|
|
63288
|
+
}
|
|
62922
63289
|
scaleToWidth(width) {
|
|
62923
63290
|
// preBeat and postBeat glyphs do not get resized
|
|
62924
63291
|
const containerWidth = width - this._preBeatGlyphs.width - this._postBeatGlyphs.width;
|
|
@@ -65477,6 +65844,22 @@ class BarLayoutingInfo {
|
|
|
65477
65844
|
postBeatSize = 0;
|
|
65478
65845
|
minStretchForce = 0;
|
|
65479
65846
|
totalSpringConstant = 0;
|
|
65847
|
+
/**
|
|
65848
|
+
* The smallest note duration encountered within this bar's springs, used as the reference in
|
|
65849
|
+
* the Gourlay stretch formula. Read by the owning {@link StaffSystem} so that the system can
|
|
65850
|
+
* aggregate a shared minimum across all bars and trigger a reconcile if an added bar introduces
|
|
65851
|
+
* a shorter duration than previously seen.
|
|
65852
|
+
*/
|
|
65853
|
+
get localMinDuration() {
|
|
65854
|
+
return this._minDuration;
|
|
65855
|
+
}
|
|
65856
|
+
/**
|
|
65857
|
+
* The minimum-duration reference against which the spring constants currently held by this info
|
|
65858
|
+
* were computed. Set by {@link finish} and {@link recomputeSpringConstants}. The owning
|
|
65859
|
+
* StaffSystem compares this against its system-wide minimum to decide whether spring constants
|
|
65860
|
+
* need re-derivation.
|
|
65861
|
+
*/
|
|
65862
|
+
computedWithMinDuration = 0;
|
|
65480
65863
|
_updateMinStretchForce(force) {
|
|
65481
65864
|
if (this.minStretchForce < force) {
|
|
65482
65865
|
this.minStretchForce = force;
|
|
@@ -65646,10 +66029,26 @@ class BarLayoutingInfo {
|
|
|
65646
66029
|
this._incompleteGraceRodsWidth += sp.preBeatWidth + sp.postSpringWidth;
|
|
65647
66030
|
}
|
|
65648
66031
|
}
|
|
65649
|
-
this._calculateSpringConstants();
|
|
66032
|
+
this._calculateSpringConstants(this._minDuration);
|
|
66033
|
+
this.computedWithMinDuration = this._minDuration;
|
|
65650
66034
|
this.version++;
|
|
65651
66035
|
}
|
|
65652
|
-
|
|
66036
|
+
/**
|
|
66037
|
+
* Re-derives the spring constants (and {@link minStretchForce} / {@link totalSpringConstant})
|
|
66038
|
+
* using a caller-supplied minimum-duration reference rather than this bar's local minimum.
|
|
66039
|
+
*
|
|
66040
|
+
* Called by {@link StaffSystem.reconcileMinDurationIfDirty} when a bar added later to the
|
|
66041
|
+
* system introduced a shorter note than previously seen, invalidating this bar's spring
|
|
66042
|
+
* constants. Grace-rod data is not recomputed — it is independent of the minimum-duration
|
|
66043
|
+
* reference. The internal {@link version} is bumped so downstream consumers (e.g.
|
|
66044
|
+
* {@link BarRendererBase.applyLayoutingInfo}) pick up the refreshed positions.
|
|
66045
|
+
*/
|
|
66046
|
+
recomputeSpringConstants(minDuration) {
|
|
66047
|
+
this._calculateSpringConstants(minDuration);
|
|
66048
|
+
this.computedWithMinDuration = minDuration;
|
|
66049
|
+
this.version++;
|
|
66050
|
+
}
|
|
66051
|
+
_calculateSpringConstants(minDuration) {
|
|
65653
66052
|
let totalSpringConstant = 0;
|
|
65654
66053
|
const sortedSprings = this._timeSortedSprings;
|
|
65655
66054
|
if (sortedSprings.length === 0) {
|
|
@@ -65667,7 +66066,7 @@ class BarLayoutingInfo {
|
|
|
65667
66066
|
const nextSpring = sortedSprings[i + 1];
|
|
65668
66067
|
duration = Math.abs(nextSpring.timePosition - currentSpring.timePosition);
|
|
65669
66068
|
}
|
|
65670
|
-
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration);
|
|
66069
|
+
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration, minDuration);
|
|
65671
66070
|
totalSpringConstant += 1 / currentSpring.springConstant;
|
|
65672
66071
|
}
|
|
65673
66072
|
this.totalSpringConstant = 1 / totalSpringConstant;
|
|
@@ -65727,7 +66126,7 @@ class BarLayoutingInfo {
|
|
|
65727
66126
|
// springX += this.calculateWidth(force, spring.springConstant);
|
|
65728
66127
|
// }
|
|
65729
66128
|
// }
|
|
65730
|
-
_calculateSpringConstant(spring, duration) {
|
|
66129
|
+
_calculateSpringConstant(spring, duration, minDuration) {
|
|
65731
66130
|
if (duration <= 0) {
|
|
65732
66131
|
duration = MidiUtils.toTicks(Duration.TwoHundredFiftySixth);
|
|
65733
66132
|
}
|
|
@@ -65735,7 +66134,6 @@ class BarLayoutingInfo {
|
|
|
65735
66134
|
spring.smallestDuration = duration;
|
|
65736
66135
|
}
|
|
65737
66136
|
const smallestDuration = spring.smallestDuration;
|
|
65738
|
-
const minDuration = this._minDuration;
|
|
65739
66137
|
const minDurationWidth = BarLayoutingInfo._defaultMinDurationWidth;
|
|
65740
66138
|
const phi = 1 + 0.85 * Math.log2(duration / minDuration);
|
|
65741
66139
|
return (smallestDuration / duration) * (1 / (phi * minDurationWidth));
|
|
@@ -65798,6 +66196,18 @@ class MasterBarsRenderers {
|
|
|
65798
66196
|
canWrap = true;
|
|
65799
66197
|
masterBar;
|
|
65800
66198
|
additionalMultiBarRestIndexes = null;
|
|
66199
|
+
/**
|
|
66200
|
+
* Max fixed overhead (prefix + postfix glyph width) across all staves of this bar.
|
|
66201
|
+
* Used by the layout-mode horizontal scaling pass to carve out the fixed-overhead bucket
|
|
66202
|
+
* before distributing staff width across bars.
|
|
66203
|
+
*/
|
|
66204
|
+
maxFixedOverhead = 0;
|
|
66205
|
+
/**
|
|
66206
|
+
* Max natural content width (computedWidth - fixedOverhead) across all staves of this bar.
|
|
66207
|
+
* Used as the bar weight when the layout ignores {@link MasterBar.displayScale} (e.g.
|
|
66208
|
+
* Page layout with `SystemsLayoutMode.Automatic`).
|
|
66209
|
+
*/
|
|
66210
|
+
maxContentWidth = 0;
|
|
65801
66211
|
get lastMasterBarIndex() {
|
|
65802
66212
|
if (this.additionalMultiBarRestIndexes) {
|
|
65803
66213
|
return this.additionalMultiBarRestIndexes[this.additionalMultiBarRestIndexes.length - 1];
|
|
@@ -65979,6 +66389,45 @@ class StaffSystem {
|
|
|
65979
66389
|
* This value is mainly used in the parchment style layout for correct scaling of the bars.
|
|
65980
66390
|
*/
|
|
65981
66391
|
totalBarDisplayScale = 0;
|
|
66392
|
+
/**
|
|
66393
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxFixedOverhead} across the system. The layout-mode
|
|
66394
|
+
* horizontal scaling pass subtracts this from the available staff width before distributing the
|
|
66395
|
+
* remainder across bars.
|
|
66396
|
+
*/
|
|
66397
|
+
totalFixedOverhead = 0;
|
|
66398
|
+
/**
|
|
66399
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxContentWidth} across the system. Used as the
|
|
66400
|
+
* denominator when distributing staff width in modes that weight bars by natural content width
|
|
66401
|
+
* (Page layout with `SystemsLayoutMode.Automatic`).
|
|
66402
|
+
*/
|
|
66403
|
+
totalContentWidth = 0;
|
|
66404
|
+
/**
|
|
66405
|
+
* Shortest note duration (in ticks) across every bar that has been added to this system, used
|
|
66406
|
+
* as the common reference in the Gourlay stretch formula so that rhythmically-equivalent beats
|
|
66407
|
+
* in different bars of the same system align column-wise.
|
|
66408
|
+
*
|
|
66409
|
+
* `-1` means "no bar added yet". The value only moves downward during system assembly; when a
|
|
66410
|
+
* new bar introduces a shorter minimum, {@link isMinDurationDirty} is set so that
|
|
66411
|
+
* {@link reconcileMinDurationIfDirty} can re-derive spring constants on the previously-added
|
|
66412
|
+
* bars before layout distribution runs.
|
|
66413
|
+
*/
|
|
66414
|
+
minDuration = -1;
|
|
66415
|
+
/**
|
|
66416
|
+
* Set when a bar added to this system introduced a shorter {@link minDuration} than previously
|
|
66417
|
+
* seen, leaving earlier bars' spring constants stale. Consumed by
|
|
66418
|
+
* {@link reconcileMinDurationIfDirty} which is called from `VerticalLayoutBase._fitSystem`
|
|
66419
|
+
* once the system is fully assembled.
|
|
66420
|
+
*/
|
|
66421
|
+
isMinDurationDirty = false;
|
|
66422
|
+
/**
|
|
66423
|
+
* Whether this system coordinates a shared minimum-duration reference across its bars for the
|
|
66424
|
+
* Gourlay stretch formula. Defaults to `true` for page-style and parchment layouts where bars
|
|
66425
|
+
* of a system fight for a common staff width. Set to `false` for horizontal layouts where each
|
|
66426
|
+
* bar is sized independently (by `bar.displayWidth` or its intrinsic width) and there is no
|
|
66427
|
+
* column-alignment concern - each bar keeps its local minimum so pre-existing rendering is
|
|
66428
|
+
* preserved.
|
|
66429
|
+
*/
|
|
66430
|
+
shareMinDurationAcrossBars = true;
|
|
65982
66431
|
isLast = false;
|
|
65983
66432
|
masterBarsRenderers = [];
|
|
65984
66433
|
staves = [];
|
|
@@ -66040,6 +66489,9 @@ class StaffSystem {
|
|
|
66040
66489
|
}
|
|
66041
66490
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66042
66491
|
this._calculateAccoladeSpacing(tracks);
|
|
66492
|
+
// On the resize path the layoutingInfo was finalized in a previous layout pass, so we
|
|
66493
|
+
// only need to check whether its min-duration reference still matches the new system's.
|
|
66494
|
+
this._trackSystemMinDuration(renderers.layoutingInfo);
|
|
66043
66495
|
this._applyLayoutAndUpdateWidth();
|
|
66044
66496
|
return renderers;
|
|
66045
66497
|
}
|
|
@@ -66095,10 +66547,89 @@ class StaffSystem {
|
|
|
66095
66547
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66096
66548
|
this._calculateAccoladeSpacing(tracks);
|
|
66097
66549
|
barLayoutingInfo.finish();
|
|
66550
|
+
// Reconcile against the system-wide minimum-duration reference now that springs are
|
|
66551
|
+
// finalized. If this bar introduced a shorter note, earlier bars become stale (flagged
|
|
66552
|
+
// for bulk reconcile at fit time). If the system already had a shorter min than this
|
|
66553
|
+
// bar's local one, this bar's spring constants are recomputed immediately so the width
|
|
66554
|
+
// we return below reflects the shared reference.
|
|
66555
|
+
this._trackSystemMinDuration(barLayoutingInfo);
|
|
66098
66556
|
// ensure same widths of new renderer
|
|
66099
66557
|
result.width = this._applyLayoutAndUpdateWidth();
|
|
66100
66558
|
return result;
|
|
66101
66559
|
}
|
|
66560
|
+
/**
|
|
66561
|
+
* Updates {@link minDuration} and {@link isMinDurationDirty} when a bar is added, and brings
|
|
66562
|
+
* the just-added bar's {@link BarLayoutingInfo} in line with the current system minimum if the
|
|
66563
|
+
* system already saw a shorter reference. The bulk reconcile over previously-added bars is
|
|
66564
|
+
* deferred to {@link reconcileMinDurationIfDirty} (called from `_fitSystem`) to avoid
|
|
66565
|
+
* re-iterating the system every time a bar is appended.
|
|
66566
|
+
*/
|
|
66567
|
+
_trackSystemMinDuration(info) {
|
|
66568
|
+
if (!this.shareMinDurationAcrossBars) {
|
|
66569
|
+
return;
|
|
66570
|
+
}
|
|
66571
|
+
const localMin = info.localMinDuration;
|
|
66572
|
+
if (this.minDuration === -1 || localMin < this.minDuration) {
|
|
66573
|
+
// this bar shortens the system minimum; earlier bars (if any) are now stale
|
|
66574
|
+
if (this.masterBarsRenderers.length > 1 && localMin !== this.minDuration) {
|
|
66575
|
+
this.isMinDurationDirty = true;
|
|
66576
|
+
}
|
|
66577
|
+
this.minDuration = localMin;
|
|
66578
|
+
}
|
|
66579
|
+
if (info.computedWithMinDuration > this.minDuration) {
|
|
66580
|
+
// this bar was initialized against a larger (local) min than the system carries; pull
|
|
66581
|
+
// it down to the system reference so its computedWidth reflects the shared spacing.
|
|
66582
|
+
info.recomputeSpringConstants(this.minDuration);
|
|
66583
|
+
}
|
|
66584
|
+
}
|
|
66585
|
+
/**
|
|
66586
|
+
* Re-derives spring constants on bars whose {@link BarLayoutingInfo.computedWithMinDuration}
|
|
66587
|
+
* is out of sync with the current {@link minDuration}, and rebuilds the cached system totals
|
|
66588
|
+
* (widths, {@link totalFixedOverhead}, {@link totalContentWidth}) from the refreshed bar
|
|
66589
|
+
* widths. Called from `VerticalLayoutBase._fitSystem` after the system is fully assembled and
|
|
66590
|
+
* before distribution runs. No-op when {@link isMinDurationDirty} is false.
|
|
66591
|
+
*/
|
|
66592
|
+
reconcileMinDurationIfDirty() {
|
|
66593
|
+
if (!this.isMinDurationDirty) {
|
|
66594
|
+
return;
|
|
66595
|
+
}
|
|
66596
|
+
let systemWidth = this.accoladeWidth;
|
|
66597
|
+
let totalFixedOverhead = 0;
|
|
66598
|
+
let totalContentWidth = 0;
|
|
66599
|
+
for (const mb of this.masterBarsRenderers) {
|
|
66600
|
+
if (mb.layoutingInfo.computedWithMinDuration > this.minDuration) {
|
|
66601
|
+
mb.layoutingInfo.recomputeSpringConstants(this.minDuration);
|
|
66602
|
+
}
|
|
66603
|
+
let maxPrefix = 0;
|
|
66604
|
+
let maxContent = 0;
|
|
66605
|
+
let realWidth = 0;
|
|
66606
|
+
for (const r of mb.renderers) {
|
|
66607
|
+
r.applyLayoutingInfo();
|
|
66608
|
+
if (r.computedWidth > realWidth) {
|
|
66609
|
+
realWidth = r.computedWidth;
|
|
66610
|
+
}
|
|
66611
|
+
const overhead = r.fixedOverhead;
|
|
66612
|
+
if (overhead > maxPrefix) {
|
|
66613
|
+
maxPrefix = overhead;
|
|
66614
|
+
}
|
|
66615
|
+
const content = Math.max(0, r.computedWidth - overhead);
|
|
66616
|
+
if (content > maxContent) {
|
|
66617
|
+
maxContent = content;
|
|
66618
|
+
}
|
|
66619
|
+
}
|
|
66620
|
+
mb.maxFixedOverhead = maxPrefix;
|
|
66621
|
+
mb.maxContentWidth = maxContent;
|
|
66622
|
+
mb.width = realWidth;
|
|
66623
|
+
systemWidth += realWidth;
|
|
66624
|
+
totalFixedOverhead += maxPrefix;
|
|
66625
|
+
totalContentWidth += maxContent;
|
|
66626
|
+
}
|
|
66627
|
+
this.width = systemWidth;
|
|
66628
|
+
this.computedWidth = systemWidth;
|
|
66629
|
+
this.totalFixedOverhead = totalFixedOverhead;
|
|
66630
|
+
this.totalContentWidth = totalContentWidth;
|
|
66631
|
+
this.isMinDurationDirty = false;
|
|
66632
|
+
}
|
|
66102
66633
|
getBarDisplayScale(renderer) {
|
|
66103
66634
|
return this.staves.length > 1 ? renderer.bar.masterBar.displayScale : renderer.bar.displayScale;
|
|
66104
66635
|
}
|
|
@@ -66137,12 +66668,16 @@ class StaffSystem {
|
|
|
66137
66668
|
this.width -= width;
|
|
66138
66669
|
this.computedWidth -= width;
|
|
66139
66670
|
this.totalBarDisplayScale -= barDisplayScale;
|
|
66671
|
+
this.totalFixedOverhead -= toRemove.maxFixedOverhead;
|
|
66672
|
+
this.totalContentWidth -= toRemove.maxContentWidth;
|
|
66140
66673
|
return toRemove;
|
|
66141
66674
|
}
|
|
66142
66675
|
return null;
|
|
66143
66676
|
}
|
|
66144
66677
|
_applyLayoutAndUpdateWidth() {
|
|
66145
66678
|
let realWidth = 0;
|
|
66679
|
+
let maxFixedOverhead = 0;
|
|
66680
|
+
let maxContentWidth = 0;
|
|
66146
66681
|
let barDisplayScale = 0;
|
|
66147
66682
|
for (const s of this.allStaves) {
|
|
66148
66683
|
const last = s.barRenderers[s.barRenderers.length - 1];
|
|
@@ -66151,8 +66686,21 @@ class StaffSystem {
|
|
|
66151
66686
|
if (last.computedWidth > realWidth) {
|
|
66152
66687
|
realWidth = last.computedWidth;
|
|
66153
66688
|
}
|
|
66689
|
+
const overhead = last.fixedOverhead;
|
|
66690
|
+
if (overhead > maxFixedOverhead) {
|
|
66691
|
+
maxFixedOverhead = overhead;
|
|
66692
|
+
}
|
|
66693
|
+
const content = Math.max(0, last.computedWidth - overhead);
|
|
66694
|
+
if (content > maxContentWidth) {
|
|
66695
|
+
maxContentWidth = content;
|
|
66696
|
+
}
|
|
66154
66697
|
}
|
|
66698
|
+
const renderers = this.masterBarsRenderers[this.masterBarsRenderers.length - 1];
|
|
66699
|
+
renderers.maxFixedOverhead = maxFixedOverhead;
|
|
66700
|
+
renderers.maxContentWidth = maxContentWidth;
|
|
66155
66701
|
this.totalBarDisplayScale += barDisplayScale;
|
|
66702
|
+
this.totalFixedOverhead += maxFixedOverhead;
|
|
66703
|
+
this.totalContentWidth += maxContentWidth;
|
|
66156
66704
|
this.width += realWidth;
|
|
66157
66705
|
this.computedWidth += realWidth;
|
|
66158
66706
|
return realWidth;
|
|
@@ -66650,17 +67198,6 @@ class StaffSystem {
|
|
|
66650
67198
|
}
|
|
66651
67199
|
}
|
|
66652
67200
|
|
|
66653
|
-
/**
|
|
66654
|
-
* @internal
|
|
66655
|
-
*/
|
|
66656
|
-
class LazyPartial {
|
|
66657
|
-
args;
|
|
66658
|
-
renderCallback;
|
|
66659
|
-
constructor(args, renderCallback) {
|
|
66660
|
-
this.args = args;
|
|
66661
|
-
this.renderCallback = renderCallback;
|
|
66662
|
-
}
|
|
66663
|
-
}
|
|
66664
67201
|
/**
|
|
66665
67202
|
* This is the base class for creating new layouting engines for the score renderer.
|
|
66666
67203
|
* @internal
|
|
@@ -66691,15 +67228,21 @@ class ScoreLayout {
|
|
|
66691
67228
|
this.doResize();
|
|
66692
67229
|
}
|
|
66693
67230
|
layoutAndRender(renderHints) {
|
|
66694
|
-
this._lazyPartials.clear();
|
|
66695
67231
|
this.slurRegistry.clear();
|
|
66696
|
-
this.beamingRuleLookups.clear();
|
|
66697
|
-
this._barRendererLookup.clear();
|
|
66698
|
-
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66699
67232
|
const score = this.renderer.score;
|
|
66700
67233
|
this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
|
|
66701
67234
|
this.lastBarIndex = ModelUtils.computeLastDisplayedBarIndex(score, this.renderer.settings, this.firstBarIndex);
|
|
66702
67235
|
this.multiBarRestInfo = ModelUtils.buildMultiBarRestInfo(this.renderer.tracks, this.firstBarIndex, this.lastBarIndex);
|
|
67236
|
+
const firstChangedMasterBar = renderHints?.firstChangedMasterBar;
|
|
67237
|
+
if (firstChangedMasterBar !== undefined) {
|
|
67238
|
+
if (this.doUpdateForBars(renderHints)) {
|
|
67239
|
+
return;
|
|
67240
|
+
}
|
|
67241
|
+
}
|
|
67242
|
+
this._lazyPartials.clear();
|
|
67243
|
+
this.beamingRuleLookups.clear();
|
|
67244
|
+
this._barRendererLookup.clear();
|
|
67245
|
+
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66703
67246
|
this.pagePadding = this.renderer.settings.display.padding.map(p => p / this.renderer.settings.display.scale);
|
|
66704
67247
|
if (!this.pagePadding) {
|
|
66705
67248
|
this.pagePadding = [0, 0, 0, 0];
|
|
@@ -66714,6 +67257,9 @@ class ScoreLayout {
|
|
|
66714
67257
|
this.doLayoutAndRender(renderHints);
|
|
66715
67258
|
}
|
|
66716
67259
|
_lazyPartials = new Map();
|
|
67260
|
+
getExistingPartialArgs(id) {
|
|
67261
|
+
return this._lazyPartials.has(id) ? this._lazyPartials.get(id).args : undefined;
|
|
67262
|
+
}
|
|
66717
67263
|
registerPartial(args, callback) {
|
|
66718
67264
|
if (args.height === 0) {
|
|
66719
67265
|
return;
|
|
@@ -66732,7 +67278,11 @@ class ScoreLayout {
|
|
|
66732
67278
|
}
|
|
66733
67279
|
else {
|
|
66734
67280
|
// in case of lazy loading -> first register lazy, then notify
|
|
66735
|
-
|
|
67281
|
+
const partial = {
|
|
67282
|
+
args,
|
|
67283
|
+
renderCallback: callback
|
|
67284
|
+
};
|
|
67285
|
+
this._lazyPartials.set(args.id, partial);
|
|
66736
67286
|
this.renderer.partialLayoutFinished.trigger(args);
|
|
66737
67287
|
}
|
|
66738
67288
|
}
|
|
@@ -67027,7 +67577,7 @@ class ScoreLayout {
|
|
|
67027
67577
|
glyph.textAlign = TextAlign.Left;
|
|
67028
67578
|
}
|
|
67029
67579
|
}
|
|
67030
|
-
|
|
67580
|
+
_layoutAndRenderAnnotation(y) {
|
|
67031
67581
|
// attention, you are not allowed to remove change this notice within any version of this library without permission!
|
|
67032
67582
|
const msg = 'rendered by alphaTab';
|
|
67033
67583
|
const resources = this.renderer.settings.display.resources;
|
|
@@ -67091,6 +67641,12 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67091
67641
|
}
|
|
67092
67642
|
doResize() {
|
|
67093
67643
|
}
|
|
67644
|
+
doUpdateForBars(_renderHints) {
|
|
67645
|
+
// not supported yet, modifications likely cause anyhow full updates
|
|
67646
|
+
// as we do not optimize effect bands yet. with effect bands being more
|
|
67647
|
+
// isolated in bars we could try updating dynamically
|
|
67648
|
+
return false;
|
|
67649
|
+
}
|
|
67094
67650
|
doLayoutAndRender(renderHints) {
|
|
67095
67651
|
const score = this.renderer.score;
|
|
67096
67652
|
let startIndex = this.renderer.settings.display.startBar;
|
|
@@ -67104,6 +67660,11 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67104
67660
|
endBarIndex = startIndex + endBarIndex - 1; // map count to array index
|
|
67105
67661
|
endBarIndex = Math.min(score.masterBars.length - 1, Math.max(0, endBarIndex));
|
|
67106
67662
|
this._system = this.createEmptyStaffSystem(0);
|
|
67663
|
+
// Each bar in horizontal layout is sized independently (by bar.displayWidth or the bar's
|
|
67664
|
+
// intrinsic width), so there is no shared staff width to distribute across bars. Keep each
|
|
67665
|
+
// bar's spring constants referenced against its own local minimum-duration so rendering
|
|
67666
|
+
// matches the historical per-bar behaviour.
|
|
67667
|
+
this._system.shareMinDurationAcrossBars = false;
|
|
67107
67668
|
this._system.isLast = true;
|
|
67108
67669
|
this._system.x = this.pagePadding[0];
|
|
67109
67670
|
this._system.y = this.pagePadding[1];
|
|
@@ -67165,7 +67726,7 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67165
67726
|
currentBarIndex += partial.masterBars.length;
|
|
67166
67727
|
}
|
|
67167
67728
|
this.height = this.layoutAndRenderBottomScoreInfo(this.height);
|
|
67168
|
-
this.height = this.
|
|
67729
|
+
this.height = this._layoutAndRenderAnnotation(this.height);
|
|
67169
67730
|
this.height += this.pagePadding[3];
|
|
67170
67731
|
this.height *= this.renderer.settings.display.scale;
|
|
67171
67732
|
}
|
|
@@ -67232,11 +67793,16 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67232
67793
|
_allMasterBarRenderers = [];
|
|
67233
67794
|
_barsFromPreviousSystem = [];
|
|
67234
67795
|
_reuseViewPort = false;
|
|
67796
|
+
_preSystemPartialIds = [];
|
|
67797
|
+
_systemPartialIds = [];
|
|
67235
67798
|
doLayoutAndRender(renderHints) {
|
|
67236
67799
|
let y = this.pagePadding[1];
|
|
67237
67800
|
this.width = this.renderer.width;
|
|
67238
67801
|
this._allMasterBarRenderers = [];
|
|
67802
|
+
this._preSystemPartialIds = [];
|
|
67803
|
+
this._systemPartialIds = [];
|
|
67239
67804
|
this._reuseViewPort = renderHints?.reuseViewport ?? false;
|
|
67805
|
+
this._systems = [];
|
|
67240
67806
|
//
|
|
67241
67807
|
// 1. Score Info
|
|
67242
67808
|
y = this._layoutAndRenderScoreInfo(y, -1);
|
|
@@ -67248,15 +67814,23 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67248
67814
|
y = this._layoutAndRenderChordDiagrams(y, -1);
|
|
67249
67815
|
//
|
|
67250
67816
|
// 4. One result per StaffSystem
|
|
67251
|
-
y = this._layoutAndRenderScore(y);
|
|
67817
|
+
y = this._layoutAndRenderScore(y, this.firstBarIndex);
|
|
67252
67818
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67253
|
-
y = this.
|
|
67819
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67254
67820
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67255
67821
|
}
|
|
67256
67822
|
registerPartial(args, callback) {
|
|
67257
67823
|
args.reuseViewport = this._reuseViewPort;
|
|
67258
67824
|
super.registerPartial(args, callback);
|
|
67259
67825
|
}
|
|
67826
|
+
reregisterPartial(id) {
|
|
67827
|
+
const args = this.getExistingPartialArgs(id);
|
|
67828
|
+
if (!args) {
|
|
67829
|
+
return;
|
|
67830
|
+
}
|
|
67831
|
+
args.reuseViewport = this._reuseViewPort;
|
|
67832
|
+
this.renderer.partialLayoutFinished.trigger(args);
|
|
67833
|
+
}
|
|
67260
67834
|
get supportsResize() {
|
|
67261
67835
|
return true;
|
|
67262
67836
|
}
|
|
@@ -67267,6 +67841,47 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67267
67841
|
}
|
|
67268
67842
|
return x;
|
|
67269
67843
|
}
|
|
67844
|
+
doUpdateForBars(renderHints) {
|
|
67845
|
+
this._reuseViewPort = renderHints.reuseViewport ?? false;
|
|
67846
|
+
const firstModifiedMasterBar = renderHints.firstChangedMasterBar;
|
|
67847
|
+
// first update existing systems as needed
|
|
67848
|
+
const systemIndex = this._systems.findIndex(s => {
|
|
67849
|
+
const first = s.masterBarsRenderers[0].masterBar.index;
|
|
67850
|
+
const last = s.masterBarsRenderers[s.masterBarsRenderers.length - 1].masterBar.index;
|
|
67851
|
+
return first <= firstModifiedMasterBar && firstModifiedMasterBar <= last;
|
|
67852
|
+
});
|
|
67853
|
+
if (systemIndex === -1 || !this.renderer.settings.core.enableLazyLoading) {
|
|
67854
|
+
return false;
|
|
67855
|
+
}
|
|
67856
|
+
// Bars from the start of the re-layouted system onward will be re-registered during the
|
|
67857
|
+
// paint pass. Clear their old entries from the preserved BoundsLookup so registration
|
|
67858
|
+
// produces a clean, complete lookup after this render finishes.
|
|
67859
|
+
const firstRebuiltBarIndex = this._systems[systemIndex].masterBarsRenderers[0].masterBar.index;
|
|
67860
|
+
this.renderer.boundsLookup.clearFromMasterBar(firstRebuiltBarIndex);
|
|
67861
|
+
// for now we do a full relayout from the first modified masterbar
|
|
67862
|
+
// there is a lot of room for even more performant updates, but they come
|
|
67863
|
+
// at a risk that features break.
|
|
67864
|
+
// e.g. we could only shift systems where the content didn't change,
|
|
67865
|
+
// but we might still have ties/slurs which have to be updated.
|
|
67866
|
+
const removeSystems = this._systems.splice(systemIndex, this._systems.length - systemIndex);
|
|
67867
|
+
this._systemPartialIds.splice(systemIndex, this._systemPartialIds.length - systemIndex);
|
|
67868
|
+
const system = removeSystems[0];
|
|
67869
|
+
let y = system.y;
|
|
67870
|
+
const firstBarIndex = system.masterBarsRenderers[0].masterBar.index;
|
|
67871
|
+
// signal all partials which didn't change
|
|
67872
|
+
for (const preSystemPartial of this._preSystemPartialIds) {
|
|
67873
|
+
this.reregisterPartial(preSystemPartial);
|
|
67874
|
+
}
|
|
67875
|
+
for (let i = 0; i < systemIndex; i++) {
|
|
67876
|
+
this.reregisterPartial(this._systemPartialIds[i]);
|
|
67877
|
+
}
|
|
67878
|
+
// new partials for all other prats
|
|
67879
|
+
y = this._layoutAndRenderScore(y, firstBarIndex);
|
|
67880
|
+
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67881
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67882
|
+
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67883
|
+
return true;
|
|
67884
|
+
}
|
|
67270
67885
|
doResize() {
|
|
67271
67886
|
let y = this.pagePadding[1];
|
|
67272
67887
|
this.width = this.renderer.width;
|
|
@@ -67285,7 +67900,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67285
67900
|
// 4. One result per StaffSystem
|
|
67286
67901
|
y = this._resizeAndRenderScore(y, oldHeight);
|
|
67287
67902
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67288
|
-
y = this.
|
|
67903
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67289
67904
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67290
67905
|
}
|
|
67291
67906
|
_layoutAndRenderTunings(y, totalHeight = -1) {
|
|
@@ -67309,6 +67924,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67309
67924
|
canvas.textAlign = TextAlign.Center;
|
|
67310
67925
|
this.tuningGlyph.paint(0, 0, canvas);
|
|
67311
67926
|
});
|
|
67927
|
+
this._preSystemPartialIds.push(e.id);
|
|
67312
67928
|
return y + tuningHeight;
|
|
67313
67929
|
}
|
|
67314
67930
|
_layoutAndRenderChordDiagrams(y, totalHeight = -1) {
|
|
@@ -67332,6 +67948,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67332
67948
|
canvas.textAlign = TextAlign.Center;
|
|
67333
67949
|
this.chordDiagrams.paint(0, 0, canvas);
|
|
67334
67950
|
});
|
|
67951
|
+
this._preSystemPartialIds.push(e.id);
|
|
67335
67952
|
return y + diagramHeight;
|
|
67336
67953
|
}
|
|
67337
67954
|
_layoutAndRenderScoreInfo(y, totalHeight = -1) {
|
|
@@ -67374,12 +67991,14 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67374
67991
|
g.paint(0, 0, canvas);
|
|
67375
67992
|
}
|
|
67376
67993
|
});
|
|
67994
|
+
this._preSystemPartialIds.push(e.id);
|
|
67377
67995
|
}
|
|
67378
67996
|
return y + infoHeight;
|
|
67379
67997
|
}
|
|
67380
67998
|
_resizeAndRenderScore(y, oldHeight) {
|
|
67381
67999
|
// if we have a fixed number of bars per row, we only need to refit them.
|
|
67382
68000
|
const barsPerRowActive = this.getBarsPerSystem(0) > 0;
|
|
68001
|
+
this._systemPartialIds = [];
|
|
67383
68002
|
if (barsPerRowActive) {
|
|
67384
68003
|
for (let i = 0; i < this._systems.length; i++) {
|
|
67385
68004
|
const system = this._systems[i];
|
|
@@ -67442,11 +68061,9 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67442
68061
|
}
|
|
67443
68062
|
return y;
|
|
67444
68063
|
}
|
|
67445
|
-
_layoutAndRenderScore(y) {
|
|
67446
|
-
const startIndex = this.firstBarIndex;
|
|
68064
|
+
_layoutAndRenderScore(y, startIndex) {
|
|
67447
68065
|
let currentBarIndex = startIndex;
|
|
67448
68066
|
const endBarIndex = this.lastBarIndex;
|
|
67449
|
-
this._systems = [];
|
|
67450
68067
|
while (currentBarIndex <= endBarIndex) {
|
|
67451
68068
|
// create system and align set proper coordinates
|
|
67452
68069
|
const system = this._createStaffSystem(currentBarIndex, endBarIndex);
|
|
@@ -67481,6 +68098,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67481
68098
|
// since we use partial drawing
|
|
67482
68099
|
system.paint(0, -(args.y / this.renderer.settings.display.scale), canvas);
|
|
67483
68100
|
});
|
|
68101
|
+
this._systemPartialIds.push(args.id);
|
|
67484
68102
|
// calculate coordinates for next system
|
|
67485
68103
|
return height;
|
|
67486
68104
|
}
|
|
@@ -67488,6 +68106,10 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67488
68106
|
* Realignes the bars in this line according to the available space
|
|
67489
68107
|
*/
|
|
67490
68108
|
_fitSystem(system) {
|
|
68109
|
+
// If a bar added late in the assembly introduced a shorter note than earlier bars, the
|
|
68110
|
+
// earlier bars' spring constants (and the cached system widths / totals) are stale.
|
|
68111
|
+
// Reconcile now - it's a no-op when nothing changed.
|
|
68112
|
+
system.reconcileMinDurationIfDirty();
|
|
67491
68113
|
if (system.isFull || system.width > this._maxWidth || this.renderer.settings.display.justifyLastSystem) {
|
|
67492
68114
|
this._scaleToWidth(system, this._maxWidth);
|
|
67493
68115
|
}
|
|
@@ -67499,29 +68121,35 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67499
68121
|
_scaleToWidth(system, width) {
|
|
67500
68122
|
const staffWidth = width - system.accoladeWidth;
|
|
67501
68123
|
const shouldApplyBarScale = this.shouldApplyBarScale;
|
|
67502
|
-
|
|
67503
|
-
//
|
|
67504
|
-
//
|
|
67505
|
-
//
|
|
67506
|
-
//
|
|
67507
|
-
//
|
|
67508
|
-
|
|
67509
|
-
|
|
68124
|
+
// Industry fixed-overhead model (Behind Bars, Dorico, Finale, Sibelius, MuseScore, Guitar Pro):
|
|
68125
|
+
// prefix/postfix glyphs (clef, key sig, time sig, barlines) are treated as fixed overhead and the
|
|
68126
|
+
// remaining staff width is distributed across bars by a per-bar weight.
|
|
68127
|
+
//
|
|
68128
|
+
// distributable = staffWidth - totalFixedOverhead
|
|
68129
|
+
// contentShare = distributable / sum(weight)
|
|
68130
|
+
// bar.width = bar.maxFixedOverhead + weight * contentShare
|
|
68131
|
+
//
|
|
68132
|
+
// The weight depends on the layout mode:
|
|
68133
|
+
// - shouldApplyBarScale=true -> weight = bar.displayScale (model-driven, matches Guitar Pro)
|
|
68134
|
+
// displayScale defaults to 1, so an unset value behaves identically
|
|
68135
|
+
// to an explicit 1 (GP omits the property when not customized).
|
|
68136
|
+
// - shouldApplyBarScale=false -> weight = natural content width (automatic, ignores displayScale)
|
|
68137
|
+
//
|
|
68138
|
+
// Per-bar maxFixedOverhead / maxContentWidth and the system-wide totals are maintained incrementally
|
|
68139
|
+
// in StaffSystem._applyLayoutAndUpdateWidth / revertLastBar so this pass can apply directly.
|
|
68140
|
+
const weightTotal = shouldApplyBarScale ? system.totalBarDisplayScale : system.totalContentWidth;
|
|
68141
|
+
const distributable = Math.max(0, staffWidth - system.totalFixedOverhead);
|
|
68142
|
+
const contentShare = weightTotal > 0 ? distributable / weightTotal : 0;
|
|
67510
68143
|
for (const s of system.allStaves) {
|
|
67511
68144
|
s.resetSharedLayoutData();
|
|
67512
|
-
// scale the bars by keeping their respective ratio size
|
|
67513
68145
|
let w = 0;
|
|
67514
|
-
for (
|
|
68146
|
+
for (let i = 0; i < s.barRenderers.length; i++) {
|
|
68147
|
+
const renderer = s.barRenderers[i];
|
|
68148
|
+
const mb = system.masterBarsRenderers[i];
|
|
67515
68149
|
renderer.x = w;
|
|
67516
68150
|
renderer.y = s.topPadding + s.topOverflow;
|
|
67517
|
-
|
|
67518
|
-
|
|
67519
|
-
const barDisplayScale = system.getBarDisplayScale(renderer);
|
|
67520
|
-
actualBarWidth = (barDisplayScale * staffWidth) / totalScale;
|
|
67521
|
-
}
|
|
67522
|
-
else {
|
|
67523
|
-
actualBarWidth = renderer.computedWidth + spacePerBar;
|
|
67524
|
-
}
|
|
68151
|
+
const weight = shouldApplyBarScale ? system.getBarDisplayScale(renderer) : mb.maxContentWidth;
|
|
68152
|
+
const actualBarWidth = mb.maxFixedOverhead + weight * contentShare;
|
|
67525
68153
|
renderer.scaleToWidth(actualBarWidth);
|
|
67526
68154
|
w += renderer.width;
|
|
67527
68155
|
}
|
|
@@ -68545,11 +69173,43 @@ class TabTieGlyph extends NoteTieGlyph {
|
|
|
68545
69173
|
}
|
|
68546
69174
|
}
|
|
68547
69175
|
|
|
69176
|
+
/**
|
|
69177
|
+
* Helpers for building `TieGlyphLabel` instances from model-side
|
|
69178
|
+
* {@link SlurSegment}s.
|
|
69179
|
+
* @internal
|
|
69180
|
+
*/
|
|
69181
|
+
class TieGlyphLabels {
|
|
69182
|
+
/**
|
|
69183
|
+
* Builds a `TieGlyphLabel` for one segment of a slur. The
|
|
69184
|
+
* `isAscending` flag selects between the H/P glyph for hammer-on
|
|
69185
|
+
* vs. pull-off — score side passes a comparison on `realValue`,
|
|
69186
|
+
* tab side passes a comparison on `fret`.
|
|
69187
|
+
*/
|
|
69188
|
+
static build(s, isAscending) {
|
|
69189
|
+
if (s.kind === SlurSegmentKind.LegatoSlide) {
|
|
69190
|
+
return {
|
|
69191
|
+
fromNote: s.fromNote,
|
|
69192
|
+
toNote: s.toNote,
|
|
69193
|
+
text: s.text !== null ? s.text : 'sl.',
|
|
69194
|
+
element: NotationElement.EffectSlideText
|
|
69195
|
+
};
|
|
69196
|
+
}
|
|
69197
|
+
// HammerPull
|
|
69198
|
+
return {
|
|
69199
|
+
fromNote: s.fromNote,
|
|
69200
|
+
toNote: s.toNote,
|
|
69201
|
+
text: s.text !== null ? s.text : isAscending ? 'H' : 'P',
|
|
69202
|
+
element: NotationElement.EffectHammerOnPullOffText
|
|
69203
|
+
};
|
|
69204
|
+
}
|
|
69205
|
+
}
|
|
69206
|
+
|
|
68548
69207
|
/**
|
|
68549
69208
|
* @internal
|
|
68550
69209
|
*/
|
|
68551
69210
|
class TabSlurGlyph extends TabTieGlyph {
|
|
68552
69211
|
_forSlide;
|
|
69212
|
+
_labels = null;
|
|
68553
69213
|
constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
|
|
68554
69214
|
super(slurEffectId, startNote, endNote, forEnd);
|
|
68555
69215
|
this._forSlide = forSlide;
|
|
@@ -68557,6 +69217,22 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68557
69217
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
68558
69218
|
return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
68559
69219
|
}
|
|
69220
|
+
getSlurLabels() {
|
|
69221
|
+
if (this._labels === null) {
|
|
69222
|
+
this._labels = [];
|
|
69223
|
+
const slur = this.startNote.effectSlur;
|
|
69224
|
+
if (slur !== null) {
|
|
69225
|
+
const notationSettings = this.renderer.settings.notation;
|
|
69226
|
+
for (const s of slur.segments) {
|
|
69227
|
+
const label = TieGlyphLabels.build(s, s.toNote.fret >= s.fromNote.fret);
|
|
69228
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
69229
|
+
this._labels.push(label);
|
|
69230
|
+
}
|
|
69231
|
+
}
|
|
69232
|
+
}
|
|
69233
|
+
}
|
|
69234
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
69235
|
+
}
|
|
68560
69236
|
tryExpand(startNote, endNote, forSlide, forEnd) {
|
|
68561
69237
|
// same type required
|
|
68562
69238
|
if (this._forSlide !== forSlide) {
|
|
@@ -68582,6 +69258,7 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68582
69258
|
case BeamDirection.Up:
|
|
68583
69259
|
if (startNote.realValue > this.startNote.realValue) {
|
|
68584
69260
|
this.startNote = startNote;
|
|
69261
|
+
this._labels = null; // invalidate cache — labels live on startNote
|
|
68585
69262
|
}
|
|
68586
69263
|
if (endNote.realValue > this.endNote.realValue) {
|
|
68587
69264
|
this.endNote = endNote;
|
|
@@ -68590,6 +69267,7 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68590
69267
|
case BeamDirection.Down:
|
|
68591
69268
|
if (startNote.realValue < this.startNote.realValue) {
|
|
68592
69269
|
this.startNote = startNote;
|
|
69270
|
+
this._labels = null;
|
|
68593
69271
|
}
|
|
68594
69272
|
if (endNote.realValue < this.endNote.realValue) {
|
|
68595
69273
|
this.endNote = endNote;
|
|
@@ -69345,12 +70023,24 @@ class LineBarRenderer extends BarRendererBase {
|
|
|
69345
70023
|
const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
|
|
69346
70024
|
const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
|
|
69347
70025
|
const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
|
|
69348
|
-
let startY
|
|
69349
|
-
let endY
|
|
70026
|
+
let startY;
|
|
70027
|
+
let endY;
|
|
69350
70028
|
if (isRestOnly) {
|
|
69351
|
-
|
|
70029
|
+
// rests have no stems, so anchor to the actual rest glyph bounds
|
|
70030
|
+
// instead of a stem-adjusted flag position (which would place the bracket
|
|
70031
|
+
// a full quarter-stem length away from the rests).
|
|
70032
|
+
if (direction === BeamDirection.Up) {
|
|
70033
|
+
startY = Math.min(this.getRestY(firstNonRestBeat, NoteYPosition.Top), this.getRestY(lastNonRestBeat, NoteYPosition.Top));
|
|
70034
|
+
}
|
|
70035
|
+
else {
|
|
70036
|
+
startY = Math.max(this.getRestY(firstNonRestBeat, NoteYPosition.Bottom), this.getRestY(lastNonRestBeat, NoteYPosition.Bottom));
|
|
70037
|
+
}
|
|
69352
70038
|
endY = startY;
|
|
69353
70039
|
}
|
|
70040
|
+
else {
|
|
70041
|
+
startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
|
|
70042
|
+
endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
|
|
70043
|
+
}
|
|
69354
70044
|
// align line centered in available space
|
|
69355
70045
|
if (direction === BeamDirection.Down) {
|
|
69356
70046
|
startY += shift;
|
|
@@ -69720,7 +70410,30 @@ class LineBarRenderer extends BarRendererBase {
|
|
|
69720
70410
|
let minNoteY = 0;
|
|
69721
70411
|
for (const v of this.helpers.beamHelpers) {
|
|
69722
70412
|
for (const h of v) {
|
|
69723
|
-
if (!this.shouldPaintBeamingHelper(h))
|
|
70413
|
+
if (!this.shouldPaintBeamingHelper(h)) {
|
|
70414
|
+
// beam is not drawn, but a rest-only tuplet still draws a bracket
|
|
70415
|
+
// anchored to the rest glyph bounds and needs overflow reserved.
|
|
70416
|
+
if (h.hasTuplet && h.isRestBeamHelper) {
|
|
70417
|
+
const tupletGroup = h.beats[0].tupletGroup;
|
|
70418
|
+
const tupletFirst = tupletGroup.beats[0];
|
|
70419
|
+
const tupletLast = tupletGroup.beats[tupletGroup.beats.length - 1];
|
|
70420
|
+
const tupletDirection = this.getTupletBeamDirection(h);
|
|
70421
|
+
if (tupletDirection === BeamDirection.Up) {
|
|
70422
|
+
const restTop = Math.min(this.getRestY(tupletFirst, NoteYPosition.Top), this.getRestY(tupletLast, NoteYPosition.Top));
|
|
70423
|
+
const topY = restTop - this.tupletSize - this.tupletOffset;
|
|
70424
|
+
if (topY < maxNoteY) {
|
|
70425
|
+
maxNoteY = topY;
|
|
70426
|
+
}
|
|
70427
|
+
}
|
|
70428
|
+
else {
|
|
70429
|
+
const restBottom = Math.max(this.getRestY(tupletFirst, NoteYPosition.Bottom), this.getRestY(tupletLast, NoteYPosition.Bottom));
|
|
70430
|
+
const bottomY = restBottom + this.tupletSize + this.tupletOffset;
|
|
70431
|
+
if (bottomY > minNoteY) {
|
|
70432
|
+
minNoteY = bottomY;
|
|
70433
|
+
}
|
|
70434
|
+
}
|
|
70435
|
+
}
|
|
70436
|
+
}
|
|
69724
70437
|
else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
|
|
69725
70438
|
const tupletDirection = this.getTupletBeamDirection(h);
|
|
69726
70439
|
const direction = this.getBeamDirection(h);
|
|
@@ -73292,9 +74005,26 @@ class ScoreTieGlyph extends NoteTieGlyph {
|
|
|
73292
74005
|
* @internal
|
|
73293
74006
|
*/
|
|
73294
74007
|
class ScoreSlurGlyph extends ScoreTieGlyph {
|
|
74008
|
+
_labels = null;
|
|
73295
74009
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
73296
74010
|
return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
73297
74011
|
}
|
|
74012
|
+
getSlurLabels() {
|
|
74013
|
+
if (this._labels === null) {
|
|
74014
|
+
this._labels = [];
|
|
74015
|
+
const slur = this.startNote.beat.effectSlur;
|
|
74016
|
+
if (slur !== null) {
|
|
74017
|
+
const notationSettings = this.renderer.settings.notation;
|
|
74018
|
+
for (const s of slur.segments) {
|
|
74019
|
+
const label = TieGlyphLabels.build(s, s.toNote.realValue >= s.fromNote.realValue);
|
|
74020
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
74021
|
+
this._labels.push(label);
|
|
74022
|
+
}
|
|
74023
|
+
}
|
|
74024
|
+
}
|
|
74025
|
+
}
|
|
74026
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
74027
|
+
}
|
|
73298
74028
|
calculateStartX() {
|
|
73299
74029
|
return (this.renderer.x +
|
|
73300
74030
|
(this._isStartCentered()
|