@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.js
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
|
*
|
|
@@ -209,9 +209,9 @@
|
|
|
209
209
|
* @internal
|
|
210
210
|
*/
|
|
211
211
|
class VersionInfo {
|
|
212
|
-
static version = '1.9.0-alpha.
|
|
213
|
-
static date = '2026-
|
|
214
|
-
static commit = '
|
|
212
|
+
static version = '1.9.0-alpha.1803';
|
|
213
|
+
static date = '2026-05-15T04:13:04.606Z';
|
|
214
|
+
static commit = 'a87a8635a0a0306cdab1c7fe2e9e72576ed6f795';
|
|
215
215
|
static print(print) {
|
|
216
216
|
print(`alphaTab ${VersionInfo.version}`);
|
|
217
217
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -998,6 +998,38 @@
|
|
|
998
998
|
SlideOutType[SlideOutType["PickSlideUp"] = 6] = "PickSlideUp";
|
|
999
999
|
})(SlideOutType || (SlideOutType = {}));
|
|
1000
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* A slur arc spanning two notes, optionally with inner articulation
|
|
1003
|
+
* segments. Corresponds conceptually to a MusicXML `<slur>` element
|
|
1004
|
+
* plus the technique spans inside it.
|
|
1005
|
+
*
|
|
1006
|
+
* For this PR only effect slurs (hammer-pull + legato-slide chains)
|
|
1007
|
+
* are derived in `Note.finish()`. Phrase and legato slurs may join
|
|
1008
|
+
* this type in a future PR; a discriminator will be added at that
|
|
1009
|
+
* point.
|
|
1010
|
+
* @internal
|
|
1011
|
+
*/
|
|
1012
|
+
class Slur {
|
|
1013
|
+
originNote;
|
|
1014
|
+
destinationNote;
|
|
1015
|
+
segments = [];
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Articulation kind for an inner span of a {@link Slur}.
|
|
1020
|
+
*
|
|
1021
|
+
* Drives the renderer's font selection (which {@link NotationElement} to
|
|
1022
|
+
* use) and the default label text when {@link SlurSegment.text} is null.
|
|
1023
|
+
* `Note.finish()` classifies the kind once when building the slur; the
|
|
1024
|
+
* renderer never re-derives it.
|
|
1025
|
+
* @internal
|
|
1026
|
+
*/
|
|
1027
|
+
var SlurSegmentKind;
|
|
1028
|
+
(function (SlurSegmentKind) {
|
|
1029
|
+
SlurSegmentKind[SlurSegmentKind["HammerPull"] = 0] = "HammerPull";
|
|
1030
|
+
SlurSegmentKind[SlurSegmentKind["LegatoSlide"] = 1] = "LegatoSlide";
|
|
1031
|
+
})(SlurSegmentKind || (SlurSegmentKind = {}));
|
|
1032
|
+
|
|
1001
1033
|
/**
|
|
1002
1034
|
* This public enum lists all vibrato types that can be performed.
|
|
1003
1035
|
* @public
|
|
@@ -1338,6 +1370,14 @@
|
|
|
1338
1370
|
* The slurs shown on bend effects within the score staff.
|
|
1339
1371
|
*/
|
|
1340
1372
|
NotationElement[NotationElement["ScoreBendSlur"] = 55] = "ScoreBendSlur";
|
|
1373
|
+
/**
|
|
1374
|
+
* The hammer-on pull-off text shown on slurs.
|
|
1375
|
+
*/
|
|
1376
|
+
NotationElement[NotationElement["EffectHammerOnPullOffText"] = 56] = "EffectHammerOnPullOffText";
|
|
1377
|
+
/**
|
|
1378
|
+
* The slide text shown on slurs.
|
|
1379
|
+
*/
|
|
1380
|
+
NotationElement[NotationElement["EffectSlideText"] = 57] = "EffectSlideText";
|
|
1341
1381
|
})(exports.NotationElement || (exports.NotationElement = {}));
|
|
1342
1382
|
/**
|
|
1343
1383
|
* The notation settings control how various music notation elements are shown and behaving
|
|
@@ -6134,6 +6174,16 @@
|
|
|
6134
6174
|
* @json_ignore
|
|
6135
6175
|
*/
|
|
6136
6176
|
effectSlurDestination = null;
|
|
6177
|
+
/**
|
|
6178
|
+
* The {@link Slur} object whose origin is this note. Populated by
|
|
6179
|
+
* `finish()`; non-null only on the chain-origin note of an effect
|
|
6180
|
+
* slur. Carries the inner articulation segments used by the
|
|
6181
|
+
* renderer to paint H/P/sl. labels along the arc.
|
|
6182
|
+
* @clone_ignore
|
|
6183
|
+
* @json_ignore
|
|
6184
|
+
* @internal
|
|
6185
|
+
*/
|
|
6186
|
+
effectSlur = null;
|
|
6137
6187
|
/**
|
|
6138
6188
|
* The ornament applied on the note.
|
|
6139
6189
|
*/
|
|
@@ -6418,23 +6468,50 @@
|
|
|
6418
6468
|
break;
|
|
6419
6469
|
}
|
|
6420
6470
|
let effectSlurDestination = null;
|
|
6471
|
+
let effectSlurSegmentKind = null;
|
|
6421
6472
|
if (this.isHammerPullOrigin && this.hammerPullDestination) {
|
|
6422
6473
|
effectSlurDestination = this.hammerPullDestination;
|
|
6474
|
+
effectSlurSegmentKind = SlurSegmentKind.HammerPull;
|
|
6423
6475
|
}
|
|
6424
6476
|
else if (this.slideOutType === SlideOutType.Legato && this.slideTarget) {
|
|
6425
6477
|
effectSlurDestination = this.slideTarget;
|
|
6478
|
+
effectSlurSegmentKind = SlurSegmentKind.LegatoSlide;
|
|
6426
6479
|
}
|
|
6427
6480
|
if (effectSlurDestination) {
|
|
6428
6481
|
this.hasEffectSlur = true;
|
|
6429
6482
|
if (this.effectSlurOrigin && this.beat.pickStroke === PickStroke.None) {
|
|
6430
|
-
this.effectSlurOrigin
|
|
6431
|
-
|
|
6483
|
+
const chainOrigin = this.effectSlurOrigin;
|
|
6484
|
+
chainOrigin.effectSlurDestination = effectSlurDestination;
|
|
6485
|
+
effectSlurDestination.effectSlurOrigin = chainOrigin;
|
|
6432
6486
|
this.effectSlurOrigin = null;
|
|
6487
|
+
if (effectSlurSegmentKind !== null && chainOrigin.effectSlur !== null) {
|
|
6488
|
+
chainOrigin.effectSlur.destinationNote = effectSlurDestination;
|
|
6489
|
+
chainOrigin.effectSlur.segments.push({
|
|
6490
|
+
fromNote: this,
|
|
6491
|
+
toNote: effectSlurDestination,
|
|
6492
|
+
kind: effectSlurSegmentKind,
|
|
6493
|
+
text: null
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6433
6496
|
}
|
|
6434
6497
|
else {
|
|
6435
6498
|
this.isEffectSlurOrigin = true;
|
|
6436
6499
|
this.effectSlurDestination = effectSlurDestination;
|
|
6437
|
-
|
|
6500
|
+
effectSlurDestination.effectSlurOrigin = this;
|
|
6501
|
+
// Always allocate a fresh Slur — finish() may run twice (worker re-finish);
|
|
6502
|
+
// overwriting unconditionally keeps the derivation idempotent.
|
|
6503
|
+
const slur = new Slur();
|
|
6504
|
+
slur.originNote = this;
|
|
6505
|
+
slur.destinationNote = effectSlurDestination;
|
|
6506
|
+
if (effectSlurSegmentKind !== null) {
|
|
6507
|
+
slur.segments.push({
|
|
6508
|
+
fromNote: this,
|
|
6509
|
+
toNote: effectSlurDestination,
|
|
6510
|
+
kind: effectSlurSegmentKind,
|
|
6511
|
+
text: null
|
|
6512
|
+
});
|
|
6513
|
+
}
|
|
6514
|
+
this.effectSlur = slur;
|
|
6438
6515
|
}
|
|
6439
6516
|
}
|
|
6440
6517
|
// try to detect what kind of bend was used and cleans unneeded points if required
|
|
@@ -7684,6 +7761,23 @@
|
|
|
7684
7761
|
* @json_ignore
|
|
7685
7762
|
*/
|
|
7686
7763
|
effectSlurDestination = null;
|
|
7764
|
+
/**
|
|
7765
|
+
* Convenience accessor for the {@link Slur} of this beat. Returns
|
|
7766
|
+
* the effect slur of whichever note in this beat owns it (the
|
|
7767
|
+
* chain-origin note populated during `Note.finish()`), or `null`
|
|
7768
|
+
* when no note in the beat is an effect-slur origin.
|
|
7769
|
+
* @clone_ignore
|
|
7770
|
+
* @json_ignore
|
|
7771
|
+
* @internal
|
|
7772
|
+
*/
|
|
7773
|
+
get effectSlur() {
|
|
7774
|
+
for (const n of this.notes) {
|
|
7775
|
+
if (n.effectSlur !== null) {
|
|
7776
|
+
return n.effectSlur;
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
return null;
|
|
7780
|
+
}
|
|
7687
7781
|
/**
|
|
7688
7782
|
* Gets or sets how the beaming should be done for this beat.
|
|
7689
7783
|
*/
|
|
@@ -31948,7 +32042,9 @@
|
|
|
31948
32042
|
[exports.NotationElement.RepeatCount, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31949
32043
|
[exports.NotationElement.BarNumber, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31950
32044
|
[exports.NotationElement.ScoreBendSlur, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31951
|
-
[exports.NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)]
|
|
32045
|
+
[exports.NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)],
|
|
32046
|
+
[exports.NotationElement.EffectHammerOnPullOffText, RenderingResources._effectFont],
|
|
32047
|
+
[exports.NotationElement.EffectSlideText, RenderingResources._effectFont]
|
|
31952
32048
|
]);
|
|
31953
32049
|
/**
|
|
31954
32050
|
* The name of the SMuFL Font to use for rendering music symbols.
|
|
@@ -32239,9 +32335,16 @@
|
|
|
32239
32335
|
notationElement = exports.NotationElement.ScoreWords;
|
|
32240
32336
|
break;
|
|
32241
32337
|
}
|
|
32338
|
+
return this.getFontForNotationElement(notationElement);
|
|
32339
|
+
}
|
|
32340
|
+
/**
|
|
32341
|
+
* @internal
|
|
32342
|
+
* @param element
|
|
32343
|
+
*/
|
|
32344
|
+
getFontForNotationElement(notationElement) {
|
|
32242
32345
|
return this.elementFonts.has(notationElement)
|
|
32243
32346
|
? this.elementFonts.get(notationElement)
|
|
32244
|
-
: RenderingResources.defaultFonts.get(
|
|
32347
|
+
: RenderingResources.defaultFonts.get(notationElement);
|
|
32245
32348
|
}
|
|
32246
32349
|
}
|
|
32247
32350
|
|
|
@@ -32634,9 +32737,16 @@
|
|
|
32634
32737
|
*
|
|
32635
32738
|
* The page layout does not use `displayWidth`. The use of absolute widths would break the proper alignments needed for this kind of display.
|
|
32636
32739
|
*
|
|
32637
|
-
*
|
|
32638
|
-
*
|
|
32639
|
-
*
|
|
32740
|
+
* In both modes, prefix and postfix glyphs (clef, key signature, time signature, barlines) are treated as fixed overhead: they keep their
|
|
32741
|
+
* natural size and the remaining staff width is distributed across bars by a per-bar weight. This matches the convention used by
|
|
32742
|
+
* Guitar Pro, Dorico, Finale, Sibelius and MuseScore. Bars that carry a system-start prefix or a mid-line clef/key/time-signature change
|
|
32743
|
+
* are therefore visibly wider than plain bars with the same weight. The weight source depends on the mode:
|
|
32744
|
+
*
|
|
32745
|
+
* * `Automatic` (default for `page` layout): weights come from the built-in spacing engine (the natural content width of each bar).
|
|
32746
|
+
* `displayScale` on the model is ignored.
|
|
32747
|
+
* * `UseModelLayout` (and the `parchment` layout): weights come from `bar.displayScale` / `masterBar.displayScale`. An unset
|
|
32748
|
+
* `displayScale` defaults to `1` and behaves identically to an explicit `1`, matching Guitar Pro (which omits the value when the
|
|
32749
|
+
* author hasn't customized it).
|
|
32640
32750
|
*
|
|
32641
32751
|
* ### Horizontal Layout
|
|
32642
32752
|
*
|
|
@@ -38835,14 +38945,25 @@
|
|
|
38835
38945
|
*/
|
|
38836
38946
|
boundsLookup;
|
|
38837
38947
|
/**
|
|
38838
|
-
*
|
|
38948
|
+
* Whether this system's bounds have already been scaled via `finish`. Prevents double-scaling
|
|
38949
|
+
* when the parent `BoundsLookup` is preserved across partial renders and `finish` is invoked
|
|
38950
|
+
* again on a mix of already-scaled (preserved) and newly-registered (natural-coordinate) systems.
|
|
38951
|
+
*/
|
|
38952
|
+
isFinished = false;
|
|
38953
|
+
/**
|
|
38954
|
+
* Finished the lookup for optimized access. Idempotent: once finished, further calls are no-ops
|
|
38955
|
+
* so preserved systems survive partial renders without being re-scaled.
|
|
38839
38956
|
*/
|
|
38840
38957
|
finish(scale = 1) {
|
|
38958
|
+
if (this.isFinished) {
|
|
38959
|
+
return;
|
|
38960
|
+
}
|
|
38841
38961
|
this.realBounds.scaleWith(scale);
|
|
38842
38962
|
this.visualBounds.scaleWith(scale);
|
|
38843
38963
|
for (const t of this.bars) {
|
|
38844
38964
|
t.finish(scale);
|
|
38845
38965
|
}
|
|
38966
|
+
this.isFinished = true;
|
|
38846
38967
|
}
|
|
38847
38968
|
/**
|
|
38848
38969
|
* Adds a new master bar to this lookup.
|
|
@@ -39015,6 +39136,58 @@
|
|
|
39015
39136
|
}
|
|
39016
39137
|
this.isFinished = true;
|
|
39017
39138
|
}
|
|
39139
|
+
/**
|
|
39140
|
+
* Re-opens the lookup for registrations without discarding previously registered bounds.
|
|
39141
|
+
* Used by the renderer when it preserves this lookup across a partial render so that new
|
|
39142
|
+
* bounds for the re-layouted range can be added while preserved systems stay intact.
|
|
39143
|
+
* @internal
|
|
39144
|
+
*/
|
|
39145
|
+
resetForPartialUpdate() {
|
|
39146
|
+
this.isFinished = false;
|
|
39147
|
+
}
|
|
39148
|
+
/**
|
|
39149
|
+
* Removes all entries belonging to the given master bar index and any bars after it.
|
|
39150
|
+
* Used before a partial render re-registers bounds for the re-layouted range, so the
|
|
39151
|
+
* preserved lookup ends up with only the unchanged entries when registration begins.
|
|
39152
|
+
*
|
|
39153
|
+
* Assumes the layout aligns its re-layouted range to system boundaries - i.e. the first
|
|
39154
|
+
* system to clear starts exactly at `masterBarIndex`. Caller is responsible for passing
|
|
39155
|
+
* the first master-bar-index of the first re-layouted system.
|
|
39156
|
+
* @internal
|
|
39157
|
+
*/
|
|
39158
|
+
clearFromMasterBar(masterBarIndex) {
|
|
39159
|
+
// drop staff systems whose bars start at or after the cleared range.
|
|
39160
|
+
let firstRemovedSystem = -1;
|
|
39161
|
+
for (let i = 0; i < this.staffSystems.length; i++) {
|
|
39162
|
+
const systemBars = this.staffSystems[i].bars;
|
|
39163
|
+
if (systemBars.length > 0 && systemBars[0].index >= masterBarIndex) {
|
|
39164
|
+
firstRemovedSystem = i;
|
|
39165
|
+
break;
|
|
39166
|
+
}
|
|
39167
|
+
}
|
|
39168
|
+
if (firstRemovedSystem !== -1) {
|
|
39169
|
+
this.staffSystems.splice(firstRemovedSystem, this.staffSystems.length - firstRemovedSystem);
|
|
39170
|
+
}
|
|
39171
|
+
// drop master bar entries at or beyond the cleared range.
|
|
39172
|
+
for (const key of Array.from(this._masterBarLookup.keys())) {
|
|
39173
|
+
if (key >= masterBarIndex) {
|
|
39174
|
+
this._masterBarLookup.delete(key);
|
|
39175
|
+
}
|
|
39176
|
+
}
|
|
39177
|
+
// drop beat entries whose beats belong to cleared bars.
|
|
39178
|
+
for (const key of Array.from(this._beatLookup.keys())) {
|
|
39179
|
+
const list = this._beatLookup.get(key);
|
|
39180
|
+
const filtered = list.filter(b => b.beat.voice.bar.index < masterBarIndex);
|
|
39181
|
+
if (filtered.length === 0) {
|
|
39182
|
+
this._beatLookup.delete(key);
|
|
39183
|
+
}
|
|
39184
|
+
else if (filtered.length !== list.length) {
|
|
39185
|
+
this._beatLookup.set(key, filtered);
|
|
39186
|
+
}
|
|
39187
|
+
}
|
|
39188
|
+
// drop the in-progress pointer - the next addStaffSystem call will replace it.
|
|
39189
|
+
this._currentStaffSystem = null;
|
|
39190
|
+
}
|
|
39018
39191
|
/**
|
|
39019
39192
|
* Adds a new staff sytem to the lookup.
|
|
39020
39193
|
* @param bounds The staff system bounds to add.
|
|
@@ -46846,7 +47019,15 @@
|
|
|
46846
47019
|
Logger.warning('Rendering', 'AlphaTab skipped rendering because of width=0 (element invisible)', null);
|
|
46847
47020
|
return;
|
|
46848
47021
|
}
|
|
46849
|
-
|
|
47022
|
+
// For partial renders we preserve the existing lookup so bars outside the re-layouted
|
|
47023
|
+
// range keep their already-scaled bounds - the layout will clear the changed range
|
|
47024
|
+
// before the paint pass re-registers fresh entries for it.
|
|
47025
|
+
if (renderHints?.firstChangedMasterBar !== undefined && this.boundsLookup) {
|
|
47026
|
+
this.boundsLookup.resetForPartialUpdate();
|
|
47027
|
+
}
|
|
47028
|
+
else {
|
|
47029
|
+
this.boundsLookup = new BoundsLookup();
|
|
47030
|
+
}
|
|
46850
47031
|
this._recreateCanvas();
|
|
46851
47032
|
this.canvas.lineWidth = 1;
|
|
46852
47033
|
this.canvas.settings = this.settings;
|
|
@@ -48720,9 +48901,6 @@
|
|
|
48720
48901
|
* @param beat The beat to add.
|
|
48721
48902
|
*/
|
|
48722
48903
|
highlightBeat(beat, playbackStart) {
|
|
48723
|
-
if (beat.isEmpty && !beat.voice.isEmpty) {
|
|
48724
|
-
return;
|
|
48725
|
-
}
|
|
48726
48904
|
if (!this._highlightedBeats.has(beat.id)) {
|
|
48727
48905
|
this._highlightedBeats.set(beat.id, true);
|
|
48728
48906
|
this.highlightedBeats.push(new BeatTickLookupItem(beat, playbackStart));
|
|
@@ -49092,6 +49270,21 @@
|
|
|
49092
49270
|
}
|
|
49093
49271
|
}
|
|
49094
49272
|
|
|
49273
|
+
/**
|
|
49274
|
+
* Represents a range of the song that should be played.
|
|
49275
|
+
* @public
|
|
49276
|
+
*/
|
|
49277
|
+
class PlaybackRange {
|
|
49278
|
+
/**
|
|
49279
|
+
* The position in midi ticks from where the song should start.
|
|
49280
|
+
*/
|
|
49281
|
+
startTick = 0;
|
|
49282
|
+
/**
|
|
49283
|
+
* The position in midi ticks to where the song should be played.
|
|
49284
|
+
*/
|
|
49285
|
+
endTick = 0;
|
|
49286
|
+
}
|
|
49287
|
+
|
|
49095
49288
|
/**
|
|
49096
49289
|
* Describes how a cursor should be moving.
|
|
49097
49290
|
* @public
|
|
@@ -49252,6 +49445,13 @@
|
|
|
49252
49445
|
* @internal
|
|
49253
49446
|
*/
|
|
49254
49447
|
masterBarLookup = new Map();
|
|
49448
|
+
/**
|
|
49449
|
+
* A dictionary of all beat played. The index is the id to {@link Beat.id}.
|
|
49450
|
+
* The value is the bar relative tick time at which the beat was registered during midi generation.
|
|
49451
|
+
* This lookup only contains the first time a Beat is played.
|
|
49452
|
+
* @internal
|
|
49453
|
+
*/
|
|
49454
|
+
beatLookup = new Map();
|
|
49255
49455
|
/**
|
|
49256
49456
|
* A list of all {@link MasterBarTickLookup} sorted by time.
|
|
49257
49457
|
*/
|
|
@@ -49618,10 +49818,22 @@
|
|
|
49618
49818
|
* @returns The time in midi ticks at which the beat is played the first time or 0 if the beat is not contained
|
|
49619
49819
|
*/
|
|
49620
49820
|
getBeatStart(beat) {
|
|
49621
|
-
if (!this.masterBarLookup.has(beat.voice.bar.index)) {
|
|
49821
|
+
if (!this.masterBarLookup.has(beat.voice.bar.index) || !this.beatLookup.has(beat.id)) {
|
|
49622
49822
|
return 0;
|
|
49623
49823
|
}
|
|
49624
|
-
|
|
49824
|
+
const mb = this.masterBarLookup.get(beat.voice.bar.index);
|
|
49825
|
+
return mb.start + this.beatLookup.get(beat.id).startTick;
|
|
49826
|
+
}
|
|
49827
|
+
/**
|
|
49828
|
+
* Gets the playback range in midi ticks for a given beat.
|
|
49829
|
+
* @param beat The beat to find the time period for.
|
|
49830
|
+
* @returns The relative playback range within the parent masterbar at which the beat start and ends playing
|
|
49831
|
+
*/
|
|
49832
|
+
getRelativeBeatPlaybackRange(beat) {
|
|
49833
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49834
|
+
return undefined;
|
|
49835
|
+
}
|
|
49836
|
+
return this.beatLookup.get(beat.id);
|
|
49625
49837
|
}
|
|
49626
49838
|
/**
|
|
49627
49839
|
* Adds a new {@link MasterBarTickLookup} to the lookup table.
|
|
@@ -49639,6 +49851,12 @@
|
|
|
49639
49851
|
}
|
|
49640
49852
|
}
|
|
49641
49853
|
addBeat(beat, start, duration) {
|
|
49854
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49855
|
+
const playbackRange = new PlaybackRange();
|
|
49856
|
+
playbackRange.startTick = start;
|
|
49857
|
+
playbackRange.endTick = start + duration;
|
|
49858
|
+
this.beatLookup.set(beat.id, playbackRange);
|
|
49859
|
+
}
|
|
49642
49860
|
const currentMasterBar = this._currentMasterBar;
|
|
49643
49861
|
if (currentMasterBar) {
|
|
49644
49862
|
// pre-beat grace notes at the start of the bar we also add the beat to the previous bar
|
|
@@ -50190,7 +50408,13 @@
|
|
|
50190
50408
|
let beatStart = beat.playbackStart;
|
|
50191
50409
|
let audioDuration = beat.playbackDuration;
|
|
50192
50410
|
const masterBarDuration = beat.voice.bar.masterBar.calculateDuration();
|
|
50193
|
-
|
|
50411
|
+
// For a bar whose voice contains a single empty beat (the typical "whole-bar rest"
|
|
50412
|
+
// placeholder inserted during score.finish), extend the beat's audio duration to cover
|
|
50413
|
+
// the full bar so cursor navigation has a beat to follow across the whole bar. Don't
|
|
50414
|
+
// apply this when the voice has multiple beats: those represent explicit rhythmic
|
|
50415
|
+
// subdivisions even when each beat is empty (e.g. a recording grid of placeholder
|
|
50416
|
+
// slots), and overriding would make every beat overlap the whole bar.
|
|
50417
|
+
if (beat.voice.bar.isEmpty && beat.voice.beats.length === 1) {
|
|
50194
50418
|
audioDuration = masterBarDuration;
|
|
50195
50419
|
}
|
|
50196
50420
|
else if (beat.voice.bar.masterBar.tripletFeel !== TripletFeel.NoTripletFeel &&
|
|
@@ -52216,21 +52440,6 @@
|
|
|
52216
52440
|
}
|
|
52217
52441
|
}
|
|
52218
52442
|
|
|
52219
|
-
/**
|
|
52220
|
-
* Represents a range of the song that should be played.
|
|
52221
|
-
* @public
|
|
52222
|
-
*/
|
|
52223
|
-
class PlaybackRange {
|
|
52224
|
-
/**
|
|
52225
|
-
* The position in midi ticks from where the song should start.
|
|
52226
|
-
*/
|
|
52227
|
-
startTick = 0;
|
|
52228
|
-
/**
|
|
52229
|
-
* The position in midi ticks to where the song should be played.
|
|
52230
|
-
*/
|
|
52231
|
-
endTick = 0;
|
|
52232
|
-
}
|
|
52233
|
-
|
|
52234
52443
|
/**
|
|
52235
52444
|
* A {@link IAlphaSynth} implementation wrapping and underling other {@link IAlphaSynth}
|
|
52236
52445
|
* allowing dynamic changing of the underlying instance without loosing aspects like the
|
|
@@ -54811,7 +55020,8 @@
|
|
|
54811
55020
|
this._isInitialBeatCursorUpdate ||
|
|
54812
55021
|
barBounds.y !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.y ||
|
|
54813
55022
|
startBeatX < previousBeatBounds.onNotesX ||
|
|
54814
|
-
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1
|
|
55023
|
+
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1 ||
|
|
55024
|
+
barBounds.h !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.h;
|
|
54815
55025
|
if (jumpCursor) {
|
|
54816
55026
|
cursorHandler.placeBeatCursor(beatCursor, beatBoundings, startBeatX);
|
|
54817
55027
|
}
|
|
@@ -55430,23 +55640,24 @@
|
|
|
55430
55640
|
if (this._selectionStart && this._tickCache) {
|
|
55431
55641
|
// get the start and stop ticks (which consider properly repeats)
|
|
55432
55642
|
const tickCache = this._tickCache;
|
|
55433
|
-
const
|
|
55643
|
+
const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
|
|
55644
|
+
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat);
|
|
55645
|
+
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? this._selectionStart.beat.playbackStart;
|
|
55434
55646
|
// move to selection start
|
|
55435
55647
|
this._currentBeat = null; // reset current beat so it is updating the cursor
|
|
55436
55648
|
if (this._player.state === PlayerState.Paused) {
|
|
55437
|
-
this._cursorUpdateTick(
|
|
55649
|
+
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
|
|
55438
55650
|
}
|
|
55439
|
-
this.tickPosition =
|
|
55651
|
+
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55440
55652
|
// set playback range
|
|
55441
55653
|
if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
|
|
55442
|
-
const
|
|
55654
|
+
const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
|
|
55655
|
+
const endBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat);
|
|
55656
|
+
const endBeatPlaybackEnd = endBeatPlaybackRange?.endTick ??
|
|
55657
|
+
this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
|
|
55443
55658
|
const range = new PlaybackRange();
|
|
55444
|
-
range.startTick =
|
|
55445
|
-
range.endTick =
|
|
55446
|
-
realMasterBarEnd +
|
|
55447
|
-
this._selectionEnd.beat.playbackStart +
|
|
55448
|
-
this._selectionEnd.beat.playbackDuration -
|
|
55449
|
-
50;
|
|
55659
|
+
range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55660
|
+
range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
|
|
55450
55661
|
this.playbackRange = range;
|
|
55451
55662
|
}
|
|
55452
55663
|
else {
|
|
@@ -58051,8 +58262,9 @@
|
|
|
58051
58262
|
break;
|
|
58052
58263
|
case 'alphaTab.renderScore':
|
|
58053
58264
|
this._updateFontSizes(data.fontSizes);
|
|
58265
|
+
const renderHints = data.renderHints;
|
|
58054
58266
|
const score = data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
|
|
58055
|
-
this._renderMultiple(score, data.trackIndexes);
|
|
58267
|
+
this._renderMultiple(score, data.trackIndexes, renderHints);
|
|
58056
58268
|
break;
|
|
58057
58269
|
case 'alphaTab.updateSettings':
|
|
58058
58270
|
this._updateSettings(data.settings);
|
|
@@ -61492,6 +61704,12 @@
|
|
|
61492
61704
|
_tieHeight = 0;
|
|
61493
61705
|
_boundingBox;
|
|
61494
61706
|
_shouldPaint = false;
|
|
61707
|
+
// Resolved per-label paint state. Lazily grown; re-layouts mutate
|
|
61708
|
+
// existing entries in place and update `_resolvedLabelCount` to
|
|
61709
|
+
// signal how many of them are valid this pass.
|
|
61710
|
+
_resolvedLabels = [];
|
|
61711
|
+
_resolvedLabelCount = 0;
|
|
61712
|
+
_labelBaselineOffset = 0;
|
|
61495
61713
|
get checkForOverflow() {
|
|
61496
61714
|
return this._shouldPaint && this._boundingBox !== undefined;
|
|
61497
61715
|
}
|
|
@@ -61561,16 +61779,88 @@
|
|
|
61561
61779
|
}
|
|
61562
61780
|
this._boundingBox = undefined;
|
|
61563
61781
|
this.y = Math.min(this._startY, this._endY);
|
|
61782
|
+
const down = this.tieDirection === BeamDirection.Down;
|
|
61564
61783
|
let tieBoundingBox;
|
|
61784
|
+
// Bezier control points for the tie. Computed once and reused
|
|
61785
|
+
// for both the bounding box (via _calculateActualTieHeightFromCps)
|
|
61786
|
+
// and label-apex sampling further below — avoids a redundant
|
|
61787
|
+
// call to _computeBezierControlPoints (and its 14-element array
|
|
61788
|
+
// allocation) per labeled slur per layout.
|
|
61789
|
+
let cps = [];
|
|
61565
61790
|
if (this.shouldDrawBendSlur()) {
|
|
61566
61791
|
this._tieHeight = 0;
|
|
61567
|
-
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY,
|
|
61792
|
+
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY, down, this.renderer.smuflMetrics.tieHeight);
|
|
61568
61793
|
}
|
|
61569
61794
|
else {
|
|
61570
61795
|
this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
|
|
61571
|
-
|
|
61796
|
+
const tieThickness = this.renderer.smuflMetrics.tieMidpointThickness;
|
|
61797
|
+
cps = TieGlyph._computeBezierControlPoints(1, this._startX, this._startY, this._endX, this._endY, down, this._tieHeight, tieThickness);
|
|
61798
|
+
tieBoundingBox = TieGlyph._calculateActualTieHeightFromCps(cps, this._startX, this._startY, this._endX, this._endY, down, tieThickness);
|
|
61572
61799
|
}
|
|
61573
61800
|
this._boundingBox = tieBoundingBox;
|
|
61801
|
+
this._resolvedLabelCount = 0;
|
|
61802
|
+
const labels = this.getSlurLabels();
|
|
61803
|
+
if (labels !== null && labels.length > 0 && this.shouldPaintLabels()) {
|
|
61804
|
+
const res = this.renderer.settings.display.resources;
|
|
61805
|
+
const padding = this.renderer.smuflMetrics.oneStaffSpace * 0.25;
|
|
61806
|
+
let maxTextHeight = 0;
|
|
61807
|
+
// Single Y line for all labels — the outer arc apex.
|
|
61808
|
+
// Painted offset adds `padding` on the outward side, so
|
|
61809
|
+
// every label sits the same fixed distance from its arc.
|
|
61810
|
+
const labelLineY = cps.length > 0
|
|
61811
|
+
? 0.125 * cps[7] + 0.375 * cps[9] + 0.375 * cps[11] + 0.125 * cps[13]
|
|
61812
|
+
: (this._startY + this._endY) / 2;
|
|
61813
|
+
for (const label of labels) {
|
|
61814
|
+
const fromX = this.resolveLabelAnchorX(label.fromNote);
|
|
61815
|
+
const toX = this.resolveLabelAnchorX(label.toNote);
|
|
61816
|
+
if (fromX === null || toX === null) {
|
|
61817
|
+
continue;
|
|
61818
|
+
}
|
|
61819
|
+
const midX = (fromX + toX) / 2;
|
|
61820
|
+
if (midX < this._startX || midX > this._endX) {
|
|
61821
|
+
continue;
|
|
61822
|
+
}
|
|
61823
|
+
// Per-element font.size as an upper bound on glyph
|
|
61824
|
+
// height — avoids per-label measureText calls. All H/P
|
|
61825
|
+
// and sl. labels use the same _effectFont, so this is
|
|
61826
|
+
// typically computed once.
|
|
61827
|
+
const font = res.getFontForNotationElement(label.element);
|
|
61828
|
+
if (font.size > maxTextHeight) {
|
|
61829
|
+
maxTextHeight = font.size;
|
|
61830
|
+
}
|
|
61831
|
+
// grow cache lazily; mutate existing slot in place otherwise
|
|
61832
|
+
let slot;
|
|
61833
|
+
if (this._resolvedLabelCount < this._resolvedLabels.length) {
|
|
61834
|
+
slot = this._resolvedLabels[this._resolvedLabelCount];
|
|
61835
|
+
slot.x = midX;
|
|
61836
|
+
slot.y = labelLineY;
|
|
61837
|
+
slot.text = label.text;
|
|
61838
|
+
slot.element = label.element;
|
|
61839
|
+
}
|
|
61840
|
+
else {
|
|
61841
|
+
slot = {
|
|
61842
|
+
x: midX,
|
|
61843
|
+
y: labelLineY,
|
|
61844
|
+
text: label.text,
|
|
61845
|
+
element: label.element
|
|
61846
|
+
};
|
|
61847
|
+
this._resolvedLabels.push(slot);
|
|
61848
|
+
}
|
|
61849
|
+
this._resolvedLabelCount++;
|
|
61850
|
+
}
|
|
61851
|
+
if (this._resolvedLabelCount > 0) {
|
|
61852
|
+
// canvas.textBaseline is 'hanging' (TextBaseline.Top), so
|
|
61853
|
+
// fillText positions `y` at the glyph's top edge.
|
|
61854
|
+
if (this.tieDirection === BeamDirection.Up) {
|
|
61855
|
+
tieBoundingBox.y -= maxTextHeight + padding;
|
|
61856
|
+
this._labelBaselineOffset = -(maxTextHeight + padding);
|
|
61857
|
+
}
|
|
61858
|
+
else {
|
|
61859
|
+
this._labelBaselineOffset = padding;
|
|
61860
|
+
}
|
|
61861
|
+
tieBoundingBox.h += maxTextHeight + padding;
|
|
61862
|
+
}
|
|
61863
|
+
}
|
|
61574
61864
|
this.height = tieBoundingBox.h;
|
|
61575
61865
|
if (this.tieDirection === BeamDirection.Up) {
|
|
61576
61866
|
// the tie might go above `this.y` due to its shape
|
|
@@ -61586,13 +61876,77 @@
|
|
|
61586
61876
|
if (!this._shouldPaint) {
|
|
61587
61877
|
return;
|
|
61588
61878
|
}
|
|
61879
|
+
const isDown = this.tieDirection === BeamDirection.Down;
|
|
61589
61880
|
if (this.shouldDrawBendSlur()) {
|
|
61590
|
-
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61881
|
+
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this.renderer.smuflMetrics.tieHeight);
|
|
61591
61882
|
}
|
|
61592
61883
|
else {
|
|
61593
|
-
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61884
|
+
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
|
|
61885
|
+
}
|
|
61886
|
+
if (this._resolvedLabelCount > 0) {
|
|
61887
|
+
const ta = canvas.textAlign;
|
|
61888
|
+
const tb = canvas.textBaseline;
|
|
61889
|
+
canvas.textAlign = TextAlign.Center;
|
|
61890
|
+
canvas.textBaseline = TextBaseline.Top;
|
|
61891
|
+
const res = this.renderer.resources;
|
|
61892
|
+
let lastElement = -1;
|
|
61893
|
+
for (let i = 0; i < this._resolvedLabelCount; i++) {
|
|
61894
|
+
const label = this._resolvedLabels[i];
|
|
61895
|
+
if (label.element !== lastElement) {
|
|
61896
|
+
canvas.font = res.getFontForNotationElement(label.element);
|
|
61897
|
+
lastElement = label.element;
|
|
61898
|
+
}
|
|
61899
|
+
canvas.fillText(label.text, cx + label.x, cy + label.y + this._labelBaselineOffset);
|
|
61900
|
+
}
|
|
61901
|
+
canvas.textAlign = ta;
|
|
61902
|
+
canvas.textBaseline = tb;
|
|
61594
61903
|
}
|
|
61595
61904
|
}
|
|
61905
|
+
/**
|
|
61906
|
+
* Returns the labels to paint along this slur, or `null` when there
|
|
61907
|
+
* are none. Override in subclasses.
|
|
61908
|
+
*/
|
|
61909
|
+
getSlurLabels() {
|
|
61910
|
+
return null;
|
|
61911
|
+
}
|
|
61912
|
+
/**
|
|
61913
|
+
* Whether label painting is enabled. Defaults to `true`. Subclasses
|
|
61914
|
+
* may override to disable labels on the bend-slur path or other
|
|
61915
|
+
* special cases.
|
|
61916
|
+
*/
|
|
61917
|
+
shouldPaintLabels() {
|
|
61918
|
+
return !this.shouldDrawBendSlur();
|
|
61919
|
+
}
|
|
61920
|
+
/**
|
|
61921
|
+
* Looks up the absolute X coordinate of an anchor note. Reuses
|
|
61922
|
+
* the start/end bar renderers already resolved by the subclass
|
|
61923
|
+
* (NoteTieGlyph) when the note's bar matches — most labels live
|
|
61924
|
+
* in the slur's start or end bar, so this avoids the double Map
|
|
61925
|
+
* lookup in `getRendererForBar` per label per layout. Returns
|
|
61926
|
+
* `null` when the note's bar is not rendered on this glyph's
|
|
61927
|
+
* staff (cross-system case).
|
|
61928
|
+
*/
|
|
61929
|
+
resolveLabelAnchorX(note) {
|
|
61930
|
+
const bar = note.beat.voice.bar;
|
|
61931
|
+
let renderer = null;
|
|
61932
|
+
const start = this.lookupStartBeatRenderer();
|
|
61933
|
+
if (start !== null && start.bar === bar) {
|
|
61934
|
+
renderer = start;
|
|
61935
|
+
}
|
|
61936
|
+
else {
|
|
61937
|
+
const end = this.lookupEndBeatRenderer();
|
|
61938
|
+
if (end !== null && end.bar === bar) {
|
|
61939
|
+
renderer = end;
|
|
61940
|
+
}
|
|
61941
|
+
else {
|
|
61942
|
+
renderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, bar);
|
|
61943
|
+
}
|
|
61944
|
+
}
|
|
61945
|
+
if (renderer === null) {
|
|
61946
|
+
return null;
|
|
61947
|
+
}
|
|
61948
|
+
return renderer.x + renderer.getNoteX(note, NoteXPosition.Center);
|
|
61949
|
+
}
|
|
61596
61950
|
getTieHeight(_startX, _startY, _endX, _endY) {
|
|
61597
61951
|
return this.renderer.smuflMetrics.tieHeight;
|
|
61598
61952
|
}
|
|
@@ -61611,11 +61965,18 @@
|
|
|
61611
61965
|
}
|
|
61612
61966
|
static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
|
|
61613
61967
|
const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
|
|
61968
|
+
return TieGlyph._calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size);
|
|
61969
|
+
}
|
|
61970
|
+
/**
|
|
61971
|
+
* Derives the bounding box for a tie from already-computed control
|
|
61972
|
+
* points. Splits the bbox math from cps generation so callers that
|
|
61973
|
+
* need BOTH cps and bbox (e.g. multi-label slur layout) avoid a
|
|
61974
|
+
* second call to `_computeBezierControlPoints`.
|
|
61975
|
+
*/
|
|
61976
|
+
static _calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size) {
|
|
61614
61977
|
if (cp.length === 0) {
|
|
61615
61978
|
return new Bounds(x1, y1, x2 - x1, y2 - y1);
|
|
61616
61979
|
}
|
|
61617
|
-
// For a musical tie/slur, the extrema occur predictably near the midpoint
|
|
61618
|
-
// Evaluate at midpoint (t=0.5) and check endpoints
|
|
61619
61980
|
const p0x = cp[0];
|
|
61620
61981
|
const p0y = cp[1];
|
|
61621
61982
|
const c1x = cp[2];
|
|
@@ -61624,15 +61985,12 @@
|
|
|
61624
61985
|
const c2y = cp[5];
|
|
61625
61986
|
const p1x = cp[6];
|
|
61626
61987
|
const p1y = cp[7];
|
|
61627
|
-
// Evaluate at t=0.5 for midpoint
|
|
61628
61988
|
const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
|
|
61629
61989
|
const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
|
|
61630
|
-
// Bounds are simply min/max of start, end, and midpoint
|
|
61631
61990
|
const xMin = Math.min(p0x, p1x, midX);
|
|
61632
61991
|
const xMax = Math.max(p0x, p1x, midX);
|
|
61633
61992
|
let yMin = Math.min(p0y, p1y, midY);
|
|
61634
61993
|
let yMax = Math.max(p0y, p1y, midY);
|
|
61635
|
-
// Account for thickness of the tie/slur
|
|
61636
61994
|
if (down) {
|
|
61637
61995
|
yMax += size;
|
|
61638
61996
|
}
|
|
@@ -62925,6 +63283,15 @@
|
|
|
62925
63283
|
}
|
|
62926
63284
|
return false;
|
|
62927
63285
|
}
|
|
63286
|
+
/**
|
|
63287
|
+
* The fixed-overhead width of this renderer: glyphs that do not stretch when
|
|
63288
|
+
* the bar is scaled (clef, key signature, time signature, barlines, courtesy
|
|
63289
|
+
* accidentals, etc). Treated as a fixed allocation by the system-level layout
|
|
63290
|
+
* before distributing remaining width across bars by {@link Bar.displayScale}.
|
|
63291
|
+
*/
|
|
63292
|
+
get fixedOverhead() {
|
|
63293
|
+
return this._preBeatGlyphs.width + this._postBeatGlyphs.width;
|
|
63294
|
+
}
|
|
62928
63295
|
scaleToWidth(width) {
|
|
62929
63296
|
// preBeat and postBeat glyphs do not get resized
|
|
62930
63297
|
const containerWidth = width - this._preBeatGlyphs.width - this._postBeatGlyphs.width;
|
|
@@ -65483,6 +65850,22 @@
|
|
|
65483
65850
|
postBeatSize = 0;
|
|
65484
65851
|
minStretchForce = 0;
|
|
65485
65852
|
totalSpringConstant = 0;
|
|
65853
|
+
/**
|
|
65854
|
+
* The smallest note duration encountered within this bar's springs, used as the reference in
|
|
65855
|
+
* the Gourlay stretch formula. Read by the owning {@link StaffSystem} so that the system can
|
|
65856
|
+
* aggregate a shared minimum across all bars and trigger a reconcile if an added bar introduces
|
|
65857
|
+
* a shorter duration than previously seen.
|
|
65858
|
+
*/
|
|
65859
|
+
get localMinDuration() {
|
|
65860
|
+
return this._minDuration;
|
|
65861
|
+
}
|
|
65862
|
+
/**
|
|
65863
|
+
* The minimum-duration reference against which the spring constants currently held by this info
|
|
65864
|
+
* were computed. Set by {@link finish} and {@link recomputeSpringConstants}. The owning
|
|
65865
|
+
* StaffSystem compares this against its system-wide minimum to decide whether spring constants
|
|
65866
|
+
* need re-derivation.
|
|
65867
|
+
*/
|
|
65868
|
+
computedWithMinDuration = 0;
|
|
65486
65869
|
_updateMinStretchForce(force) {
|
|
65487
65870
|
if (this.minStretchForce < force) {
|
|
65488
65871
|
this.minStretchForce = force;
|
|
@@ -65652,10 +66035,26 @@
|
|
|
65652
66035
|
this._incompleteGraceRodsWidth += sp.preBeatWidth + sp.postSpringWidth;
|
|
65653
66036
|
}
|
|
65654
66037
|
}
|
|
65655
|
-
this._calculateSpringConstants();
|
|
66038
|
+
this._calculateSpringConstants(this._minDuration);
|
|
66039
|
+
this.computedWithMinDuration = this._minDuration;
|
|
65656
66040
|
this.version++;
|
|
65657
66041
|
}
|
|
65658
|
-
|
|
66042
|
+
/**
|
|
66043
|
+
* Re-derives the spring constants (and {@link minStretchForce} / {@link totalSpringConstant})
|
|
66044
|
+
* using a caller-supplied minimum-duration reference rather than this bar's local minimum.
|
|
66045
|
+
*
|
|
66046
|
+
* Called by {@link StaffSystem.reconcileMinDurationIfDirty} when a bar added later to the
|
|
66047
|
+
* system introduced a shorter note than previously seen, invalidating this bar's spring
|
|
66048
|
+
* constants. Grace-rod data is not recomputed — it is independent of the minimum-duration
|
|
66049
|
+
* reference. The internal {@link version} is bumped so downstream consumers (e.g.
|
|
66050
|
+
* {@link BarRendererBase.applyLayoutingInfo}) pick up the refreshed positions.
|
|
66051
|
+
*/
|
|
66052
|
+
recomputeSpringConstants(minDuration) {
|
|
66053
|
+
this._calculateSpringConstants(minDuration);
|
|
66054
|
+
this.computedWithMinDuration = minDuration;
|
|
66055
|
+
this.version++;
|
|
66056
|
+
}
|
|
66057
|
+
_calculateSpringConstants(minDuration) {
|
|
65659
66058
|
let totalSpringConstant = 0;
|
|
65660
66059
|
const sortedSprings = this._timeSortedSprings;
|
|
65661
66060
|
if (sortedSprings.length === 0) {
|
|
@@ -65673,7 +66072,7 @@
|
|
|
65673
66072
|
const nextSpring = sortedSprings[i + 1];
|
|
65674
66073
|
duration = Math.abs(nextSpring.timePosition - currentSpring.timePosition);
|
|
65675
66074
|
}
|
|
65676
|
-
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration);
|
|
66075
|
+
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration, minDuration);
|
|
65677
66076
|
totalSpringConstant += 1 / currentSpring.springConstant;
|
|
65678
66077
|
}
|
|
65679
66078
|
this.totalSpringConstant = 1 / totalSpringConstant;
|
|
@@ -65733,7 +66132,7 @@
|
|
|
65733
66132
|
// springX += this.calculateWidth(force, spring.springConstant);
|
|
65734
66133
|
// }
|
|
65735
66134
|
// }
|
|
65736
|
-
_calculateSpringConstant(spring, duration) {
|
|
66135
|
+
_calculateSpringConstant(spring, duration, minDuration) {
|
|
65737
66136
|
if (duration <= 0) {
|
|
65738
66137
|
duration = MidiUtils.toTicks(Duration.TwoHundredFiftySixth);
|
|
65739
66138
|
}
|
|
@@ -65741,7 +66140,6 @@
|
|
|
65741
66140
|
spring.smallestDuration = duration;
|
|
65742
66141
|
}
|
|
65743
66142
|
const smallestDuration = spring.smallestDuration;
|
|
65744
|
-
const minDuration = this._minDuration;
|
|
65745
66143
|
const minDurationWidth = BarLayoutingInfo._defaultMinDurationWidth;
|
|
65746
66144
|
const phi = 1 + 0.85 * Math.log2(duration / minDuration);
|
|
65747
66145
|
return (smallestDuration / duration) * (1 / (phi * minDurationWidth));
|
|
@@ -65804,6 +66202,18 @@
|
|
|
65804
66202
|
canWrap = true;
|
|
65805
66203
|
masterBar;
|
|
65806
66204
|
additionalMultiBarRestIndexes = null;
|
|
66205
|
+
/**
|
|
66206
|
+
* Max fixed overhead (prefix + postfix glyph width) across all staves of this bar.
|
|
66207
|
+
* Used by the layout-mode horizontal scaling pass to carve out the fixed-overhead bucket
|
|
66208
|
+
* before distributing staff width across bars.
|
|
66209
|
+
*/
|
|
66210
|
+
maxFixedOverhead = 0;
|
|
66211
|
+
/**
|
|
66212
|
+
* Max natural content width (computedWidth - fixedOverhead) across all staves of this bar.
|
|
66213
|
+
* Used as the bar weight when the layout ignores {@link MasterBar.displayScale} (e.g.
|
|
66214
|
+
* Page layout with `SystemsLayoutMode.Automatic`).
|
|
66215
|
+
*/
|
|
66216
|
+
maxContentWidth = 0;
|
|
65807
66217
|
get lastMasterBarIndex() {
|
|
65808
66218
|
if (this.additionalMultiBarRestIndexes) {
|
|
65809
66219
|
return this.additionalMultiBarRestIndexes[this.additionalMultiBarRestIndexes.length - 1];
|
|
@@ -65985,6 +66395,45 @@
|
|
|
65985
66395
|
* This value is mainly used in the parchment style layout for correct scaling of the bars.
|
|
65986
66396
|
*/
|
|
65987
66397
|
totalBarDisplayScale = 0;
|
|
66398
|
+
/**
|
|
66399
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxFixedOverhead} across the system. The layout-mode
|
|
66400
|
+
* horizontal scaling pass subtracts this from the available staff width before distributing the
|
|
66401
|
+
* remainder across bars.
|
|
66402
|
+
*/
|
|
66403
|
+
totalFixedOverhead = 0;
|
|
66404
|
+
/**
|
|
66405
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxContentWidth} across the system. Used as the
|
|
66406
|
+
* denominator when distributing staff width in modes that weight bars by natural content width
|
|
66407
|
+
* (Page layout with `SystemsLayoutMode.Automatic`).
|
|
66408
|
+
*/
|
|
66409
|
+
totalContentWidth = 0;
|
|
66410
|
+
/**
|
|
66411
|
+
* Shortest note duration (in ticks) across every bar that has been added to this system, used
|
|
66412
|
+
* as the common reference in the Gourlay stretch formula so that rhythmically-equivalent beats
|
|
66413
|
+
* in different bars of the same system align column-wise.
|
|
66414
|
+
*
|
|
66415
|
+
* `-1` means "no bar added yet". The value only moves downward during system assembly; when a
|
|
66416
|
+
* new bar introduces a shorter minimum, {@link isMinDurationDirty} is set so that
|
|
66417
|
+
* {@link reconcileMinDurationIfDirty} can re-derive spring constants on the previously-added
|
|
66418
|
+
* bars before layout distribution runs.
|
|
66419
|
+
*/
|
|
66420
|
+
minDuration = -1;
|
|
66421
|
+
/**
|
|
66422
|
+
* Set when a bar added to this system introduced a shorter {@link minDuration} than previously
|
|
66423
|
+
* seen, leaving earlier bars' spring constants stale. Consumed by
|
|
66424
|
+
* {@link reconcileMinDurationIfDirty} which is called from `VerticalLayoutBase._fitSystem`
|
|
66425
|
+
* once the system is fully assembled.
|
|
66426
|
+
*/
|
|
66427
|
+
isMinDurationDirty = false;
|
|
66428
|
+
/**
|
|
66429
|
+
* Whether this system coordinates a shared minimum-duration reference across its bars for the
|
|
66430
|
+
* Gourlay stretch formula. Defaults to `true` for page-style and parchment layouts where bars
|
|
66431
|
+
* of a system fight for a common staff width. Set to `false` for horizontal layouts where each
|
|
66432
|
+
* bar is sized independently (by `bar.displayWidth` or its intrinsic width) and there is no
|
|
66433
|
+
* column-alignment concern - each bar keeps its local minimum so pre-existing rendering is
|
|
66434
|
+
* preserved.
|
|
66435
|
+
*/
|
|
66436
|
+
shareMinDurationAcrossBars = true;
|
|
65988
66437
|
isLast = false;
|
|
65989
66438
|
masterBarsRenderers = [];
|
|
65990
66439
|
staves = [];
|
|
@@ -66046,6 +66495,9 @@
|
|
|
66046
66495
|
}
|
|
66047
66496
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66048
66497
|
this._calculateAccoladeSpacing(tracks);
|
|
66498
|
+
// On the resize path the layoutingInfo was finalized in a previous layout pass, so we
|
|
66499
|
+
// only need to check whether its min-duration reference still matches the new system's.
|
|
66500
|
+
this._trackSystemMinDuration(renderers.layoutingInfo);
|
|
66049
66501
|
this._applyLayoutAndUpdateWidth();
|
|
66050
66502
|
return renderers;
|
|
66051
66503
|
}
|
|
@@ -66101,10 +66553,89 @@
|
|
|
66101
66553
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66102
66554
|
this._calculateAccoladeSpacing(tracks);
|
|
66103
66555
|
barLayoutingInfo.finish();
|
|
66556
|
+
// Reconcile against the system-wide minimum-duration reference now that springs are
|
|
66557
|
+
// finalized. If this bar introduced a shorter note, earlier bars become stale (flagged
|
|
66558
|
+
// for bulk reconcile at fit time). If the system already had a shorter min than this
|
|
66559
|
+
// bar's local one, this bar's spring constants are recomputed immediately so the width
|
|
66560
|
+
// we return below reflects the shared reference.
|
|
66561
|
+
this._trackSystemMinDuration(barLayoutingInfo);
|
|
66104
66562
|
// ensure same widths of new renderer
|
|
66105
66563
|
result.width = this._applyLayoutAndUpdateWidth();
|
|
66106
66564
|
return result;
|
|
66107
66565
|
}
|
|
66566
|
+
/**
|
|
66567
|
+
* Updates {@link minDuration} and {@link isMinDurationDirty} when a bar is added, and brings
|
|
66568
|
+
* the just-added bar's {@link BarLayoutingInfo} in line with the current system minimum if the
|
|
66569
|
+
* system already saw a shorter reference. The bulk reconcile over previously-added bars is
|
|
66570
|
+
* deferred to {@link reconcileMinDurationIfDirty} (called from `_fitSystem`) to avoid
|
|
66571
|
+
* re-iterating the system every time a bar is appended.
|
|
66572
|
+
*/
|
|
66573
|
+
_trackSystemMinDuration(info) {
|
|
66574
|
+
if (!this.shareMinDurationAcrossBars) {
|
|
66575
|
+
return;
|
|
66576
|
+
}
|
|
66577
|
+
const localMin = info.localMinDuration;
|
|
66578
|
+
if (this.minDuration === -1 || localMin < this.minDuration) {
|
|
66579
|
+
// this bar shortens the system minimum; earlier bars (if any) are now stale
|
|
66580
|
+
if (this.masterBarsRenderers.length > 1 && localMin !== this.minDuration) {
|
|
66581
|
+
this.isMinDurationDirty = true;
|
|
66582
|
+
}
|
|
66583
|
+
this.minDuration = localMin;
|
|
66584
|
+
}
|
|
66585
|
+
if (info.computedWithMinDuration > this.minDuration) {
|
|
66586
|
+
// this bar was initialized against a larger (local) min than the system carries; pull
|
|
66587
|
+
// it down to the system reference so its computedWidth reflects the shared spacing.
|
|
66588
|
+
info.recomputeSpringConstants(this.minDuration);
|
|
66589
|
+
}
|
|
66590
|
+
}
|
|
66591
|
+
/**
|
|
66592
|
+
* Re-derives spring constants on bars whose {@link BarLayoutingInfo.computedWithMinDuration}
|
|
66593
|
+
* is out of sync with the current {@link minDuration}, and rebuilds the cached system totals
|
|
66594
|
+
* (widths, {@link totalFixedOverhead}, {@link totalContentWidth}) from the refreshed bar
|
|
66595
|
+
* widths. Called from `VerticalLayoutBase._fitSystem` after the system is fully assembled and
|
|
66596
|
+
* before distribution runs. No-op when {@link isMinDurationDirty} is false.
|
|
66597
|
+
*/
|
|
66598
|
+
reconcileMinDurationIfDirty() {
|
|
66599
|
+
if (!this.isMinDurationDirty) {
|
|
66600
|
+
return;
|
|
66601
|
+
}
|
|
66602
|
+
let systemWidth = this.accoladeWidth;
|
|
66603
|
+
let totalFixedOverhead = 0;
|
|
66604
|
+
let totalContentWidth = 0;
|
|
66605
|
+
for (const mb of this.masterBarsRenderers) {
|
|
66606
|
+
if (mb.layoutingInfo.computedWithMinDuration > this.minDuration) {
|
|
66607
|
+
mb.layoutingInfo.recomputeSpringConstants(this.minDuration);
|
|
66608
|
+
}
|
|
66609
|
+
let maxPrefix = 0;
|
|
66610
|
+
let maxContent = 0;
|
|
66611
|
+
let realWidth = 0;
|
|
66612
|
+
for (const r of mb.renderers) {
|
|
66613
|
+
r.applyLayoutingInfo();
|
|
66614
|
+
if (r.computedWidth > realWidth) {
|
|
66615
|
+
realWidth = r.computedWidth;
|
|
66616
|
+
}
|
|
66617
|
+
const overhead = r.fixedOverhead;
|
|
66618
|
+
if (overhead > maxPrefix) {
|
|
66619
|
+
maxPrefix = overhead;
|
|
66620
|
+
}
|
|
66621
|
+
const content = Math.max(0, r.computedWidth - overhead);
|
|
66622
|
+
if (content > maxContent) {
|
|
66623
|
+
maxContent = content;
|
|
66624
|
+
}
|
|
66625
|
+
}
|
|
66626
|
+
mb.maxFixedOverhead = maxPrefix;
|
|
66627
|
+
mb.maxContentWidth = maxContent;
|
|
66628
|
+
mb.width = realWidth;
|
|
66629
|
+
systemWidth += realWidth;
|
|
66630
|
+
totalFixedOverhead += maxPrefix;
|
|
66631
|
+
totalContentWidth += maxContent;
|
|
66632
|
+
}
|
|
66633
|
+
this.width = systemWidth;
|
|
66634
|
+
this.computedWidth = systemWidth;
|
|
66635
|
+
this.totalFixedOverhead = totalFixedOverhead;
|
|
66636
|
+
this.totalContentWidth = totalContentWidth;
|
|
66637
|
+
this.isMinDurationDirty = false;
|
|
66638
|
+
}
|
|
66108
66639
|
getBarDisplayScale(renderer) {
|
|
66109
66640
|
return this.staves.length > 1 ? renderer.bar.masterBar.displayScale : renderer.bar.displayScale;
|
|
66110
66641
|
}
|
|
@@ -66143,12 +66674,16 @@
|
|
|
66143
66674
|
this.width -= width;
|
|
66144
66675
|
this.computedWidth -= width;
|
|
66145
66676
|
this.totalBarDisplayScale -= barDisplayScale;
|
|
66677
|
+
this.totalFixedOverhead -= toRemove.maxFixedOverhead;
|
|
66678
|
+
this.totalContentWidth -= toRemove.maxContentWidth;
|
|
66146
66679
|
return toRemove;
|
|
66147
66680
|
}
|
|
66148
66681
|
return null;
|
|
66149
66682
|
}
|
|
66150
66683
|
_applyLayoutAndUpdateWidth() {
|
|
66151
66684
|
let realWidth = 0;
|
|
66685
|
+
let maxFixedOverhead = 0;
|
|
66686
|
+
let maxContentWidth = 0;
|
|
66152
66687
|
let barDisplayScale = 0;
|
|
66153
66688
|
for (const s of this.allStaves) {
|
|
66154
66689
|
const last = s.barRenderers[s.barRenderers.length - 1];
|
|
@@ -66157,8 +66692,21 @@
|
|
|
66157
66692
|
if (last.computedWidth > realWidth) {
|
|
66158
66693
|
realWidth = last.computedWidth;
|
|
66159
66694
|
}
|
|
66695
|
+
const overhead = last.fixedOverhead;
|
|
66696
|
+
if (overhead > maxFixedOverhead) {
|
|
66697
|
+
maxFixedOverhead = overhead;
|
|
66698
|
+
}
|
|
66699
|
+
const content = Math.max(0, last.computedWidth - overhead);
|
|
66700
|
+
if (content > maxContentWidth) {
|
|
66701
|
+
maxContentWidth = content;
|
|
66702
|
+
}
|
|
66160
66703
|
}
|
|
66704
|
+
const renderers = this.masterBarsRenderers[this.masterBarsRenderers.length - 1];
|
|
66705
|
+
renderers.maxFixedOverhead = maxFixedOverhead;
|
|
66706
|
+
renderers.maxContentWidth = maxContentWidth;
|
|
66161
66707
|
this.totalBarDisplayScale += barDisplayScale;
|
|
66708
|
+
this.totalFixedOverhead += maxFixedOverhead;
|
|
66709
|
+
this.totalContentWidth += maxContentWidth;
|
|
66162
66710
|
this.width += realWidth;
|
|
66163
66711
|
this.computedWidth += realWidth;
|
|
66164
66712
|
return realWidth;
|
|
@@ -66656,17 +67204,6 @@
|
|
|
66656
67204
|
}
|
|
66657
67205
|
}
|
|
66658
67206
|
|
|
66659
|
-
/**
|
|
66660
|
-
* @internal
|
|
66661
|
-
*/
|
|
66662
|
-
class LazyPartial {
|
|
66663
|
-
args;
|
|
66664
|
-
renderCallback;
|
|
66665
|
-
constructor(args, renderCallback) {
|
|
66666
|
-
this.args = args;
|
|
66667
|
-
this.renderCallback = renderCallback;
|
|
66668
|
-
}
|
|
66669
|
-
}
|
|
66670
67207
|
/**
|
|
66671
67208
|
* This is the base class for creating new layouting engines for the score renderer.
|
|
66672
67209
|
* @internal
|
|
@@ -66697,15 +67234,21 @@
|
|
|
66697
67234
|
this.doResize();
|
|
66698
67235
|
}
|
|
66699
67236
|
layoutAndRender(renderHints) {
|
|
66700
|
-
this._lazyPartials.clear();
|
|
66701
67237
|
this.slurRegistry.clear();
|
|
66702
|
-
this.beamingRuleLookups.clear();
|
|
66703
|
-
this._barRendererLookup.clear();
|
|
66704
|
-
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66705
67238
|
const score = this.renderer.score;
|
|
66706
67239
|
this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
|
|
66707
67240
|
this.lastBarIndex = ModelUtils.computeLastDisplayedBarIndex(score, this.renderer.settings, this.firstBarIndex);
|
|
66708
67241
|
this.multiBarRestInfo = ModelUtils.buildMultiBarRestInfo(this.renderer.tracks, this.firstBarIndex, this.lastBarIndex);
|
|
67242
|
+
const firstChangedMasterBar = renderHints?.firstChangedMasterBar;
|
|
67243
|
+
if (firstChangedMasterBar !== undefined) {
|
|
67244
|
+
if (this.doUpdateForBars(renderHints)) {
|
|
67245
|
+
return;
|
|
67246
|
+
}
|
|
67247
|
+
}
|
|
67248
|
+
this._lazyPartials.clear();
|
|
67249
|
+
this.beamingRuleLookups.clear();
|
|
67250
|
+
this._barRendererLookup.clear();
|
|
67251
|
+
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66709
67252
|
this.pagePadding = this.renderer.settings.display.padding.map(p => p / this.renderer.settings.display.scale);
|
|
66710
67253
|
if (!this.pagePadding) {
|
|
66711
67254
|
this.pagePadding = [0, 0, 0, 0];
|
|
@@ -66720,6 +67263,9 @@
|
|
|
66720
67263
|
this.doLayoutAndRender(renderHints);
|
|
66721
67264
|
}
|
|
66722
67265
|
_lazyPartials = new Map();
|
|
67266
|
+
getExistingPartialArgs(id) {
|
|
67267
|
+
return this._lazyPartials.has(id) ? this._lazyPartials.get(id).args : undefined;
|
|
67268
|
+
}
|
|
66723
67269
|
registerPartial(args, callback) {
|
|
66724
67270
|
if (args.height === 0) {
|
|
66725
67271
|
return;
|
|
@@ -66738,7 +67284,11 @@
|
|
|
66738
67284
|
}
|
|
66739
67285
|
else {
|
|
66740
67286
|
// in case of lazy loading -> first register lazy, then notify
|
|
66741
|
-
|
|
67287
|
+
const partial = {
|
|
67288
|
+
args,
|
|
67289
|
+
renderCallback: callback
|
|
67290
|
+
};
|
|
67291
|
+
this._lazyPartials.set(args.id, partial);
|
|
66742
67292
|
this.renderer.partialLayoutFinished.trigger(args);
|
|
66743
67293
|
}
|
|
66744
67294
|
}
|
|
@@ -67033,7 +67583,7 @@
|
|
|
67033
67583
|
glyph.textAlign = TextAlign.Left;
|
|
67034
67584
|
}
|
|
67035
67585
|
}
|
|
67036
|
-
|
|
67586
|
+
_layoutAndRenderAnnotation(y) {
|
|
67037
67587
|
// attention, you are not allowed to remove change this notice within any version of this library without permission!
|
|
67038
67588
|
const msg = 'rendered by alphaTab';
|
|
67039
67589
|
const resources = this.renderer.settings.display.resources;
|
|
@@ -67097,6 +67647,12 @@
|
|
|
67097
67647
|
}
|
|
67098
67648
|
doResize() {
|
|
67099
67649
|
}
|
|
67650
|
+
doUpdateForBars(_renderHints) {
|
|
67651
|
+
// not supported yet, modifications likely cause anyhow full updates
|
|
67652
|
+
// as we do not optimize effect bands yet. with effect bands being more
|
|
67653
|
+
// isolated in bars we could try updating dynamically
|
|
67654
|
+
return false;
|
|
67655
|
+
}
|
|
67100
67656
|
doLayoutAndRender(renderHints) {
|
|
67101
67657
|
const score = this.renderer.score;
|
|
67102
67658
|
let startIndex = this.renderer.settings.display.startBar;
|
|
@@ -67110,6 +67666,11 @@
|
|
|
67110
67666
|
endBarIndex = startIndex + endBarIndex - 1; // map count to array index
|
|
67111
67667
|
endBarIndex = Math.min(score.masterBars.length - 1, Math.max(0, endBarIndex));
|
|
67112
67668
|
this._system = this.createEmptyStaffSystem(0);
|
|
67669
|
+
// Each bar in horizontal layout is sized independently (by bar.displayWidth or the bar's
|
|
67670
|
+
// intrinsic width), so there is no shared staff width to distribute across bars. Keep each
|
|
67671
|
+
// bar's spring constants referenced against its own local minimum-duration so rendering
|
|
67672
|
+
// matches the historical per-bar behaviour.
|
|
67673
|
+
this._system.shareMinDurationAcrossBars = false;
|
|
67113
67674
|
this._system.isLast = true;
|
|
67114
67675
|
this._system.x = this.pagePadding[0];
|
|
67115
67676
|
this._system.y = this.pagePadding[1];
|
|
@@ -67171,7 +67732,7 @@
|
|
|
67171
67732
|
currentBarIndex += partial.masterBars.length;
|
|
67172
67733
|
}
|
|
67173
67734
|
this.height = this.layoutAndRenderBottomScoreInfo(this.height);
|
|
67174
|
-
this.height = this.
|
|
67735
|
+
this.height = this._layoutAndRenderAnnotation(this.height);
|
|
67175
67736
|
this.height += this.pagePadding[3];
|
|
67176
67737
|
this.height *= this.renderer.settings.display.scale;
|
|
67177
67738
|
}
|
|
@@ -67238,11 +67799,16 @@
|
|
|
67238
67799
|
_allMasterBarRenderers = [];
|
|
67239
67800
|
_barsFromPreviousSystem = [];
|
|
67240
67801
|
_reuseViewPort = false;
|
|
67802
|
+
_preSystemPartialIds = [];
|
|
67803
|
+
_systemPartialIds = [];
|
|
67241
67804
|
doLayoutAndRender(renderHints) {
|
|
67242
67805
|
let y = this.pagePadding[1];
|
|
67243
67806
|
this.width = this.renderer.width;
|
|
67244
67807
|
this._allMasterBarRenderers = [];
|
|
67808
|
+
this._preSystemPartialIds = [];
|
|
67809
|
+
this._systemPartialIds = [];
|
|
67245
67810
|
this._reuseViewPort = renderHints?.reuseViewport ?? false;
|
|
67811
|
+
this._systems = [];
|
|
67246
67812
|
//
|
|
67247
67813
|
// 1. Score Info
|
|
67248
67814
|
y = this._layoutAndRenderScoreInfo(y, -1);
|
|
@@ -67254,15 +67820,23 @@
|
|
|
67254
67820
|
y = this._layoutAndRenderChordDiagrams(y, -1);
|
|
67255
67821
|
//
|
|
67256
67822
|
// 4. One result per StaffSystem
|
|
67257
|
-
y = this._layoutAndRenderScore(y);
|
|
67823
|
+
y = this._layoutAndRenderScore(y, this.firstBarIndex);
|
|
67258
67824
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67259
|
-
y = this.
|
|
67825
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67260
67826
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67261
67827
|
}
|
|
67262
67828
|
registerPartial(args, callback) {
|
|
67263
67829
|
args.reuseViewport = this._reuseViewPort;
|
|
67264
67830
|
super.registerPartial(args, callback);
|
|
67265
67831
|
}
|
|
67832
|
+
reregisterPartial(id) {
|
|
67833
|
+
const args = this.getExistingPartialArgs(id);
|
|
67834
|
+
if (!args) {
|
|
67835
|
+
return;
|
|
67836
|
+
}
|
|
67837
|
+
args.reuseViewport = this._reuseViewPort;
|
|
67838
|
+
this.renderer.partialLayoutFinished.trigger(args);
|
|
67839
|
+
}
|
|
67266
67840
|
get supportsResize() {
|
|
67267
67841
|
return true;
|
|
67268
67842
|
}
|
|
@@ -67273,6 +67847,47 @@
|
|
|
67273
67847
|
}
|
|
67274
67848
|
return x;
|
|
67275
67849
|
}
|
|
67850
|
+
doUpdateForBars(renderHints) {
|
|
67851
|
+
this._reuseViewPort = renderHints.reuseViewport ?? false;
|
|
67852
|
+
const firstModifiedMasterBar = renderHints.firstChangedMasterBar;
|
|
67853
|
+
// first update existing systems as needed
|
|
67854
|
+
const systemIndex = this._systems.findIndex(s => {
|
|
67855
|
+
const first = s.masterBarsRenderers[0].masterBar.index;
|
|
67856
|
+
const last = s.masterBarsRenderers[s.masterBarsRenderers.length - 1].masterBar.index;
|
|
67857
|
+
return first <= firstModifiedMasterBar && firstModifiedMasterBar <= last;
|
|
67858
|
+
});
|
|
67859
|
+
if (systemIndex === -1 || !this.renderer.settings.core.enableLazyLoading) {
|
|
67860
|
+
return false;
|
|
67861
|
+
}
|
|
67862
|
+
// Bars from the start of the re-layouted system onward will be re-registered during the
|
|
67863
|
+
// paint pass. Clear their old entries from the preserved BoundsLookup so registration
|
|
67864
|
+
// produces a clean, complete lookup after this render finishes.
|
|
67865
|
+
const firstRebuiltBarIndex = this._systems[systemIndex].masterBarsRenderers[0].masterBar.index;
|
|
67866
|
+
this.renderer.boundsLookup.clearFromMasterBar(firstRebuiltBarIndex);
|
|
67867
|
+
// for now we do a full relayout from the first modified masterbar
|
|
67868
|
+
// there is a lot of room for even more performant updates, but they come
|
|
67869
|
+
// at a risk that features break.
|
|
67870
|
+
// e.g. we could only shift systems where the content didn't change,
|
|
67871
|
+
// but we might still have ties/slurs which have to be updated.
|
|
67872
|
+
const removeSystems = this._systems.splice(systemIndex, this._systems.length - systemIndex);
|
|
67873
|
+
this._systemPartialIds.splice(systemIndex, this._systemPartialIds.length - systemIndex);
|
|
67874
|
+
const system = removeSystems[0];
|
|
67875
|
+
let y = system.y;
|
|
67876
|
+
const firstBarIndex = system.masterBarsRenderers[0].masterBar.index;
|
|
67877
|
+
// signal all partials which didn't change
|
|
67878
|
+
for (const preSystemPartial of this._preSystemPartialIds) {
|
|
67879
|
+
this.reregisterPartial(preSystemPartial);
|
|
67880
|
+
}
|
|
67881
|
+
for (let i = 0; i < systemIndex; i++) {
|
|
67882
|
+
this.reregisterPartial(this._systemPartialIds[i]);
|
|
67883
|
+
}
|
|
67884
|
+
// new partials for all other prats
|
|
67885
|
+
y = this._layoutAndRenderScore(y, firstBarIndex);
|
|
67886
|
+
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67887
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67888
|
+
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67889
|
+
return true;
|
|
67890
|
+
}
|
|
67276
67891
|
doResize() {
|
|
67277
67892
|
let y = this.pagePadding[1];
|
|
67278
67893
|
this.width = this.renderer.width;
|
|
@@ -67291,7 +67906,7 @@
|
|
|
67291
67906
|
// 4. One result per StaffSystem
|
|
67292
67907
|
y = this._resizeAndRenderScore(y, oldHeight);
|
|
67293
67908
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67294
|
-
y = this.
|
|
67909
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67295
67910
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67296
67911
|
}
|
|
67297
67912
|
_layoutAndRenderTunings(y, totalHeight = -1) {
|
|
@@ -67315,6 +67930,7 @@
|
|
|
67315
67930
|
canvas.textAlign = TextAlign.Center;
|
|
67316
67931
|
this.tuningGlyph.paint(0, 0, canvas);
|
|
67317
67932
|
});
|
|
67933
|
+
this._preSystemPartialIds.push(e.id);
|
|
67318
67934
|
return y + tuningHeight;
|
|
67319
67935
|
}
|
|
67320
67936
|
_layoutAndRenderChordDiagrams(y, totalHeight = -1) {
|
|
@@ -67338,6 +67954,7 @@
|
|
|
67338
67954
|
canvas.textAlign = TextAlign.Center;
|
|
67339
67955
|
this.chordDiagrams.paint(0, 0, canvas);
|
|
67340
67956
|
});
|
|
67957
|
+
this._preSystemPartialIds.push(e.id);
|
|
67341
67958
|
return y + diagramHeight;
|
|
67342
67959
|
}
|
|
67343
67960
|
_layoutAndRenderScoreInfo(y, totalHeight = -1) {
|
|
@@ -67380,12 +67997,14 @@
|
|
|
67380
67997
|
g.paint(0, 0, canvas);
|
|
67381
67998
|
}
|
|
67382
67999
|
});
|
|
68000
|
+
this._preSystemPartialIds.push(e.id);
|
|
67383
68001
|
}
|
|
67384
68002
|
return y + infoHeight;
|
|
67385
68003
|
}
|
|
67386
68004
|
_resizeAndRenderScore(y, oldHeight) {
|
|
67387
68005
|
// if we have a fixed number of bars per row, we only need to refit them.
|
|
67388
68006
|
const barsPerRowActive = this.getBarsPerSystem(0) > 0;
|
|
68007
|
+
this._systemPartialIds = [];
|
|
67389
68008
|
if (barsPerRowActive) {
|
|
67390
68009
|
for (let i = 0; i < this._systems.length; i++) {
|
|
67391
68010
|
const system = this._systems[i];
|
|
@@ -67448,11 +68067,9 @@
|
|
|
67448
68067
|
}
|
|
67449
68068
|
return y;
|
|
67450
68069
|
}
|
|
67451
|
-
_layoutAndRenderScore(y) {
|
|
67452
|
-
const startIndex = this.firstBarIndex;
|
|
68070
|
+
_layoutAndRenderScore(y, startIndex) {
|
|
67453
68071
|
let currentBarIndex = startIndex;
|
|
67454
68072
|
const endBarIndex = this.lastBarIndex;
|
|
67455
|
-
this._systems = [];
|
|
67456
68073
|
while (currentBarIndex <= endBarIndex) {
|
|
67457
68074
|
// create system and align set proper coordinates
|
|
67458
68075
|
const system = this._createStaffSystem(currentBarIndex, endBarIndex);
|
|
@@ -67487,6 +68104,7 @@
|
|
|
67487
68104
|
// since we use partial drawing
|
|
67488
68105
|
system.paint(0, -(args.y / this.renderer.settings.display.scale), canvas);
|
|
67489
68106
|
});
|
|
68107
|
+
this._systemPartialIds.push(args.id);
|
|
67490
68108
|
// calculate coordinates for next system
|
|
67491
68109
|
return height;
|
|
67492
68110
|
}
|
|
@@ -67494,6 +68112,10 @@
|
|
|
67494
68112
|
* Realignes the bars in this line according to the available space
|
|
67495
68113
|
*/
|
|
67496
68114
|
_fitSystem(system) {
|
|
68115
|
+
// If a bar added late in the assembly introduced a shorter note than earlier bars, the
|
|
68116
|
+
// earlier bars' spring constants (and the cached system widths / totals) are stale.
|
|
68117
|
+
// Reconcile now - it's a no-op when nothing changed.
|
|
68118
|
+
system.reconcileMinDurationIfDirty();
|
|
67497
68119
|
if (system.isFull || system.width > this._maxWidth || this.renderer.settings.display.justifyLastSystem) {
|
|
67498
68120
|
this._scaleToWidth(system, this._maxWidth);
|
|
67499
68121
|
}
|
|
@@ -67505,29 +68127,35 @@
|
|
|
67505
68127
|
_scaleToWidth(system, width) {
|
|
67506
68128
|
const staffWidth = width - system.accoladeWidth;
|
|
67507
68129
|
const shouldApplyBarScale = this.shouldApplyBarScale;
|
|
67508
|
-
|
|
67509
|
-
//
|
|
67510
|
-
//
|
|
67511
|
-
//
|
|
67512
|
-
//
|
|
67513
|
-
//
|
|
67514
|
-
|
|
67515
|
-
|
|
68130
|
+
// Industry fixed-overhead model (Behind Bars, Dorico, Finale, Sibelius, MuseScore, Guitar Pro):
|
|
68131
|
+
// prefix/postfix glyphs (clef, key sig, time sig, barlines) are treated as fixed overhead and the
|
|
68132
|
+
// remaining staff width is distributed across bars by a per-bar weight.
|
|
68133
|
+
//
|
|
68134
|
+
// distributable = staffWidth - totalFixedOverhead
|
|
68135
|
+
// contentShare = distributable / sum(weight)
|
|
68136
|
+
// bar.width = bar.maxFixedOverhead + weight * contentShare
|
|
68137
|
+
//
|
|
68138
|
+
// The weight depends on the layout mode:
|
|
68139
|
+
// - shouldApplyBarScale=true -> weight = bar.displayScale (model-driven, matches Guitar Pro)
|
|
68140
|
+
// displayScale defaults to 1, so an unset value behaves identically
|
|
68141
|
+
// to an explicit 1 (GP omits the property when not customized).
|
|
68142
|
+
// - shouldApplyBarScale=false -> weight = natural content width (automatic, ignores displayScale)
|
|
68143
|
+
//
|
|
68144
|
+
// Per-bar maxFixedOverhead / maxContentWidth and the system-wide totals are maintained incrementally
|
|
68145
|
+
// in StaffSystem._applyLayoutAndUpdateWidth / revertLastBar so this pass can apply directly.
|
|
68146
|
+
const weightTotal = shouldApplyBarScale ? system.totalBarDisplayScale : system.totalContentWidth;
|
|
68147
|
+
const distributable = Math.max(0, staffWidth - system.totalFixedOverhead);
|
|
68148
|
+
const contentShare = weightTotal > 0 ? distributable / weightTotal : 0;
|
|
67516
68149
|
for (const s of system.allStaves) {
|
|
67517
68150
|
s.resetSharedLayoutData();
|
|
67518
|
-
// scale the bars by keeping their respective ratio size
|
|
67519
68151
|
let w = 0;
|
|
67520
|
-
for (
|
|
68152
|
+
for (let i = 0; i < s.barRenderers.length; i++) {
|
|
68153
|
+
const renderer = s.barRenderers[i];
|
|
68154
|
+
const mb = system.masterBarsRenderers[i];
|
|
67521
68155
|
renderer.x = w;
|
|
67522
68156
|
renderer.y = s.topPadding + s.topOverflow;
|
|
67523
|
-
|
|
67524
|
-
|
|
67525
|
-
const barDisplayScale = system.getBarDisplayScale(renderer);
|
|
67526
|
-
actualBarWidth = (barDisplayScale * staffWidth) / totalScale;
|
|
67527
|
-
}
|
|
67528
|
-
else {
|
|
67529
|
-
actualBarWidth = renderer.computedWidth + spacePerBar;
|
|
67530
|
-
}
|
|
68157
|
+
const weight = shouldApplyBarScale ? system.getBarDisplayScale(renderer) : mb.maxContentWidth;
|
|
68158
|
+
const actualBarWidth = mb.maxFixedOverhead + weight * contentShare;
|
|
67531
68159
|
renderer.scaleToWidth(actualBarWidth);
|
|
67532
68160
|
w += renderer.width;
|
|
67533
68161
|
}
|
|
@@ -68551,11 +69179,43 @@
|
|
|
68551
69179
|
}
|
|
68552
69180
|
}
|
|
68553
69181
|
|
|
69182
|
+
/**
|
|
69183
|
+
* Helpers for building `TieGlyphLabel` instances from model-side
|
|
69184
|
+
* {@link SlurSegment}s.
|
|
69185
|
+
* @internal
|
|
69186
|
+
*/
|
|
69187
|
+
class TieGlyphLabels {
|
|
69188
|
+
/**
|
|
69189
|
+
* Builds a `TieGlyphLabel` for one segment of a slur. The
|
|
69190
|
+
* `isAscending` flag selects between the H/P glyph for hammer-on
|
|
69191
|
+
* vs. pull-off — score side passes a comparison on `realValue`,
|
|
69192
|
+
* tab side passes a comparison on `fret`.
|
|
69193
|
+
*/
|
|
69194
|
+
static build(s, isAscending) {
|
|
69195
|
+
if (s.kind === SlurSegmentKind.LegatoSlide) {
|
|
69196
|
+
return {
|
|
69197
|
+
fromNote: s.fromNote,
|
|
69198
|
+
toNote: s.toNote,
|
|
69199
|
+
text: s.text !== null ? s.text : 'sl.',
|
|
69200
|
+
element: exports.NotationElement.EffectSlideText
|
|
69201
|
+
};
|
|
69202
|
+
}
|
|
69203
|
+
// HammerPull
|
|
69204
|
+
return {
|
|
69205
|
+
fromNote: s.fromNote,
|
|
69206
|
+
toNote: s.toNote,
|
|
69207
|
+
text: s.text !== null ? s.text : isAscending ? 'H' : 'P',
|
|
69208
|
+
element: exports.NotationElement.EffectHammerOnPullOffText
|
|
69209
|
+
};
|
|
69210
|
+
}
|
|
69211
|
+
}
|
|
69212
|
+
|
|
68554
69213
|
/**
|
|
68555
69214
|
* @internal
|
|
68556
69215
|
*/
|
|
68557
69216
|
class TabSlurGlyph extends TabTieGlyph {
|
|
68558
69217
|
_forSlide;
|
|
69218
|
+
_labels = null;
|
|
68559
69219
|
constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
|
|
68560
69220
|
super(slurEffectId, startNote, endNote, forEnd);
|
|
68561
69221
|
this._forSlide = forSlide;
|
|
@@ -68563,6 +69223,22 @@
|
|
|
68563
69223
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
68564
69224
|
return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
68565
69225
|
}
|
|
69226
|
+
getSlurLabels() {
|
|
69227
|
+
if (this._labels === null) {
|
|
69228
|
+
this._labels = [];
|
|
69229
|
+
const slur = this.startNote.effectSlur;
|
|
69230
|
+
if (slur !== null) {
|
|
69231
|
+
const notationSettings = this.renderer.settings.notation;
|
|
69232
|
+
for (const s of slur.segments) {
|
|
69233
|
+
const label = TieGlyphLabels.build(s, s.toNote.fret >= s.fromNote.fret);
|
|
69234
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
69235
|
+
this._labels.push(label);
|
|
69236
|
+
}
|
|
69237
|
+
}
|
|
69238
|
+
}
|
|
69239
|
+
}
|
|
69240
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
69241
|
+
}
|
|
68566
69242
|
tryExpand(startNote, endNote, forSlide, forEnd) {
|
|
68567
69243
|
// same type required
|
|
68568
69244
|
if (this._forSlide !== forSlide) {
|
|
@@ -68588,6 +69264,7 @@
|
|
|
68588
69264
|
case BeamDirection.Up:
|
|
68589
69265
|
if (startNote.realValue > this.startNote.realValue) {
|
|
68590
69266
|
this.startNote = startNote;
|
|
69267
|
+
this._labels = null; // invalidate cache — labels live on startNote
|
|
68591
69268
|
}
|
|
68592
69269
|
if (endNote.realValue > this.endNote.realValue) {
|
|
68593
69270
|
this.endNote = endNote;
|
|
@@ -68596,6 +69273,7 @@
|
|
|
68596
69273
|
case BeamDirection.Down:
|
|
68597
69274
|
if (startNote.realValue < this.startNote.realValue) {
|
|
68598
69275
|
this.startNote = startNote;
|
|
69276
|
+
this._labels = null;
|
|
68599
69277
|
}
|
|
68600
69278
|
if (endNote.realValue < this.endNote.realValue) {
|
|
68601
69279
|
this.endNote = endNote;
|
|
@@ -69351,12 +70029,24 @@
|
|
|
69351
70029
|
const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
|
|
69352
70030
|
const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
|
|
69353
70031
|
const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
|
|
69354
|
-
let startY
|
|
69355
|
-
let endY
|
|
70032
|
+
let startY;
|
|
70033
|
+
let endY;
|
|
69356
70034
|
if (isRestOnly) {
|
|
69357
|
-
|
|
70035
|
+
// rests have no stems, so anchor to the actual rest glyph bounds
|
|
70036
|
+
// instead of a stem-adjusted flag position (which would place the bracket
|
|
70037
|
+
// a full quarter-stem length away from the rests).
|
|
70038
|
+
if (direction === BeamDirection.Up) {
|
|
70039
|
+
startY = Math.min(this.getRestY(firstNonRestBeat, NoteYPosition.Top), this.getRestY(lastNonRestBeat, NoteYPosition.Top));
|
|
70040
|
+
}
|
|
70041
|
+
else {
|
|
70042
|
+
startY = Math.max(this.getRestY(firstNonRestBeat, NoteYPosition.Bottom), this.getRestY(lastNonRestBeat, NoteYPosition.Bottom));
|
|
70043
|
+
}
|
|
69358
70044
|
endY = startY;
|
|
69359
70045
|
}
|
|
70046
|
+
else {
|
|
70047
|
+
startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
|
|
70048
|
+
endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
|
|
70049
|
+
}
|
|
69360
70050
|
// align line centered in available space
|
|
69361
70051
|
if (direction === BeamDirection.Down) {
|
|
69362
70052
|
startY += shift;
|
|
@@ -69726,7 +70416,30 @@
|
|
|
69726
70416
|
let minNoteY = 0;
|
|
69727
70417
|
for (const v of this.helpers.beamHelpers) {
|
|
69728
70418
|
for (const h of v) {
|
|
69729
|
-
if (!this.shouldPaintBeamingHelper(h))
|
|
70419
|
+
if (!this.shouldPaintBeamingHelper(h)) {
|
|
70420
|
+
// beam is not drawn, but a rest-only tuplet still draws a bracket
|
|
70421
|
+
// anchored to the rest glyph bounds and needs overflow reserved.
|
|
70422
|
+
if (h.hasTuplet && h.isRestBeamHelper) {
|
|
70423
|
+
const tupletGroup = h.beats[0].tupletGroup;
|
|
70424
|
+
const tupletFirst = tupletGroup.beats[0];
|
|
70425
|
+
const tupletLast = tupletGroup.beats[tupletGroup.beats.length - 1];
|
|
70426
|
+
const tupletDirection = this.getTupletBeamDirection(h);
|
|
70427
|
+
if (tupletDirection === BeamDirection.Up) {
|
|
70428
|
+
const restTop = Math.min(this.getRestY(tupletFirst, NoteYPosition.Top), this.getRestY(tupletLast, NoteYPosition.Top));
|
|
70429
|
+
const topY = restTop - this.tupletSize - this.tupletOffset;
|
|
70430
|
+
if (topY < maxNoteY) {
|
|
70431
|
+
maxNoteY = topY;
|
|
70432
|
+
}
|
|
70433
|
+
}
|
|
70434
|
+
else {
|
|
70435
|
+
const restBottom = Math.max(this.getRestY(tupletFirst, NoteYPosition.Bottom), this.getRestY(tupletLast, NoteYPosition.Bottom));
|
|
70436
|
+
const bottomY = restBottom + this.tupletSize + this.tupletOffset;
|
|
70437
|
+
if (bottomY > minNoteY) {
|
|
70438
|
+
minNoteY = bottomY;
|
|
70439
|
+
}
|
|
70440
|
+
}
|
|
70441
|
+
}
|
|
70442
|
+
}
|
|
69730
70443
|
else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
|
|
69731
70444
|
const tupletDirection = this.getTupletBeamDirection(h);
|
|
69732
70445
|
const direction = this.getBeamDirection(h);
|
|
@@ -73298,9 +74011,26 @@
|
|
|
73298
74011
|
* @internal
|
|
73299
74012
|
*/
|
|
73300
74013
|
class ScoreSlurGlyph extends ScoreTieGlyph {
|
|
74014
|
+
_labels = null;
|
|
73301
74015
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
73302
74016
|
return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
73303
74017
|
}
|
|
74018
|
+
getSlurLabels() {
|
|
74019
|
+
if (this._labels === null) {
|
|
74020
|
+
this._labels = [];
|
|
74021
|
+
const slur = this.startNote.beat.effectSlur;
|
|
74022
|
+
if (slur !== null) {
|
|
74023
|
+
const notationSettings = this.renderer.settings.notation;
|
|
74024
|
+
for (const s of slur.segments) {
|
|
74025
|
+
const label = TieGlyphLabels.build(s, s.toNote.realValue >= s.fromNote.realValue);
|
|
74026
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
74027
|
+
this._labels.push(label);
|
|
74028
|
+
}
|
|
74029
|
+
}
|
|
74030
|
+
}
|
|
74031
|
+
}
|
|
74032
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
74033
|
+
}
|
|
73304
74034
|
calculateStartX() {
|
|
73305
74035
|
return (this.renderer.x +
|
|
73306
74036
|
(this._isStartCentered()
|