@coderline/alphatab 1.9.0-alpha.1785 → 1.9.0-alpha.1804
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alphaTab.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +399 -52
- package/dist/alphaTab.d.ts +15 -1
- package/dist/alphaTab.js +399 -52
- 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 +3 -3
package/dist/alphaTab.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.9.0-alpha.
|
|
2
|
+
* alphaTab v1.9.0-alpha.1804 (develop, build 1804)
|
|
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.1804';
|
|
213
|
+
static date = '2026-05-16T03:54:50.685Z';
|
|
214
|
+
static commit = '6e757c0cbec66598a7fa5327abc9d05616984486';
|
|
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
|
*/
|
|
@@ -10227,7 +10321,7 @@
|
|
|
10227
10321
|
encoding = 'utf-8';
|
|
10228
10322
|
}
|
|
10229
10323
|
const decoder = new TextDecoder(encoding);
|
|
10230
|
-
return decoder.decode(data
|
|
10324
|
+
return decoder.decode(data);
|
|
10231
10325
|
}
|
|
10232
10326
|
static _detectEncoding(data) {
|
|
10233
10327
|
if (data.length > 2 && data[0] === 0xfe && data[1] === 0xff) {
|
|
@@ -19657,7 +19751,7 @@
|
|
|
19657
19751
|
static _versionString = 'FICHIER GUITAR PRO ';
|
|
19658
19752
|
// NOTE: General Midi only defines percussion instruments from 35-81
|
|
19659
19753
|
// Guitar Pro 5 allowed GS extensions (27-34 and 82-87)
|
|
19660
|
-
// GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
|
|
19754
|
+
// GP7-8 do not have all these definitions anymore, this lookup ensures some fallback
|
|
19661
19755
|
// (even if they are not correct)
|
|
19662
19756
|
// we can support this properly in future when we allow custom alphaTex articulation definitions
|
|
19663
19757
|
// then we don't need to rely on GP specifics anymore but handle things on export/import
|
|
@@ -21048,12 +21142,13 @@
|
|
|
21048
21142
|
* @returns
|
|
21049
21143
|
*/
|
|
21050
21144
|
static gpReadStringByteLength(data, length, encoding) {
|
|
21145
|
+
// Fixed-width string field: 1 length byte + `length` data bytes, decoded
|
|
21146
|
+
// up to min(stringLength, length). Always consumes 1 + length bytes.
|
|
21051
21147
|
const stringLength = data.readByte();
|
|
21052
|
-
const
|
|
21053
|
-
|
|
21054
|
-
|
|
21055
|
-
|
|
21056
|
-
return s;
|
|
21148
|
+
const fieldBytes = new Uint8Array(length);
|
|
21149
|
+
data.read(fieldBytes, 0, length);
|
|
21150
|
+
const effectiveLength = Math.min(stringLength, length);
|
|
21151
|
+
return IOHelper.toString(fieldBytes.subarray(0, effectiveLength), encoding);
|
|
21057
21152
|
}
|
|
21058
21153
|
}
|
|
21059
21154
|
/**
|
|
@@ -24273,6 +24368,10 @@
|
|
|
24273
24368
|
}
|
|
24274
24369
|
// build masterbar automations
|
|
24275
24370
|
for (const [barNumber, automations] of this._masterTrackAutomations) {
|
|
24371
|
+
if (barNumber < 0 || barNumber >= this.score.masterBars.length) {
|
|
24372
|
+
// automation references a bar that is not in the score's masterBars list
|
|
24373
|
+
continue;
|
|
24374
|
+
}
|
|
24276
24375
|
const masterBar = this.score.masterBars[barNumber];
|
|
24277
24376
|
for (let i = 0, j = automations.length; i < j; i++) {
|
|
24278
24377
|
const automation = automations[i];
|
|
@@ -31948,7 +32047,9 @@
|
|
|
31948
32047
|
[exports.NotationElement.RepeatCount, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31949
32048
|
[exports.NotationElement.BarNumber, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31950
32049
|
[exports.NotationElement.ScoreBendSlur, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31951
|
-
[exports.NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)]
|
|
32050
|
+
[exports.NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)],
|
|
32051
|
+
[exports.NotationElement.EffectHammerOnPullOffText, RenderingResources._effectFont],
|
|
32052
|
+
[exports.NotationElement.EffectSlideText, RenderingResources._effectFont]
|
|
31952
32053
|
]);
|
|
31953
32054
|
/**
|
|
31954
32055
|
* The name of the SMuFL Font to use for rendering music symbols.
|
|
@@ -32239,9 +32340,16 @@
|
|
|
32239
32340
|
notationElement = exports.NotationElement.ScoreWords;
|
|
32240
32341
|
break;
|
|
32241
32342
|
}
|
|
32343
|
+
return this.getFontForNotationElement(notationElement);
|
|
32344
|
+
}
|
|
32345
|
+
/**
|
|
32346
|
+
* @internal
|
|
32347
|
+
* @param element
|
|
32348
|
+
*/
|
|
32349
|
+
getFontForNotationElement(notationElement) {
|
|
32242
32350
|
return this.elementFonts.has(notationElement)
|
|
32243
32351
|
? this.elementFonts.get(notationElement)
|
|
32244
|
-
: RenderingResources.defaultFonts.get(
|
|
32352
|
+
: RenderingResources.defaultFonts.get(notationElement);
|
|
32245
32353
|
}
|
|
32246
32354
|
}
|
|
32247
32355
|
|
|
@@ -49167,6 +49275,21 @@
|
|
|
49167
49275
|
}
|
|
49168
49276
|
}
|
|
49169
49277
|
|
|
49278
|
+
/**
|
|
49279
|
+
* Represents a range of the song that should be played.
|
|
49280
|
+
* @public
|
|
49281
|
+
*/
|
|
49282
|
+
class PlaybackRange {
|
|
49283
|
+
/**
|
|
49284
|
+
* The position in midi ticks from where the song should start.
|
|
49285
|
+
*/
|
|
49286
|
+
startTick = 0;
|
|
49287
|
+
/**
|
|
49288
|
+
* The position in midi ticks to where the song should be played.
|
|
49289
|
+
*/
|
|
49290
|
+
endTick = 0;
|
|
49291
|
+
}
|
|
49292
|
+
|
|
49170
49293
|
/**
|
|
49171
49294
|
* Describes how a cursor should be moving.
|
|
49172
49295
|
* @public
|
|
@@ -49327,6 +49450,13 @@
|
|
|
49327
49450
|
* @internal
|
|
49328
49451
|
*/
|
|
49329
49452
|
masterBarLookup = new Map();
|
|
49453
|
+
/**
|
|
49454
|
+
* A dictionary of all beat played. The index is the id to {@link Beat.id}.
|
|
49455
|
+
* The value is the bar relative tick time at which the beat was registered during midi generation.
|
|
49456
|
+
* This lookup only contains the first time a Beat is played.
|
|
49457
|
+
* @internal
|
|
49458
|
+
*/
|
|
49459
|
+
beatLookup = new Map();
|
|
49330
49460
|
/**
|
|
49331
49461
|
* A list of all {@link MasterBarTickLookup} sorted by time.
|
|
49332
49462
|
*/
|
|
@@ -49693,10 +49823,22 @@
|
|
|
49693
49823
|
* @returns The time in midi ticks at which the beat is played the first time or 0 if the beat is not contained
|
|
49694
49824
|
*/
|
|
49695
49825
|
getBeatStart(beat) {
|
|
49696
|
-
if (!this.masterBarLookup.has(beat.voice.bar.index)) {
|
|
49826
|
+
if (!this.masterBarLookup.has(beat.voice.bar.index) || !this.beatLookup.has(beat.id)) {
|
|
49697
49827
|
return 0;
|
|
49698
49828
|
}
|
|
49699
|
-
|
|
49829
|
+
const mb = this.masterBarLookup.get(beat.voice.bar.index);
|
|
49830
|
+
return mb.start + this.beatLookup.get(beat.id).startTick;
|
|
49831
|
+
}
|
|
49832
|
+
/**
|
|
49833
|
+
* Gets the playback range in midi ticks for a given beat.
|
|
49834
|
+
* @param beat The beat to find the time period for.
|
|
49835
|
+
* @returns The relative playback range within the parent masterbar at which the beat start and ends playing
|
|
49836
|
+
*/
|
|
49837
|
+
getRelativeBeatPlaybackRange(beat) {
|
|
49838
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49839
|
+
return undefined;
|
|
49840
|
+
}
|
|
49841
|
+
return this.beatLookup.get(beat.id);
|
|
49700
49842
|
}
|
|
49701
49843
|
/**
|
|
49702
49844
|
* Adds a new {@link MasterBarTickLookup} to the lookup table.
|
|
@@ -49714,6 +49856,12 @@
|
|
|
49714
49856
|
}
|
|
49715
49857
|
}
|
|
49716
49858
|
addBeat(beat, start, duration) {
|
|
49859
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49860
|
+
const playbackRange = new PlaybackRange();
|
|
49861
|
+
playbackRange.startTick = start;
|
|
49862
|
+
playbackRange.endTick = start + duration;
|
|
49863
|
+
this.beatLookup.set(beat.id, playbackRange);
|
|
49864
|
+
}
|
|
49717
49865
|
const currentMasterBar = this._currentMasterBar;
|
|
49718
49866
|
if (currentMasterBar) {
|
|
49719
49867
|
// pre-beat grace notes at the start of the bar we also add the beat to the previous bar
|
|
@@ -52297,21 +52445,6 @@
|
|
|
52297
52445
|
}
|
|
52298
52446
|
}
|
|
52299
52447
|
|
|
52300
|
-
/**
|
|
52301
|
-
* Represents a range of the song that should be played.
|
|
52302
|
-
* @public
|
|
52303
|
-
*/
|
|
52304
|
-
class PlaybackRange {
|
|
52305
|
-
/**
|
|
52306
|
-
* The position in midi ticks from where the song should start.
|
|
52307
|
-
*/
|
|
52308
|
-
startTick = 0;
|
|
52309
|
-
/**
|
|
52310
|
-
* The position in midi ticks to where the song should be played.
|
|
52311
|
-
*/
|
|
52312
|
-
endTick = 0;
|
|
52313
|
-
}
|
|
52314
|
-
|
|
52315
52448
|
/**
|
|
52316
52449
|
* A {@link IAlphaSynth} implementation wrapping and underling other {@link IAlphaSynth}
|
|
52317
52450
|
* allowing dynamic changing of the underlying instance without loosing aspects like the
|
|
@@ -55512,23 +55645,24 @@
|
|
|
55512
55645
|
if (this._selectionStart && this._tickCache) {
|
|
55513
55646
|
// get the start and stop ticks (which consider properly repeats)
|
|
55514
55647
|
const tickCache = this._tickCache;
|
|
55515
|
-
const
|
|
55648
|
+
const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
|
|
55649
|
+
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat);
|
|
55650
|
+
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? this._selectionStart.beat.playbackStart;
|
|
55516
55651
|
// move to selection start
|
|
55517
55652
|
this._currentBeat = null; // reset current beat so it is updating the cursor
|
|
55518
55653
|
if (this._player.state === PlayerState.Paused) {
|
|
55519
|
-
this._cursorUpdateTick(
|
|
55654
|
+
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
|
|
55520
55655
|
}
|
|
55521
|
-
this.tickPosition =
|
|
55656
|
+
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55522
55657
|
// set playback range
|
|
55523
55658
|
if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
|
|
55524
|
-
const
|
|
55659
|
+
const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
|
|
55660
|
+
const endBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat);
|
|
55661
|
+
const endBeatPlaybackEnd = endBeatPlaybackRange?.endTick ??
|
|
55662
|
+
this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
|
|
55525
55663
|
const range = new PlaybackRange();
|
|
55526
|
-
range.startTick =
|
|
55527
|
-
range.endTick =
|
|
55528
|
-
realMasterBarEnd +
|
|
55529
|
-
this._selectionEnd.beat.playbackStart +
|
|
55530
|
-
this._selectionEnd.beat.playbackDuration -
|
|
55531
|
-
50;
|
|
55664
|
+
range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55665
|
+
range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
|
|
55532
55666
|
this.playbackRange = range;
|
|
55533
55667
|
}
|
|
55534
55668
|
else {
|
|
@@ -61575,6 +61709,12 @@
|
|
|
61575
61709
|
_tieHeight = 0;
|
|
61576
61710
|
_boundingBox;
|
|
61577
61711
|
_shouldPaint = false;
|
|
61712
|
+
// Resolved per-label paint state. Lazily grown; re-layouts mutate
|
|
61713
|
+
// existing entries in place and update `_resolvedLabelCount` to
|
|
61714
|
+
// signal how many of them are valid this pass.
|
|
61715
|
+
_resolvedLabels = [];
|
|
61716
|
+
_resolvedLabelCount = 0;
|
|
61717
|
+
_labelBaselineOffset = 0;
|
|
61578
61718
|
get checkForOverflow() {
|
|
61579
61719
|
return this._shouldPaint && this._boundingBox !== undefined;
|
|
61580
61720
|
}
|
|
@@ -61644,16 +61784,88 @@
|
|
|
61644
61784
|
}
|
|
61645
61785
|
this._boundingBox = undefined;
|
|
61646
61786
|
this.y = Math.min(this._startY, this._endY);
|
|
61787
|
+
const down = this.tieDirection === BeamDirection.Down;
|
|
61647
61788
|
let tieBoundingBox;
|
|
61789
|
+
// Bezier control points for the tie. Computed once and reused
|
|
61790
|
+
// for both the bounding box (via _calculateActualTieHeightFromCps)
|
|
61791
|
+
// and label-apex sampling further below — avoids a redundant
|
|
61792
|
+
// call to _computeBezierControlPoints (and its 14-element array
|
|
61793
|
+
// allocation) per labeled slur per layout.
|
|
61794
|
+
let cps = [];
|
|
61648
61795
|
if (this.shouldDrawBendSlur()) {
|
|
61649
61796
|
this._tieHeight = 0;
|
|
61650
|
-
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY,
|
|
61797
|
+
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY, down, this.renderer.smuflMetrics.tieHeight);
|
|
61651
61798
|
}
|
|
61652
61799
|
else {
|
|
61653
61800
|
this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
|
|
61654
|
-
|
|
61801
|
+
const tieThickness = this.renderer.smuflMetrics.tieMidpointThickness;
|
|
61802
|
+
cps = TieGlyph._computeBezierControlPoints(1, this._startX, this._startY, this._endX, this._endY, down, this._tieHeight, tieThickness);
|
|
61803
|
+
tieBoundingBox = TieGlyph._calculateActualTieHeightFromCps(cps, this._startX, this._startY, this._endX, this._endY, down, tieThickness);
|
|
61655
61804
|
}
|
|
61656
61805
|
this._boundingBox = tieBoundingBox;
|
|
61806
|
+
this._resolvedLabelCount = 0;
|
|
61807
|
+
const labels = this.getSlurLabels();
|
|
61808
|
+
if (labels !== null && labels.length > 0 && this.shouldPaintLabels()) {
|
|
61809
|
+
const res = this.renderer.settings.display.resources;
|
|
61810
|
+
const padding = this.renderer.smuflMetrics.oneStaffSpace * 0.25;
|
|
61811
|
+
let maxTextHeight = 0;
|
|
61812
|
+
// Single Y line for all labels — the outer arc apex.
|
|
61813
|
+
// Painted offset adds `padding` on the outward side, so
|
|
61814
|
+
// every label sits the same fixed distance from its arc.
|
|
61815
|
+
const labelLineY = cps.length > 0
|
|
61816
|
+
? 0.125 * cps[7] + 0.375 * cps[9] + 0.375 * cps[11] + 0.125 * cps[13]
|
|
61817
|
+
: (this._startY + this._endY) / 2;
|
|
61818
|
+
for (const label of labels) {
|
|
61819
|
+
const fromX = this.resolveLabelAnchorX(label.fromNote);
|
|
61820
|
+
const toX = this.resolveLabelAnchorX(label.toNote);
|
|
61821
|
+
if (fromX === null || toX === null) {
|
|
61822
|
+
continue;
|
|
61823
|
+
}
|
|
61824
|
+
const midX = (fromX + toX) / 2;
|
|
61825
|
+
if (midX < this._startX || midX > this._endX) {
|
|
61826
|
+
continue;
|
|
61827
|
+
}
|
|
61828
|
+
// Per-element font.size as an upper bound on glyph
|
|
61829
|
+
// height — avoids per-label measureText calls. All H/P
|
|
61830
|
+
// and sl. labels use the same _effectFont, so this is
|
|
61831
|
+
// typically computed once.
|
|
61832
|
+
const font = res.getFontForNotationElement(label.element);
|
|
61833
|
+
if (font.size > maxTextHeight) {
|
|
61834
|
+
maxTextHeight = font.size;
|
|
61835
|
+
}
|
|
61836
|
+
// grow cache lazily; mutate existing slot in place otherwise
|
|
61837
|
+
let slot;
|
|
61838
|
+
if (this._resolvedLabelCount < this._resolvedLabels.length) {
|
|
61839
|
+
slot = this._resolvedLabels[this._resolvedLabelCount];
|
|
61840
|
+
slot.x = midX;
|
|
61841
|
+
slot.y = labelLineY;
|
|
61842
|
+
slot.text = label.text;
|
|
61843
|
+
slot.element = label.element;
|
|
61844
|
+
}
|
|
61845
|
+
else {
|
|
61846
|
+
slot = {
|
|
61847
|
+
x: midX,
|
|
61848
|
+
y: labelLineY,
|
|
61849
|
+
text: label.text,
|
|
61850
|
+
element: label.element
|
|
61851
|
+
};
|
|
61852
|
+
this._resolvedLabels.push(slot);
|
|
61853
|
+
}
|
|
61854
|
+
this._resolvedLabelCount++;
|
|
61855
|
+
}
|
|
61856
|
+
if (this._resolvedLabelCount > 0) {
|
|
61857
|
+
// canvas.textBaseline is 'hanging' (TextBaseline.Top), so
|
|
61858
|
+
// fillText positions `y` at the glyph's top edge.
|
|
61859
|
+
if (this.tieDirection === BeamDirection.Up) {
|
|
61860
|
+
tieBoundingBox.y -= maxTextHeight + padding;
|
|
61861
|
+
this._labelBaselineOffset = -(maxTextHeight + padding);
|
|
61862
|
+
}
|
|
61863
|
+
else {
|
|
61864
|
+
this._labelBaselineOffset = padding;
|
|
61865
|
+
}
|
|
61866
|
+
tieBoundingBox.h += maxTextHeight + padding;
|
|
61867
|
+
}
|
|
61868
|
+
}
|
|
61657
61869
|
this.height = tieBoundingBox.h;
|
|
61658
61870
|
if (this.tieDirection === BeamDirection.Up) {
|
|
61659
61871
|
// the tie might go above `this.y` due to its shape
|
|
@@ -61669,12 +61881,76 @@
|
|
|
61669
61881
|
if (!this._shouldPaint) {
|
|
61670
61882
|
return;
|
|
61671
61883
|
}
|
|
61884
|
+
const isDown = this.tieDirection === BeamDirection.Down;
|
|
61672
61885
|
if (this.shouldDrawBendSlur()) {
|
|
61673
|
-
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61886
|
+
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this.renderer.smuflMetrics.tieHeight);
|
|
61887
|
+
}
|
|
61888
|
+
else {
|
|
61889
|
+
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
|
|
61890
|
+
}
|
|
61891
|
+
if (this._resolvedLabelCount > 0) {
|
|
61892
|
+
const ta = canvas.textAlign;
|
|
61893
|
+
const tb = canvas.textBaseline;
|
|
61894
|
+
canvas.textAlign = TextAlign.Center;
|
|
61895
|
+
canvas.textBaseline = TextBaseline.Top;
|
|
61896
|
+
const res = this.renderer.resources;
|
|
61897
|
+
let lastElement = -1;
|
|
61898
|
+
for (let i = 0; i < this._resolvedLabelCount; i++) {
|
|
61899
|
+
const label = this._resolvedLabels[i];
|
|
61900
|
+
if (label.element !== lastElement) {
|
|
61901
|
+
canvas.font = res.getFontForNotationElement(label.element);
|
|
61902
|
+
lastElement = label.element;
|
|
61903
|
+
}
|
|
61904
|
+
canvas.fillText(label.text, cx + label.x, cy + label.y + this._labelBaselineOffset);
|
|
61905
|
+
}
|
|
61906
|
+
canvas.textAlign = ta;
|
|
61907
|
+
canvas.textBaseline = tb;
|
|
61908
|
+
}
|
|
61909
|
+
}
|
|
61910
|
+
/**
|
|
61911
|
+
* Returns the labels to paint along this slur, or `null` when there
|
|
61912
|
+
* are none. Override in subclasses.
|
|
61913
|
+
*/
|
|
61914
|
+
getSlurLabels() {
|
|
61915
|
+
return null;
|
|
61916
|
+
}
|
|
61917
|
+
/**
|
|
61918
|
+
* Whether label painting is enabled. Defaults to `true`. Subclasses
|
|
61919
|
+
* may override to disable labels on the bend-slur path or other
|
|
61920
|
+
* special cases.
|
|
61921
|
+
*/
|
|
61922
|
+
shouldPaintLabels() {
|
|
61923
|
+
return !this.shouldDrawBendSlur();
|
|
61924
|
+
}
|
|
61925
|
+
/**
|
|
61926
|
+
* Looks up the absolute X coordinate of an anchor note. Reuses
|
|
61927
|
+
* the start/end bar renderers already resolved by the subclass
|
|
61928
|
+
* (NoteTieGlyph) when the note's bar matches — most labels live
|
|
61929
|
+
* in the slur's start or end bar, so this avoids the double Map
|
|
61930
|
+
* lookup in `getRendererForBar` per label per layout. Returns
|
|
61931
|
+
* `null` when the note's bar is not rendered on this glyph's
|
|
61932
|
+
* staff (cross-system case).
|
|
61933
|
+
*/
|
|
61934
|
+
resolveLabelAnchorX(note) {
|
|
61935
|
+
const bar = note.beat.voice.bar;
|
|
61936
|
+
let renderer = null;
|
|
61937
|
+
const start = this.lookupStartBeatRenderer();
|
|
61938
|
+
if (start !== null && start.bar === bar) {
|
|
61939
|
+
renderer = start;
|
|
61674
61940
|
}
|
|
61675
61941
|
else {
|
|
61676
|
-
|
|
61942
|
+
const end = this.lookupEndBeatRenderer();
|
|
61943
|
+
if (end !== null && end.bar === bar) {
|
|
61944
|
+
renderer = end;
|
|
61945
|
+
}
|
|
61946
|
+
else {
|
|
61947
|
+
renderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, bar);
|
|
61948
|
+
}
|
|
61677
61949
|
}
|
|
61950
|
+
if (renderer === null) {
|
|
61951
|
+
return null;
|
|
61952
|
+
}
|
|
61953
|
+
return renderer.x + renderer.getNoteX(note, NoteXPosition.Center);
|
|
61678
61954
|
}
|
|
61679
61955
|
getTieHeight(_startX, _startY, _endX, _endY) {
|
|
61680
61956
|
return this.renderer.smuflMetrics.tieHeight;
|
|
@@ -61694,11 +61970,18 @@
|
|
|
61694
61970
|
}
|
|
61695
61971
|
static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
|
|
61696
61972
|
const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
|
|
61973
|
+
return TieGlyph._calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size);
|
|
61974
|
+
}
|
|
61975
|
+
/**
|
|
61976
|
+
* Derives the bounding box for a tie from already-computed control
|
|
61977
|
+
* points. Splits the bbox math from cps generation so callers that
|
|
61978
|
+
* need BOTH cps and bbox (e.g. multi-label slur layout) avoid a
|
|
61979
|
+
* second call to `_computeBezierControlPoints`.
|
|
61980
|
+
*/
|
|
61981
|
+
static _calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size) {
|
|
61697
61982
|
if (cp.length === 0) {
|
|
61698
61983
|
return new Bounds(x1, y1, x2 - x1, y2 - y1);
|
|
61699
61984
|
}
|
|
61700
|
-
// For a musical tie/slur, the extrema occur predictably near the midpoint
|
|
61701
|
-
// Evaluate at midpoint (t=0.5) and check endpoints
|
|
61702
61985
|
const p0x = cp[0];
|
|
61703
61986
|
const p0y = cp[1];
|
|
61704
61987
|
const c1x = cp[2];
|
|
@@ -61707,15 +61990,12 @@
|
|
|
61707
61990
|
const c2y = cp[5];
|
|
61708
61991
|
const p1x = cp[6];
|
|
61709
61992
|
const p1y = cp[7];
|
|
61710
|
-
// Evaluate at t=0.5 for midpoint
|
|
61711
61993
|
const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
|
|
61712
61994
|
const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
|
|
61713
|
-
// Bounds are simply min/max of start, end, and midpoint
|
|
61714
61995
|
const xMin = Math.min(p0x, p1x, midX);
|
|
61715
61996
|
const xMax = Math.max(p0x, p1x, midX);
|
|
61716
61997
|
let yMin = Math.min(p0y, p1y, midY);
|
|
61717
61998
|
let yMax = Math.max(p0y, p1y, midY);
|
|
61718
|
-
// Account for thickness of the tie/slur
|
|
61719
61999
|
if (down) {
|
|
61720
62000
|
yMax += size;
|
|
61721
62001
|
}
|
|
@@ -68904,11 +69184,43 @@
|
|
|
68904
69184
|
}
|
|
68905
69185
|
}
|
|
68906
69186
|
|
|
69187
|
+
/**
|
|
69188
|
+
* Helpers for building `TieGlyphLabel` instances from model-side
|
|
69189
|
+
* {@link SlurSegment}s.
|
|
69190
|
+
* @internal
|
|
69191
|
+
*/
|
|
69192
|
+
class TieGlyphLabels {
|
|
69193
|
+
/**
|
|
69194
|
+
* Builds a `TieGlyphLabel` for one segment of a slur. The
|
|
69195
|
+
* `isAscending` flag selects between the H/P glyph for hammer-on
|
|
69196
|
+
* vs. pull-off — score side passes a comparison on `realValue`,
|
|
69197
|
+
* tab side passes a comparison on `fret`.
|
|
69198
|
+
*/
|
|
69199
|
+
static build(s, isAscending) {
|
|
69200
|
+
if (s.kind === SlurSegmentKind.LegatoSlide) {
|
|
69201
|
+
return {
|
|
69202
|
+
fromNote: s.fromNote,
|
|
69203
|
+
toNote: s.toNote,
|
|
69204
|
+
text: s.text !== null ? s.text : 'sl.',
|
|
69205
|
+
element: exports.NotationElement.EffectSlideText
|
|
69206
|
+
};
|
|
69207
|
+
}
|
|
69208
|
+
// HammerPull
|
|
69209
|
+
return {
|
|
69210
|
+
fromNote: s.fromNote,
|
|
69211
|
+
toNote: s.toNote,
|
|
69212
|
+
text: s.text !== null ? s.text : isAscending ? 'H' : 'P',
|
|
69213
|
+
element: exports.NotationElement.EffectHammerOnPullOffText
|
|
69214
|
+
};
|
|
69215
|
+
}
|
|
69216
|
+
}
|
|
69217
|
+
|
|
68907
69218
|
/**
|
|
68908
69219
|
* @internal
|
|
68909
69220
|
*/
|
|
68910
69221
|
class TabSlurGlyph extends TabTieGlyph {
|
|
68911
69222
|
_forSlide;
|
|
69223
|
+
_labels = null;
|
|
68912
69224
|
constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
|
|
68913
69225
|
super(slurEffectId, startNote, endNote, forEnd);
|
|
68914
69226
|
this._forSlide = forSlide;
|
|
@@ -68916,6 +69228,22 @@
|
|
|
68916
69228
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
68917
69229
|
return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
68918
69230
|
}
|
|
69231
|
+
getSlurLabels() {
|
|
69232
|
+
if (this._labels === null) {
|
|
69233
|
+
this._labels = [];
|
|
69234
|
+
const slur = this.startNote.effectSlur;
|
|
69235
|
+
if (slur !== null) {
|
|
69236
|
+
const notationSettings = this.renderer.settings.notation;
|
|
69237
|
+
for (const s of slur.segments) {
|
|
69238
|
+
const label = TieGlyphLabels.build(s, s.toNote.fret >= s.fromNote.fret);
|
|
69239
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
69240
|
+
this._labels.push(label);
|
|
69241
|
+
}
|
|
69242
|
+
}
|
|
69243
|
+
}
|
|
69244
|
+
}
|
|
69245
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
69246
|
+
}
|
|
68919
69247
|
tryExpand(startNote, endNote, forSlide, forEnd) {
|
|
68920
69248
|
// same type required
|
|
68921
69249
|
if (this._forSlide !== forSlide) {
|
|
@@ -68941,6 +69269,7 @@
|
|
|
68941
69269
|
case BeamDirection.Up:
|
|
68942
69270
|
if (startNote.realValue > this.startNote.realValue) {
|
|
68943
69271
|
this.startNote = startNote;
|
|
69272
|
+
this._labels = null; // invalidate cache — labels live on startNote
|
|
68944
69273
|
}
|
|
68945
69274
|
if (endNote.realValue > this.endNote.realValue) {
|
|
68946
69275
|
this.endNote = endNote;
|
|
@@ -68949,6 +69278,7 @@
|
|
|
68949
69278
|
case BeamDirection.Down:
|
|
68950
69279
|
if (startNote.realValue < this.startNote.realValue) {
|
|
68951
69280
|
this.startNote = startNote;
|
|
69281
|
+
this._labels = null;
|
|
68952
69282
|
}
|
|
68953
69283
|
if (endNote.realValue < this.endNote.realValue) {
|
|
68954
69284
|
this.endNote = endNote;
|
|
@@ -73686,9 +74016,26 @@
|
|
|
73686
74016
|
* @internal
|
|
73687
74017
|
*/
|
|
73688
74018
|
class ScoreSlurGlyph extends ScoreTieGlyph {
|
|
74019
|
+
_labels = null;
|
|
73689
74020
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
73690
74021
|
return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
73691
74022
|
}
|
|
74023
|
+
getSlurLabels() {
|
|
74024
|
+
if (this._labels === null) {
|
|
74025
|
+
this._labels = [];
|
|
74026
|
+
const slur = this.startNote.beat.effectSlur;
|
|
74027
|
+
if (slur !== null) {
|
|
74028
|
+
const notationSettings = this.renderer.settings.notation;
|
|
74029
|
+
for (const s of slur.segments) {
|
|
74030
|
+
const label = TieGlyphLabels.build(s, s.toNote.realValue >= s.fromNote.realValue);
|
|
74031
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
74032
|
+
this._labels.push(label);
|
|
74033
|
+
}
|
|
74034
|
+
}
|
|
74035
|
+
}
|
|
74036
|
+
}
|
|
74037
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
74038
|
+
}
|
|
73692
74039
|
calculateStartX() {
|
|
73693
74040
|
return (this.renderer.x +
|
|
73694
74041
|
(this._isStartCentered()
|