@coderline/alphatab 1.9.0-alpha.1785 → 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/dist/alphaTab.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +387 -45
- package/dist/alphaTab.d.ts +15 -1
- package/dist/alphaTab.js +387 -45
- 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.core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.9.0-alpha.
|
|
2
|
+
* alphaTab v1.9.0-alpha.1803 (develop, build 1803)
|
|
3
3
|
*
|
|
4
4
|
* Copyright © 2026, Daniel Kuschny and Contributors, All rights reserved.
|
|
5
5
|
*
|
|
@@ -203,9 +203,9 @@ class AlphaTabError extends Error {
|
|
|
203
203
|
* @internal
|
|
204
204
|
*/
|
|
205
205
|
class VersionInfo {
|
|
206
|
-
static version = '1.9.0-alpha.
|
|
207
|
-
static date = '2026-
|
|
208
|
-
static commit = '
|
|
206
|
+
static version = '1.9.0-alpha.1803';
|
|
207
|
+
static date = '2026-05-15T04:13:04.606Z';
|
|
208
|
+
static commit = 'a87a8635a0a0306cdab1c7fe2e9e72576ed6f795';
|
|
209
209
|
static print(print) {
|
|
210
210
|
print(`alphaTab ${VersionInfo.version}`);
|
|
211
211
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -992,6 +992,38 @@ var SlideOutType;
|
|
|
992
992
|
SlideOutType[SlideOutType["PickSlideUp"] = 6] = "PickSlideUp";
|
|
993
993
|
})(SlideOutType || (SlideOutType = {}));
|
|
994
994
|
|
|
995
|
+
/**
|
|
996
|
+
* A slur arc spanning two notes, optionally with inner articulation
|
|
997
|
+
* segments. Corresponds conceptually to a MusicXML `<slur>` element
|
|
998
|
+
* plus the technique spans inside it.
|
|
999
|
+
*
|
|
1000
|
+
* For this PR only effect slurs (hammer-pull + legato-slide chains)
|
|
1001
|
+
* are derived in `Note.finish()`. Phrase and legato slurs may join
|
|
1002
|
+
* this type in a future PR; a discriminator will be added at that
|
|
1003
|
+
* point.
|
|
1004
|
+
* @internal
|
|
1005
|
+
*/
|
|
1006
|
+
class Slur {
|
|
1007
|
+
originNote;
|
|
1008
|
+
destinationNote;
|
|
1009
|
+
segments = [];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Articulation kind for an inner span of a {@link Slur}.
|
|
1014
|
+
*
|
|
1015
|
+
* Drives the renderer's font selection (which {@link NotationElement} to
|
|
1016
|
+
* use) and the default label text when {@link SlurSegment.text} is null.
|
|
1017
|
+
* `Note.finish()` classifies the kind once when building the slur; the
|
|
1018
|
+
* renderer never re-derives it.
|
|
1019
|
+
* @internal
|
|
1020
|
+
*/
|
|
1021
|
+
var SlurSegmentKind;
|
|
1022
|
+
(function (SlurSegmentKind) {
|
|
1023
|
+
SlurSegmentKind[SlurSegmentKind["HammerPull"] = 0] = "HammerPull";
|
|
1024
|
+
SlurSegmentKind[SlurSegmentKind["LegatoSlide"] = 1] = "LegatoSlide";
|
|
1025
|
+
})(SlurSegmentKind || (SlurSegmentKind = {}));
|
|
1026
|
+
|
|
995
1027
|
/**
|
|
996
1028
|
* This public enum lists all vibrato types that can be performed.
|
|
997
1029
|
* @public
|
|
@@ -1332,6 +1364,14 @@ var NotationElement;
|
|
|
1332
1364
|
* The slurs shown on bend effects within the score staff.
|
|
1333
1365
|
*/
|
|
1334
1366
|
NotationElement[NotationElement["ScoreBendSlur"] = 55] = "ScoreBendSlur";
|
|
1367
|
+
/**
|
|
1368
|
+
* The hammer-on pull-off text shown on slurs.
|
|
1369
|
+
*/
|
|
1370
|
+
NotationElement[NotationElement["EffectHammerOnPullOffText"] = 56] = "EffectHammerOnPullOffText";
|
|
1371
|
+
/**
|
|
1372
|
+
* The slide text shown on slurs.
|
|
1373
|
+
*/
|
|
1374
|
+
NotationElement[NotationElement["EffectSlideText"] = 57] = "EffectSlideText";
|
|
1335
1375
|
})(NotationElement || (NotationElement = {}));
|
|
1336
1376
|
/**
|
|
1337
1377
|
* The notation settings control how various music notation elements are shown and behaving
|
|
@@ -6128,6 +6168,16 @@ class Note {
|
|
|
6128
6168
|
* @json_ignore
|
|
6129
6169
|
*/
|
|
6130
6170
|
effectSlurDestination = null;
|
|
6171
|
+
/**
|
|
6172
|
+
* The {@link Slur} object whose origin is this note. Populated by
|
|
6173
|
+
* `finish()`; non-null only on the chain-origin note of an effect
|
|
6174
|
+
* slur. Carries the inner articulation segments used by the
|
|
6175
|
+
* renderer to paint H/P/sl. labels along the arc.
|
|
6176
|
+
* @clone_ignore
|
|
6177
|
+
* @json_ignore
|
|
6178
|
+
* @internal
|
|
6179
|
+
*/
|
|
6180
|
+
effectSlur = null;
|
|
6131
6181
|
/**
|
|
6132
6182
|
* The ornament applied on the note.
|
|
6133
6183
|
*/
|
|
@@ -6412,23 +6462,50 @@ class Note {
|
|
|
6412
6462
|
break;
|
|
6413
6463
|
}
|
|
6414
6464
|
let effectSlurDestination = null;
|
|
6465
|
+
let effectSlurSegmentKind = null;
|
|
6415
6466
|
if (this.isHammerPullOrigin && this.hammerPullDestination) {
|
|
6416
6467
|
effectSlurDestination = this.hammerPullDestination;
|
|
6468
|
+
effectSlurSegmentKind = SlurSegmentKind.HammerPull;
|
|
6417
6469
|
}
|
|
6418
6470
|
else if (this.slideOutType === SlideOutType.Legato && this.slideTarget) {
|
|
6419
6471
|
effectSlurDestination = this.slideTarget;
|
|
6472
|
+
effectSlurSegmentKind = SlurSegmentKind.LegatoSlide;
|
|
6420
6473
|
}
|
|
6421
6474
|
if (effectSlurDestination) {
|
|
6422
6475
|
this.hasEffectSlur = true;
|
|
6423
6476
|
if (this.effectSlurOrigin && this.beat.pickStroke === PickStroke.None) {
|
|
6424
|
-
this.effectSlurOrigin
|
|
6425
|
-
|
|
6477
|
+
const chainOrigin = this.effectSlurOrigin;
|
|
6478
|
+
chainOrigin.effectSlurDestination = effectSlurDestination;
|
|
6479
|
+
effectSlurDestination.effectSlurOrigin = chainOrigin;
|
|
6426
6480
|
this.effectSlurOrigin = null;
|
|
6481
|
+
if (effectSlurSegmentKind !== null && chainOrigin.effectSlur !== null) {
|
|
6482
|
+
chainOrigin.effectSlur.destinationNote = effectSlurDestination;
|
|
6483
|
+
chainOrigin.effectSlur.segments.push({
|
|
6484
|
+
fromNote: this,
|
|
6485
|
+
toNote: effectSlurDestination,
|
|
6486
|
+
kind: effectSlurSegmentKind,
|
|
6487
|
+
text: null
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6427
6490
|
}
|
|
6428
6491
|
else {
|
|
6429
6492
|
this.isEffectSlurOrigin = true;
|
|
6430
6493
|
this.effectSlurDestination = effectSlurDestination;
|
|
6431
|
-
|
|
6494
|
+
effectSlurDestination.effectSlurOrigin = this;
|
|
6495
|
+
// Always allocate a fresh Slur — finish() may run twice (worker re-finish);
|
|
6496
|
+
// overwriting unconditionally keeps the derivation idempotent.
|
|
6497
|
+
const slur = new Slur();
|
|
6498
|
+
slur.originNote = this;
|
|
6499
|
+
slur.destinationNote = effectSlurDestination;
|
|
6500
|
+
if (effectSlurSegmentKind !== null) {
|
|
6501
|
+
slur.segments.push({
|
|
6502
|
+
fromNote: this,
|
|
6503
|
+
toNote: effectSlurDestination,
|
|
6504
|
+
kind: effectSlurSegmentKind,
|
|
6505
|
+
text: null
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
this.effectSlur = slur;
|
|
6432
6509
|
}
|
|
6433
6510
|
}
|
|
6434
6511
|
// try to detect what kind of bend was used and cleans unneeded points if required
|
|
@@ -7678,6 +7755,23 @@ class Beat {
|
|
|
7678
7755
|
* @json_ignore
|
|
7679
7756
|
*/
|
|
7680
7757
|
effectSlurDestination = null;
|
|
7758
|
+
/**
|
|
7759
|
+
* Convenience accessor for the {@link Slur} of this beat. Returns
|
|
7760
|
+
* the effect slur of whichever note in this beat owns it (the
|
|
7761
|
+
* chain-origin note populated during `Note.finish()`), or `null`
|
|
7762
|
+
* when no note in the beat is an effect-slur origin.
|
|
7763
|
+
* @clone_ignore
|
|
7764
|
+
* @json_ignore
|
|
7765
|
+
* @internal
|
|
7766
|
+
*/
|
|
7767
|
+
get effectSlur() {
|
|
7768
|
+
for (const n of this.notes) {
|
|
7769
|
+
if (n.effectSlur !== null) {
|
|
7770
|
+
return n.effectSlur;
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
return null;
|
|
7774
|
+
}
|
|
7681
7775
|
/**
|
|
7682
7776
|
* Gets or sets how the beaming should be done for this beat.
|
|
7683
7777
|
*/
|
|
@@ -31942,7 +32036,9 @@ class RenderingResources {
|
|
|
31942
32036
|
[NotationElement.RepeatCount, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31943
32037
|
[NotationElement.BarNumber, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31944
32038
|
[NotationElement.ScoreBendSlur, new Font(RenderingResources._sansFont, 11, FontStyle.Plain)],
|
|
31945
|
-
[NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)]
|
|
32039
|
+
[NotationElement.EffectAlternateEndings, new Font(RenderingResources._serifFont, 15, FontStyle.Plain)],
|
|
32040
|
+
[NotationElement.EffectHammerOnPullOffText, RenderingResources._effectFont],
|
|
32041
|
+
[NotationElement.EffectSlideText, RenderingResources._effectFont]
|
|
31946
32042
|
]);
|
|
31947
32043
|
/**
|
|
31948
32044
|
* The name of the SMuFL Font to use for rendering music symbols.
|
|
@@ -32233,9 +32329,16 @@ class RenderingResources {
|
|
|
32233
32329
|
notationElement = NotationElement.ScoreWords;
|
|
32234
32330
|
break;
|
|
32235
32331
|
}
|
|
32332
|
+
return this.getFontForNotationElement(notationElement);
|
|
32333
|
+
}
|
|
32334
|
+
/**
|
|
32335
|
+
* @internal
|
|
32336
|
+
* @param element
|
|
32337
|
+
*/
|
|
32338
|
+
getFontForNotationElement(notationElement) {
|
|
32236
32339
|
return this.elementFonts.has(notationElement)
|
|
32237
32340
|
? this.elementFonts.get(notationElement)
|
|
32238
|
-
: RenderingResources.defaultFonts.get(
|
|
32341
|
+
: RenderingResources.defaultFonts.get(notationElement);
|
|
32239
32342
|
}
|
|
32240
32343
|
}
|
|
32241
32344
|
|
|
@@ -49161,6 +49264,21 @@ class MasterBarTickLookup {
|
|
|
49161
49264
|
}
|
|
49162
49265
|
}
|
|
49163
49266
|
|
|
49267
|
+
/**
|
|
49268
|
+
* Represents a range of the song that should be played.
|
|
49269
|
+
* @public
|
|
49270
|
+
*/
|
|
49271
|
+
class PlaybackRange {
|
|
49272
|
+
/**
|
|
49273
|
+
* The position in midi ticks from where the song should start.
|
|
49274
|
+
*/
|
|
49275
|
+
startTick = 0;
|
|
49276
|
+
/**
|
|
49277
|
+
* The position in midi ticks to where the song should be played.
|
|
49278
|
+
*/
|
|
49279
|
+
endTick = 0;
|
|
49280
|
+
}
|
|
49281
|
+
|
|
49164
49282
|
/**
|
|
49165
49283
|
* Describes how a cursor should be moving.
|
|
49166
49284
|
* @public
|
|
@@ -49321,6 +49439,13 @@ class MidiTickLookup {
|
|
|
49321
49439
|
* @internal
|
|
49322
49440
|
*/
|
|
49323
49441
|
masterBarLookup = new Map();
|
|
49442
|
+
/**
|
|
49443
|
+
* A dictionary of all beat played. The index is the id to {@link Beat.id}.
|
|
49444
|
+
* The value is the bar relative tick time at which the beat was registered during midi generation.
|
|
49445
|
+
* This lookup only contains the first time a Beat is played.
|
|
49446
|
+
* @internal
|
|
49447
|
+
*/
|
|
49448
|
+
beatLookup = new Map();
|
|
49324
49449
|
/**
|
|
49325
49450
|
* A list of all {@link MasterBarTickLookup} sorted by time.
|
|
49326
49451
|
*/
|
|
@@ -49687,10 +49812,22 @@ class MidiTickLookup {
|
|
|
49687
49812
|
* @returns The time in midi ticks at which the beat is played the first time or 0 if the beat is not contained
|
|
49688
49813
|
*/
|
|
49689
49814
|
getBeatStart(beat) {
|
|
49690
|
-
if (!this.masterBarLookup.has(beat.voice.bar.index)) {
|
|
49815
|
+
if (!this.masterBarLookup.has(beat.voice.bar.index) || !this.beatLookup.has(beat.id)) {
|
|
49691
49816
|
return 0;
|
|
49692
49817
|
}
|
|
49693
|
-
|
|
49818
|
+
const mb = this.masterBarLookup.get(beat.voice.bar.index);
|
|
49819
|
+
return mb.start + this.beatLookup.get(beat.id).startTick;
|
|
49820
|
+
}
|
|
49821
|
+
/**
|
|
49822
|
+
* Gets the playback range in midi ticks for a given beat.
|
|
49823
|
+
* @param beat The beat to find the time period for.
|
|
49824
|
+
* @returns The relative playback range within the parent masterbar at which the beat start and ends playing
|
|
49825
|
+
*/
|
|
49826
|
+
getRelativeBeatPlaybackRange(beat) {
|
|
49827
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49828
|
+
return undefined;
|
|
49829
|
+
}
|
|
49830
|
+
return this.beatLookup.get(beat.id);
|
|
49694
49831
|
}
|
|
49695
49832
|
/**
|
|
49696
49833
|
* Adds a new {@link MasterBarTickLookup} to the lookup table.
|
|
@@ -49708,6 +49845,12 @@ class MidiTickLookup {
|
|
|
49708
49845
|
}
|
|
49709
49846
|
}
|
|
49710
49847
|
addBeat(beat, start, duration) {
|
|
49848
|
+
if (!this.beatLookup.has(beat.id)) {
|
|
49849
|
+
const playbackRange = new PlaybackRange();
|
|
49850
|
+
playbackRange.startTick = start;
|
|
49851
|
+
playbackRange.endTick = start + duration;
|
|
49852
|
+
this.beatLookup.set(beat.id, playbackRange);
|
|
49853
|
+
}
|
|
49711
49854
|
const currentMasterBar = this._currentMasterBar;
|
|
49712
49855
|
if (currentMasterBar) {
|
|
49713
49856
|
// pre-beat grace notes at the start of the bar we also add the beat to the previous bar
|
|
@@ -52291,21 +52434,6 @@ class ActiveBeatsChangedEventArgs {
|
|
|
52291
52434
|
}
|
|
52292
52435
|
}
|
|
52293
52436
|
|
|
52294
|
-
/**
|
|
52295
|
-
* Represents a range of the song that should be played.
|
|
52296
|
-
* @public
|
|
52297
|
-
*/
|
|
52298
|
-
class PlaybackRange {
|
|
52299
|
-
/**
|
|
52300
|
-
* The position in midi ticks from where the song should start.
|
|
52301
|
-
*/
|
|
52302
|
-
startTick = 0;
|
|
52303
|
-
/**
|
|
52304
|
-
* The position in midi ticks to where the song should be played.
|
|
52305
|
-
*/
|
|
52306
|
-
endTick = 0;
|
|
52307
|
-
}
|
|
52308
|
-
|
|
52309
52437
|
/**
|
|
52310
52438
|
* A {@link IAlphaSynth} implementation wrapping and underling other {@link IAlphaSynth}
|
|
52311
52439
|
* allowing dynamic changing of the underlying instance without loosing aspects like the
|
|
@@ -55506,23 +55634,24 @@ class AlphaTabApiBase {
|
|
|
55506
55634
|
if (this._selectionStart && this._tickCache) {
|
|
55507
55635
|
// get the start and stop ticks (which consider properly repeats)
|
|
55508
55636
|
const tickCache = this._tickCache;
|
|
55509
|
-
const
|
|
55637
|
+
const realStartMasterBarStart = tickCache.getMasterBarStart(this._selectionStart.beat.voice.bar.masterBar);
|
|
55638
|
+
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat);
|
|
55639
|
+
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? this._selectionStart.beat.playbackStart;
|
|
55510
55640
|
// move to selection start
|
|
55511
55641
|
this._currentBeat = null; // reset current beat so it is updating the cursor
|
|
55512
55642
|
if (this._player.state === PlayerState.Paused) {
|
|
55513
|
-
this._cursorUpdateTick(
|
|
55643
|
+
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
|
|
55514
55644
|
}
|
|
55515
|
-
this.tickPosition =
|
|
55645
|
+
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55516
55646
|
// set playback range
|
|
55517
55647
|
if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
|
|
55518
|
-
const
|
|
55648
|
+
const realEndMasterBarStart = tickCache.getMasterBarStart(this._selectionEnd.beat.voice.bar.masterBar);
|
|
55649
|
+
const endBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionEnd.beat);
|
|
55650
|
+
const endBeatPlaybackEnd = endBeatPlaybackRange?.endTick ??
|
|
55651
|
+
this._selectionEnd.beat.playbackStart + this._selectionEnd.beat.playbackDuration;
|
|
55519
55652
|
const range = new PlaybackRange();
|
|
55520
|
-
range.startTick =
|
|
55521
|
-
range.endTick =
|
|
55522
|
-
realMasterBarEnd +
|
|
55523
|
-
this._selectionEnd.beat.playbackStart +
|
|
55524
|
-
this._selectionEnd.beat.playbackDuration -
|
|
55525
|
-
50;
|
|
55653
|
+
range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
|
|
55654
|
+
range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
|
|
55526
55655
|
this.playbackRange = range;
|
|
55527
55656
|
}
|
|
55528
55657
|
else {
|
|
@@ -61569,6 +61698,12 @@ class TieGlyph extends Glyph {
|
|
|
61569
61698
|
_tieHeight = 0;
|
|
61570
61699
|
_boundingBox;
|
|
61571
61700
|
_shouldPaint = false;
|
|
61701
|
+
// Resolved per-label paint state. Lazily grown; re-layouts mutate
|
|
61702
|
+
// existing entries in place and update `_resolvedLabelCount` to
|
|
61703
|
+
// signal how many of them are valid this pass.
|
|
61704
|
+
_resolvedLabels = [];
|
|
61705
|
+
_resolvedLabelCount = 0;
|
|
61706
|
+
_labelBaselineOffset = 0;
|
|
61572
61707
|
get checkForOverflow() {
|
|
61573
61708
|
return this._shouldPaint && this._boundingBox !== undefined;
|
|
61574
61709
|
}
|
|
@@ -61638,16 +61773,88 @@ class TieGlyph extends Glyph {
|
|
|
61638
61773
|
}
|
|
61639
61774
|
this._boundingBox = undefined;
|
|
61640
61775
|
this.y = Math.min(this._startY, this._endY);
|
|
61776
|
+
const down = this.tieDirection === BeamDirection.Down;
|
|
61641
61777
|
let tieBoundingBox;
|
|
61778
|
+
// Bezier control points for the tie. Computed once and reused
|
|
61779
|
+
// for both the bounding box (via _calculateActualTieHeightFromCps)
|
|
61780
|
+
// and label-apex sampling further below — avoids a redundant
|
|
61781
|
+
// call to _computeBezierControlPoints (and its 14-element array
|
|
61782
|
+
// allocation) per labeled slur per layout.
|
|
61783
|
+
let cps = [];
|
|
61642
61784
|
if (this.shouldDrawBendSlur()) {
|
|
61643
61785
|
this._tieHeight = 0;
|
|
61644
|
-
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY,
|
|
61786
|
+
tieBoundingBox = TieGlyph.calculateBendSlurHeight(this._startX, this._startY, this._endX, this._endY, down, this.renderer.smuflMetrics.tieHeight);
|
|
61645
61787
|
}
|
|
61646
61788
|
else {
|
|
61647
61789
|
this._tieHeight = this.getTieHeight(this._startX, this._startY, this._endX, this._endY);
|
|
61648
|
-
|
|
61790
|
+
const tieThickness = this.renderer.smuflMetrics.tieMidpointThickness;
|
|
61791
|
+
cps = TieGlyph._computeBezierControlPoints(1, this._startX, this._startY, this._endX, this._endY, down, this._tieHeight, tieThickness);
|
|
61792
|
+
tieBoundingBox = TieGlyph._calculateActualTieHeightFromCps(cps, this._startX, this._startY, this._endX, this._endY, down, tieThickness);
|
|
61649
61793
|
}
|
|
61650
61794
|
this._boundingBox = tieBoundingBox;
|
|
61795
|
+
this._resolvedLabelCount = 0;
|
|
61796
|
+
const labels = this.getSlurLabels();
|
|
61797
|
+
if (labels !== null && labels.length > 0 && this.shouldPaintLabels()) {
|
|
61798
|
+
const res = this.renderer.settings.display.resources;
|
|
61799
|
+
const padding = this.renderer.smuflMetrics.oneStaffSpace * 0.25;
|
|
61800
|
+
let maxTextHeight = 0;
|
|
61801
|
+
// Single Y line for all labels — the outer arc apex.
|
|
61802
|
+
// Painted offset adds `padding` on the outward side, so
|
|
61803
|
+
// every label sits the same fixed distance from its arc.
|
|
61804
|
+
const labelLineY = cps.length > 0
|
|
61805
|
+
? 0.125 * cps[7] + 0.375 * cps[9] + 0.375 * cps[11] + 0.125 * cps[13]
|
|
61806
|
+
: (this._startY + this._endY) / 2;
|
|
61807
|
+
for (const label of labels) {
|
|
61808
|
+
const fromX = this.resolveLabelAnchorX(label.fromNote);
|
|
61809
|
+
const toX = this.resolveLabelAnchorX(label.toNote);
|
|
61810
|
+
if (fromX === null || toX === null) {
|
|
61811
|
+
continue;
|
|
61812
|
+
}
|
|
61813
|
+
const midX = (fromX + toX) / 2;
|
|
61814
|
+
if (midX < this._startX || midX > this._endX) {
|
|
61815
|
+
continue;
|
|
61816
|
+
}
|
|
61817
|
+
// Per-element font.size as an upper bound on glyph
|
|
61818
|
+
// height — avoids per-label measureText calls. All H/P
|
|
61819
|
+
// and sl. labels use the same _effectFont, so this is
|
|
61820
|
+
// typically computed once.
|
|
61821
|
+
const font = res.getFontForNotationElement(label.element);
|
|
61822
|
+
if (font.size > maxTextHeight) {
|
|
61823
|
+
maxTextHeight = font.size;
|
|
61824
|
+
}
|
|
61825
|
+
// grow cache lazily; mutate existing slot in place otherwise
|
|
61826
|
+
let slot;
|
|
61827
|
+
if (this._resolvedLabelCount < this._resolvedLabels.length) {
|
|
61828
|
+
slot = this._resolvedLabels[this._resolvedLabelCount];
|
|
61829
|
+
slot.x = midX;
|
|
61830
|
+
slot.y = labelLineY;
|
|
61831
|
+
slot.text = label.text;
|
|
61832
|
+
slot.element = label.element;
|
|
61833
|
+
}
|
|
61834
|
+
else {
|
|
61835
|
+
slot = {
|
|
61836
|
+
x: midX,
|
|
61837
|
+
y: labelLineY,
|
|
61838
|
+
text: label.text,
|
|
61839
|
+
element: label.element
|
|
61840
|
+
};
|
|
61841
|
+
this._resolvedLabels.push(slot);
|
|
61842
|
+
}
|
|
61843
|
+
this._resolvedLabelCount++;
|
|
61844
|
+
}
|
|
61845
|
+
if (this._resolvedLabelCount > 0) {
|
|
61846
|
+
// canvas.textBaseline is 'hanging' (TextBaseline.Top), so
|
|
61847
|
+
// fillText positions `y` at the glyph's top edge.
|
|
61848
|
+
if (this.tieDirection === BeamDirection.Up) {
|
|
61849
|
+
tieBoundingBox.y -= maxTextHeight + padding;
|
|
61850
|
+
this._labelBaselineOffset = -(maxTextHeight + padding);
|
|
61851
|
+
}
|
|
61852
|
+
else {
|
|
61853
|
+
this._labelBaselineOffset = padding;
|
|
61854
|
+
}
|
|
61855
|
+
tieBoundingBox.h += maxTextHeight + padding;
|
|
61856
|
+
}
|
|
61857
|
+
}
|
|
61651
61858
|
this.height = tieBoundingBox.h;
|
|
61652
61859
|
if (this.tieDirection === BeamDirection.Up) {
|
|
61653
61860
|
// the tie might go above `this.y` due to its shape
|
|
@@ -61663,12 +61870,76 @@ class TieGlyph extends Glyph {
|
|
|
61663
61870
|
if (!this._shouldPaint) {
|
|
61664
61871
|
return;
|
|
61665
61872
|
}
|
|
61873
|
+
const isDown = this.tieDirection === BeamDirection.Down;
|
|
61666
61874
|
if (this.shouldDrawBendSlur()) {
|
|
61667
|
-
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY,
|
|
61875
|
+
TieGlyph.drawBendSlur(canvas, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this.renderer.smuflMetrics.tieHeight);
|
|
61876
|
+
}
|
|
61877
|
+
else {
|
|
61878
|
+
TieGlyph.paintTie(canvas, 1, cx + this._startX, cy + this._startY, cx + this._endX, cy + this._endY, isDown, this._tieHeight, this.renderer.smuflMetrics.tieMidpointThickness);
|
|
61879
|
+
}
|
|
61880
|
+
if (this._resolvedLabelCount > 0) {
|
|
61881
|
+
const ta = canvas.textAlign;
|
|
61882
|
+
const tb = canvas.textBaseline;
|
|
61883
|
+
canvas.textAlign = TextAlign.Center;
|
|
61884
|
+
canvas.textBaseline = TextBaseline.Top;
|
|
61885
|
+
const res = this.renderer.resources;
|
|
61886
|
+
let lastElement = -1;
|
|
61887
|
+
for (let i = 0; i < this._resolvedLabelCount; i++) {
|
|
61888
|
+
const label = this._resolvedLabels[i];
|
|
61889
|
+
if (label.element !== lastElement) {
|
|
61890
|
+
canvas.font = res.getFontForNotationElement(label.element);
|
|
61891
|
+
lastElement = label.element;
|
|
61892
|
+
}
|
|
61893
|
+
canvas.fillText(label.text, cx + label.x, cy + label.y + this._labelBaselineOffset);
|
|
61894
|
+
}
|
|
61895
|
+
canvas.textAlign = ta;
|
|
61896
|
+
canvas.textBaseline = tb;
|
|
61897
|
+
}
|
|
61898
|
+
}
|
|
61899
|
+
/**
|
|
61900
|
+
* Returns the labels to paint along this slur, or `null` when there
|
|
61901
|
+
* are none. Override in subclasses.
|
|
61902
|
+
*/
|
|
61903
|
+
getSlurLabels() {
|
|
61904
|
+
return null;
|
|
61905
|
+
}
|
|
61906
|
+
/**
|
|
61907
|
+
* Whether label painting is enabled. Defaults to `true`. Subclasses
|
|
61908
|
+
* may override to disable labels on the bend-slur path or other
|
|
61909
|
+
* special cases.
|
|
61910
|
+
*/
|
|
61911
|
+
shouldPaintLabels() {
|
|
61912
|
+
return !this.shouldDrawBendSlur();
|
|
61913
|
+
}
|
|
61914
|
+
/**
|
|
61915
|
+
* Looks up the absolute X coordinate of an anchor note. Reuses
|
|
61916
|
+
* the start/end bar renderers already resolved by the subclass
|
|
61917
|
+
* (NoteTieGlyph) when the note's bar matches — most labels live
|
|
61918
|
+
* in the slur's start or end bar, so this avoids the double Map
|
|
61919
|
+
* lookup in `getRendererForBar` per label per layout. Returns
|
|
61920
|
+
* `null` when the note's bar is not rendered on this glyph's
|
|
61921
|
+
* staff (cross-system case).
|
|
61922
|
+
*/
|
|
61923
|
+
resolveLabelAnchorX(note) {
|
|
61924
|
+
const bar = note.beat.voice.bar;
|
|
61925
|
+
let renderer = null;
|
|
61926
|
+
const start = this.lookupStartBeatRenderer();
|
|
61927
|
+
if (start !== null && start.bar === bar) {
|
|
61928
|
+
renderer = start;
|
|
61668
61929
|
}
|
|
61669
61930
|
else {
|
|
61670
|
-
|
|
61931
|
+
const end = this.lookupEndBeatRenderer();
|
|
61932
|
+
if (end !== null && end.bar === bar) {
|
|
61933
|
+
renderer = end;
|
|
61934
|
+
}
|
|
61935
|
+
else {
|
|
61936
|
+
renderer = this.renderer.scoreRenderer.layout.getRendererForBar(this.renderer.staff.staffId, bar);
|
|
61937
|
+
}
|
|
61938
|
+
}
|
|
61939
|
+
if (renderer === null) {
|
|
61940
|
+
return null;
|
|
61671
61941
|
}
|
|
61942
|
+
return renderer.x + renderer.getNoteX(note, NoteXPosition.Center);
|
|
61672
61943
|
}
|
|
61673
61944
|
getTieHeight(_startX, _startY, _endX, _endY) {
|
|
61674
61945
|
return this.renderer.smuflMetrics.tieHeight;
|
|
@@ -61688,11 +61959,18 @@ class TieGlyph extends Glyph {
|
|
|
61688
61959
|
}
|
|
61689
61960
|
static calculateActualTieHeight(scale, x1, y1, x2, y2, down, offset, size) {
|
|
61690
61961
|
const cp = TieGlyph._computeBezierControlPoints(scale, x1, y1, x2, y2, down, offset, size);
|
|
61962
|
+
return TieGlyph._calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size);
|
|
61963
|
+
}
|
|
61964
|
+
/**
|
|
61965
|
+
* Derives the bounding box for a tie from already-computed control
|
|
61966
|
+
* points. Splits the bbox math from cps generation so callers that
|
|
61967
|
+
* need BOTH cps and bbox (e.g. multi-label slur layout) avoid a
|
|
61968
|
+
* second call to `_computeBezierControlPoints`.
|
|
61969
|
+
*/
|
|
61970
|
+
static _calculateActualTieHeightFromCps(cp, x1, y1, x2, y2, down, size) {
|
|
61691
61971
|
if (cp.length === 0) {
|
|
61692
61972
|
return new Bounds(x1, y1, x2 - x1, y2 - y1);
|
|
61693
61973
|
}
|
|
61694
|
-
// For a musical tie/slur, the extrema occur predictably near the midpoint
|
|
61695
|
-
// Evaluate at midpoint (t=0.5) and check endpoints
|
|
61696
61974
|
const p0x = cp[0];
|
|
61697
61975
|
const p0y = cp[1];
|
|
61698
61976
|
const c1x = cp[2];
|
|
@@ -61701,15 +61979,12 @@ class TieGlyph extends Glyph {
|
|
|
61701
61979
|
const c2y = cp[5];
|
|
61702
61980
|
const p1x = cp[6];
|
|
61703
61981
|
const p1y = cp[7];
|
|
61704
|
-
// Evaluate at t=0.5 for midpoint
|
|
61705
61982
|
const midX = 0.125 * p0x + 0.375 * c1x + 0.375 * c2x + 0.125 * p1x;
|
|
61706
61983
|
const midY = 0.125 * p0y + 0.375 * c1y + 0.375 * c2y + 0.125 * p1y;
|
|
61707
|
-
// Bounds are simply min/max of start, end, and midpoint
|
|
61708
61984
|
const xMin = Math.min(p0x, p1x, midX);
|
|
61709
61985
|
const xMax = Math.max(p0x, p1x, midX);
|
|
61710
61986
|
let yMin = Math.min(p0y, p1y, midY);
|
|
61711
61987
|
let yMax = Math.max(p0y, p1y, midY);
|
|
61712
|
-
// Account for thickness of the tie/slur
|
|
61713
61988
|
if (down) {
|
|
61714
61989
|
yMax += size;
|
|
61715
61990
|
}
|
|
@@ -68898,11 +69173,43 @@ class TabTieGlyph extends NoteTieGlyph {
|
|
|
68898
69173
|
}
|
|
68899
69174
|
}
|
|
68900
69175
|
|
|
69176
|
+
/**
|
|
69177
|
+
* Helpers for building `TieGlyphLabel` instances from model-side
|
|
69178
|
+
* {@link SlurSegment}s.
|
|
69179
|
+
* @internal
|
|
69180
|
+
*/
|
|
69181
|
+
class TieGlyphLabels {
|
|
69182
|
+
/**
|
|
69183
|
+
* Builds a `TieGlyphLabel` for one segment of a slur. The
|
|
69184
|
+
* `isAscending` flag selects between the H/P glyph for hammer-on
|
|
69185
|
+
* vs. pull-off — score side passes a comparison on `realValue`,
|
|
69186
|
+
* tab side passes a comparison on `fret`.
|
|
69187
|
+
*/
|
|
69188
|
+
static build(s, isAscending) {
|
|
69189
|
+
if (s.kind === SlurSegmentKind.LegatoSlide) {
|
|
69190
|
+
return {
|
|
69191
|
+
fromNote: s.fromNote,
|
|
69192
|
+
toNote: s.toNote,
|
|
69193
|
+
text: s.text !== null ? s.text : 'sl.',
|
|
69194
|
+
element: NotationElement.EffectSlideText
|
|
69195
|
+
};
|
|
69196
|
+
}
|
|
69197
|
+
// HammerPull
|
|
69198
|
+
return {
|
|
69199
|
+
fromNote: s.fromNote,
|
|
69200
|
+
toNote: s.toNote,
|
|
69201
|
+
text: s.text !== null ? s.text : isAscending ? 'H' : 'P',
|
|
69202
|
+
element: NotationElement.EffectHammerOnPullOffText
|
|
69203
|
+
};
|
|
69204
|
+
}
|
|
69205
|
+
}
|
|
69206
|
+
|
|
68901
69207
|
/**
|
|
68902
69208
|
* @internal
|
|
68903
69209
|
*/
|
|
68904
69210
|
class TabSlurGlyph extends TabTieGlyph {
|
|
68905
69211
|
_forSlide;
|
|
69212
|
+
_labels = null;
|
|
68906
69213
|
constructor(slurEffectId, startNote, endNote, forSlide, forEnd) {
|
|
68907
69214
|
super(slurEffectId, startNote, endNote, forEnd);
|
|
68908
69215
|
this._forSlide = forSlide;
|
|
@@ -68910,6 +69217,22 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68910
69217
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
68911
69218
|
return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
68912
69219
|
}
|
|
69220
|
+
getSlurLabels() {
|
|
69221
|
+
if (this._labels === null) {
|
|
69222
|
+
this._labels = [];
|
|
69223
|
+
const slur = this.startNote.effectSlur;
|
|
69224
|
+
if (slur !== null) {
|
|
69225
|
+
const notationSettings = this.renderer.settings.notation;
|
|
69226
|
+
for (const s of slur.segments) {
|
|
69227
|
+
const label = TieGlyphLabels.build(s, s.toNote.fret >= s.fromNote.fret);
|
|
69228
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
69229
|
+
this._labels.push(label);
|
|
69230
|
+
}
|
|
69231
|
+
}
|
|
69232
|
+
}
|
|
69233
|
+
}
|
|
69234
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
69235
|
+
}
|
|
68913
69236
|
tryExpand(startNote, endNote, forSlide, forEnd) {
|
|
68914
69237
|
// same type required
|
|
68915
69238
|
if (this._forSlide !== forSlide) {
|
|
@@ -68935,6 +69258,7 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68935
69258
|
case BeamDirection.Up:
|
|
68936
69259
|
if (startNote.realValue > this.startNote.realValue) {
|
|
68937
69260
|
this.startNote = startNote;
|
|
69261
|
+
this._labels = null; // invalidate cache — labels live on startNote
|
|
68938
69262
|
}
|
|
68939
69263
|
if (endNote.realValue > this.endNote.realValue) {
|
|
68940
69264
|
this.endNote = endNote;
|
|
@@ -68943,6 +69267,7 @@ class TabSlurGlyph extends TabTieGlyph {
|
|
|
68943
69267
|
case BeamDirection.Down:
|
|
68944
69268
|
if (startNote.realValue < this.startNote.realValue) {
|
|
68945
69269
|
this.startNote = startNote;
|
|
69270
|
+
this._labels = null;
|
|
68946
69271
|
}
|
|
68947
69272
|
if (endNote.realValue < this.endNote.realValue) {
|
|
68948
69273
|
this.endNote = endNote;
|
|
@@ -73680,9 +74005,26 @@ class ScoreTieGlyph extends NoteTieGlyph {
|
|
|
73680
74005
|
* @internal
|
|
73681
74006
|
*/
|
|
73682
74007
|
class ScoreSlurGlyph extends ScoreTieGlyph {
|
|
74008
|
+
_labels = null;
|
|
73683
74009
|
getTieHeight(startX, _startY, endX, _endY) {
|
|
73684
74010
|
return (Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
|
|
73685
74011
|
}
|
|
74012
|
+
getSlurLabels() {
|
|
74013
|
+
if (this._labels === null) {
|
|
74014
|
+
this._labels = [];
|
|
74015
|
+
const slur = this.startNote.beat.effectSlur;
|
|
74016
|
+
if (slur !== null) {
|
|
74017
|
+
const notationSettings = this.renderer.settings.notation;
|
|
74018
|
+
for (const s of slur.segments) {
|
|
74019
|
+
const label = TieGlyphLabels.build(s, s.toNote.realValue >= s.fromNote.realValue);
|
|
74020
|
+
if (notationSettings.isNotationElementVisible(label.element)) {
|
|
74021
|
+
this._labels.push(label);
|
|
74022
|
+
}
|
|
74023
|
+
}
|
|
74024
|
+
}
|
|
74025
|
+
}
|
|
74026
|
+
return this._labels.length > 0 ? this._labels : null;
|
|
74027
|
+
}
|
|
73686
74028
|
calculateStartX() {
|
|
73687
74029
|
return (this.renderer.x +
|
|
73688
74030
|
(this._isStartCentered()
|
package/dist/alphaTab.d.ts
CHANGED
|
@@ -11515,6 +11515,12 @@ declare class MidiTickLookup {
|
|
|
11515
11515
|
* @returns The time in midi ticks at which the beat is played the first time or 0 if the beat is not contained
|
|
11516
11516
|
*/
|
|
11517
11517
|
getBeatStart(beat: Beat): number;
|
|
11518
|
+
/**
|
|
11519
|
+
* Gets the playback range in midi ticks for a given beat.
|
|
11520
|
+
* @param beat The beat to find the time period for.
|
|
11521
|
+
* @returns The relative playback range within the parent masterbar at which the beat start and ends playing
|
|
11522
|
+
*/
|
|
11523
|
+
getRelativeBeatPlaybackRange(beat: Beat): PlaybackRange | undefined;
|
|
11518
11524
|
/**
|
|
11519
11525
|
* Adds a new {@link MasterBarTickLookup} to the lookup table.
|
|
11520
11526
|
* @param masterBar The item to add.
|
|
@@ -12232,7 +12238,15 @@ export declare enum NotationElement {
|
|
|
12232
12238
|
/**
|
|
12233
12239
|
* The slurs shown on bend effects within the score staff.
|
|
12234
12240
|
*/
|
|
12235
|
-
ScoreBendSlur = 55
|
|
12241
|
+
ScoreBendSlur = 55,
|
|
12242
|
+
/**
|
|
12243
|
+
* The hammer-on pull-off text shown on slurs.
|
|
12244
|
+
*/
|
|
12245
|
+
EffectHammerOnPullOffText = 56,
|
|
12246
|
+
/**
|
|
12247
|
+
* The slide text shown on slurs.
|
|
12248
|
+
*/
|
|
12249
|
+
EffectSlideText = 57
|
|
12236
12250
|
}
|
|
12237
12251
|
|
|
12238
12252
|
/**
|