@coderline/alphatab 1.9.0-alpha.1767 → 1.9.0-alpha.1785
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 +714 -264
- package/dist/alphaTab.d.ts +35 -61
- package/dist/alphaTab.js +714 -264
- 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 +9 -14
package/dist/alphaTab.core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.9.0-alpha.
|
|
2
|
+
* alphaTab v1.9.0-alpha.1785 (develop, build 1785)
|
|
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-04-
|
|
208
|
-
static commit = '
|
|
206
|
+
static version = '1.9.0-alpha.1785';
|
|
207
|
+
static date = '2026-04-27T03:55:02.662Z';
|
|
208
|
+
static commit = '760ed909a3d8dc36b159d23b4ff6780e95a3daf1';
|
|
209
209
|
static print(print) {
|
|
210
210
|
print(`alphaTab ${VersionInfo.version}`);
|
|
211
211
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -3826,7 +3826,6 @@ class Score {
|
|
|
3826
3826
|
*/
|
|
3827
3827
|
class SynthConstants {
|
|
3828
3828
|
static DefaultChannelCount = 16 + 1;
|
|
3829
|
-
static MetronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
3830
3829
|
static MetronomeKey = 33;
|
|
3831
3830
|
static AudioChannels = 2;
|
|
3832
3831
|
static MinVolume = 0;
|
|
@@ -3850,6 +3849,10 @@ class SynthConstants {
|
|
|
3850
3849
|
static DefaultPitchWheel = SynthConstants.MaxPitchWheel / 2;
|
|
3851
3850
|
static MicroBufferCount = 32;
|
|
3852
3851
|
static MicroBufferSize = 64;
|
|
3852
|
+
/**
|
|
3853
|
+
* approximately -60 dB, which is inaudible to humans
|
|
3854
|
+
*/
|
|
3855
|
+
static AudibleLevelThreshold = 1e-3;
|
|
3853
3856
|
}
|
|
3854
3857
|
|
|
3855
3858
|
/**
|
|
@@ -23814,26 +23817,26 @@ class GpifParser {
|
|
|
23814
23817
|
case 'HarmonicType':
|
|
23815
23818
|
const htype = c.findChildElement('HType');
|
|
23816
23819
|
if (htype) {
|
|
23817
|
-
switch (htype.innerText) {
|
|
23818
|
-
case '
|
|
23820
|
+
switch (htype.innerText.toLowerCase()) {
|
|
23821
|
+
case 'noharmonic':
|
|
23819
23822
|
note.harmonicType = HarmonicType.None;
|
|
23820
23823
|
break;
|
|
23821
|
-
case '
|
|
23824
|
+
case 'natural':
|
|
23822
23825
|
note.harmonicType = HarmonicType.Natural;
|
|
23823
23826
|
break;
|
|
23824
|
-
case '
|
|
23827
|
+
case 'artificial':
|
|
23825
23828
|
note.harmonicType = HarmonicType.Artificial;
|
|
23826
23829
|
break;
|
|
23827
|
-
case '
|
|
23830
|
+
case 'pinch':
|
|
23828
23831
|
note.harmonicType = HarmonicType.Pinch;
|
|
23829
23832
|
break;
|
|
23830
|
-
case '
|
|
23833
|
+
case 'tap':
|
|
23831
23834
|
note.harmonicType = HarmonicType.Tap;
|
|
23832
23835
|
break;
|
|
23833
|
-
case '
|
|
23836
|
+
case 'semi':
|
|
23834
23837
|
note.harmonicType = HarmonicType.Semi;
|
|
23835
23838
|
break;
|
|
23836
|
-
case '
|
|
23839
|
+
case 'feedback':
|
|
23837
23840
|
note.harmonicType = HarmonicType.Feedback;
|
|
23838
23841
|
break;
|
|
23839
23842
|
}
|
|
@@ -32625,9 +32628,16 @@ class DisplaySettings {
|
|
|
32625
32628
|
*
|
|
32626
32629
|
* The page layout does not use `displayWidth`. The use of absolute widths would break the proper alignments needed for this kind of display.
|
|
32627
32630
|
*
|
|
32628
|
-
*
|
|
32629
|
-
*
|
|
32630
|
-
*
|
|
32631
|
+
* In both modes, prefix and postfix glyphs (clef, key signature, time signature, barlines) are treated as fixed overhead: they keep their
|
|
32632
|
+
* natural size and the remaining staff width is distributed across bars by a per-bar weight. This matches the convention used by
|
|
32633
|
+
* Guitar Pro, Dorico, Finale, Sibelius and MuseScore. Bars that carry a system-start prefix or a mid-line clef/key/time-signature change
|
|
32634
|
+
* are therefore visibly wider than plain bars with the same weight. The weight source depends on the mode:
|
|
32635
|
+
*
|
|
32636
|
+
* * `Automatic` (default for `page` layout): weights come from the built-in spacing engine (the natural content width of each bar).
|
|
32637
|
+
* `displayScale` on the model is ignored.
|
|
32638
|
+
* * `UseModelLayout` (and the `parchment` layout): weights come from `bar.displayScale` / `masterBar.displayScale`. An unset
|
|
32639
|
+
* `displayScale` defaults to `1` and behaves identically to an explicit `1`, matching Guitar Pro (which omits the value when the
|
|
32640
|
+
* author hasn't customized it).
|
|
32631
32641
|
*
|
|
32632
32642
|
* ### Horizontal Layout
|
|
32633
32643
|
*
|
|
@@ -38826,14 +38836,25 @@ class StaffSystemBounds {
|
|
|
38826
38836
|
*/
|
|
38827
38837
|
boundsLookup;
|
|
38828
38838
|
/**
|
|
38829
|
-
*
|
|
38839
|
+
* Whether this system's bounds have already been scaled via `finish`. Prevents double-scaling
|
|
38840
|
+
* when the parent `BoundsLookup` is preserved across partial renders and `finish` is invoked
|
|
38841
|
+
* again on a mix of already-scaled (preserved) and newly-registered (natural-coordinate) systems.
|
|
38842
|
+
*/
|
|
38843
|
+
isFinished = false;
|
|
38844
|
+
/**
|
|
38845
|
+
* Finished the lookup for optimized access. Idempotent: once finished, further calls are no-ops
|
|
38846
|
+
* so preserved systems survive partial renders without being re-scaled.
|
|
38830
38847
|
*/
|
|
38831
38848
|
finish(scale = 1) {
|
|
38849
|
+
if (this.isFinished) {
|
|
38850
|
+
return;
|
|
38851
|
+
}
|
|
38832
38852
|
this.realBounds.scaleWith(scale);
|
|
38833
38853
|
this.visualBounds.scaleWith(scale);
|
|
38834
38854
|
for (const t of this.bars) {
|
|
38835
38855
|
t.finish(scale);
|
|
38836
38856
|
}
|
|
38857
|
+
this.isFinished = true;
|
|
38837
38858
|
}
|
|
38838
38859
|
/**
|
|
38839
38860
|
* Adds a new master bar to this lookup.
|
|
@@ -39006,6 +39027,58 @@ class BoundsLookup {
|
|
|
39006
39027
|
}
|
|
39007
39028
|
this.isFinished = true;
|
|
39008
39029
|
}
|
|
39030
|
+
/**
|
|
39031
|
+
* Re-opens the lookup for registrations without discarding previously registered bounds.
|
|
39032
|
+
* Used by the renderer when it preserves this lookup across a partial render so that new
|
|
39033
|
+
* bounds for the re-layouted range can be added while preserved systems stay intact.
|
|
39034
|
+
* @internal
|
|
39035
|
+
*/
|
|
39036
|
+
resetForPartialUpdate() {
|
|
39037
|
+
this.isFinished = false;
|
|
39038
|
+
}
|
|
39039
|
+
/**
|
|
39040
|
+
* Removes all entries belonging to the given master bar index and any bars after it.
|
|
39041
|
+
* Used before a partial render re-registers bounds for the re-layouted range, so the
|
|
39042
|
+
* preserved lookup ends up with only the unchanged entries when registration begins.
|
|
39043
|
+
*
|
|
39044
|
+
* Assumes the layout aligns its re-layouted range to system boundaries - i.e. the first
|
|
39045
|
+
* system to clear starts exactly at `masterBarIndex`. Caller is responsible for passing
|
|
39046
|
+
* the first master-bar-index of the first re-layouted system.
|
|
39047
|
+
* @internal
|
|
39048
|
+
*/
|
|
39049
|
+
clearFromMasterBar(masterBarIndex) {
|
|
39050
|
+
// drop staff systems whose bars start at or after the cleared range.
|
|
39051
|
+
let firstRemovedSystem = -1;
|
|
39052
|
+
for (let i = 0; i < this.staffSystems.length; i++) {
|
|
39053
|
+
const systemBars = this.staffSystems[i].bars;
|
|
39054
|
+
if (systemBars.length > 0 && systemBars[0].index >= masterBarIndex) {
|
|
39055
|
+
firstRemovedSystem = i;
|
|
39056
|
+
break;
|
|
39057
|
+
}
|
|
39058
|
+
}
|
|
39059
|
+
if (firstRemovedSystem !== -1) {
|
|
39060
|
+
this.staffSystems.splice(firstRemovedSystem, this.staffSystems.length - firstRemovedSystem);
|
|
39061
|
+
}
|
|
39062
|
+
// drop master bar entries at or beyond the cleared range.
|
|
39063
|
+
for (const key of Array.from(this._masterBarLookup.keys())) {
|
|
39064
|
+
if (key >= masterBarIndex) {
|
|
39065
|
+
this._masterBarLookup.delete(key);
|
|
39066
|
+
}
|
|
39067
|
+
}
|
|
39068
|
+
// drop beat entries whose beats belong to cleared bars.
|
|
39069
|
+
for (const key of Array.from(this._beatLookup.keys())) {
|
|
39070
|
+
const list = this._beatLookup.get(key);
|
|
39071
|
+
const filtered = list.filter(b => b.beat.voice.bar.index < masterBarIndex);
|
|
39072
|
+
if (filtered.length === 0) {
|
|
39073
|
+
this._beatLookup.delete(key);
|
|
39074
|
+
}
|
|
39075
|
+
else if (filtered.length !== list.length) {
|
|
39076
|
+
this._beatLookup.set(key, filtered);
|
|
39077
|
+
}
|
|
39078
|
+
}
|
|
39079
|
+
// drop the in-progress pointer - the next addStaffSystem call will replace it.
|
|
39080
|
+
this._currentStaffSystem = null;
|
|
39081
|
+
}
|
|
39009
39082
|
/**
|
|
39010
39083
|
* Adds a new staff sytem to the lookup.
|
|
39011
39084
|
* @param bounds The staff system bounds to add.
|
|
@@ -39223,8 +39296,11 @@ class AlphaTabWorkerScoreRenderer {
|
|
|
39223
39296
|
this.renderFinished.trigger(data.result);
|
|
39224
39297
|
break;
|
|
39225
39298
|
case 'alphaTab.postRenderFinished':
|
|
39226
|
-
|
|
39227
|
-
|
|
39299
|
+
const score = this._api.score;
|
|
39300
|
+
if (score && data.boundsLookup) {
|
|
39301
|
+
this.boundsLookup = BoundsLookup.fromJson(data.boundsLookup, this._api.score);
|
|
39302
|
+
this.boundsLookup?.finish();
|
|
39303
|
+
}
|
|
39228
39304
|
this.postRenderFinished.trigger();
|
|
39229
39305
|
break;
|
|
39230
39306
|
case 'alphaTab.error':
|
|
@@ -39481,6 +39557,164 @@ class AudioElementBackingTrackSynthOutput {
|
|
|
39481
39557
|
}
|
|
39482
39558
|
}
|
|
39483
39559
|
|
|
39560
|
+
/**
|
|
39561
|
+
* @internal
|
|
39562
|
+
*/
|
|
39563
|
+
class QueueItem {
|
|
39564
|
+
value;
|
|
39565
|
+
next;
|
|
39566
|
+
constructor(value) {
|
|
39567
|
+
this.value = value;
|
|
39568
|
+
}
|
|
39569
|
+
}
|
|
39570
|
+
/**
|
|
39571
|
+
* @internal
|
|
39572
|
+
*/
|
|
39573
|
+
class Queue {
|
|
39574
|
+
_head;
|
|
39575
|
+
_tail;
|
|
39576
|
+
get isEmpty() {
|
|
39577
|
+
return this._head === undefined;
|
|
39578
|
+
}
|
|
39579
|
+
clear() {
|
|
39580
|
+
this._head = undefined;
|
|
39581
|
+
this._tail = undefined;
|
|
39582
|
+
}
|
|
39583
|
+
enqueue(item) {
|
|
39584
|
+
const queueItem = new QueueItem(item);
|
|
39585
|
+
if (this._tail) {
|
|
39586
|
+
// not empty -> add after tail
|
|
39587
|
+
this._tail.next = queueItem;
|
|
39588
|
+
this._tail = queueItem;
|
|
39589
|
+
}
|
|
39590
|
+
else {
|
|
39591
|
+
// empty -> new item takes head and tail
|
|
39592
|
+
this._head = queueItem;
|
|
39593
|
+
this._tail = queueItem;
|
|
39594
|
+
}
|
|
39595
|
+
}
|
|
39596
|
+
enqueueFront(item) {
|
|
39597
|
+
const queueItem = new QueueItem(item);
|
|
39598
|
+
queueItem.next = this._head;
|
|
39599
|
+
if (this._head) {
|
|
39600
|
+
this._head = queueItem;
|
|
39601
|
+
}
|
|
39602
|
+
else {
|
|
39603
|
+
this._head = queueItem;
|
|
39604
|
+
this._tail = queueItem;
|
|
39605
|
+
}
|
|
39606
|
+
}
|
|
39607
|
+
peek() {
|
|
39608
|
+
const head = this._head;
|
|
39609
|
+
if (!head) {
|
|
39610
|
+
return undefined;
|
|
39611
|
+
}
|
|
39612
|
+
return head.value;
|
|
39613
|
+
}
|
|
39614
|
+
dequeue() {
|
|
39615
|
+
const head = this._head;
|
|
39616
|
+
if (!head) {
|
|
39617
|
+
return undefined;
|
|
39618
|
+
}
|
|
39619
|
+
const newHead = head.next;
|
|
39620
|
+
this._head = newHead;
|
|
39621
|
+
// last item removed?
|
|
39622
|
+
if (!newHead) {
|
|
39623
|
+
this._tail = undefined;
|
|
39624
|
+
}
|
|
39625
|
+
return head.value;
|
|
39626
|
+
}
|
|
39627
|
+
}
|
|
39628
|
+
|
|
39629
|
+
/**
|
|
39630
|
+
* The options controlling how to export the audio.
|
|
39631
|
+
* @public
|
|
39632
|
+
*/
|
|
39633
|
+
class AudioExportOptions {
|
|
39634
|
+
/**
|
|
39635
|
+
* The soundfonts to load and use for generating the audio.
|
|
39636
|
+
* If not provided, the already loaded soundfonts of the synthesizer will be used.
|
|
39637
|
+
* If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
|
|
39638
|
+
*/
|
|
39639
|
+
soundFonts;
|
|
39640
|
+
/**
|
|
39641
|
+
* The output sample rate.
|
|
39642
|
+
* @default `44100`
|
|
39643
|
+
*/
|
|
39644
|
+
sampleRate = 44100;
|
|
39645
|
+
/**
|
|
39646
|
+
* Whether to respect sync point information during export.
|
|
39647
|
+
* @default `true`
|
|
39648
|
+
* @remarks
|
|
39649
|
+
* If the song contains sync point information for synchronization with an external media,
|
|
39650
|
+
* this option allows controlling whether the synthesized audio is aligned with these points.
|
|
39651
|
+
*
|
|
39652
|
+
* This is useful when mixing the exported audio together with external media, keeping the same timing.
|
|
39653
|
+
*
|
|
39654
|
+
* Disable this option if you want the original/exact timing as per music sheet in the exported audio.
|
|
39655
|
+
*/
|
|
39656
|
+
useSyncPoints = false;
|
|
39657
|
+
/**
|
|
39658
|
+
* The current master volume as percentage. (range: 0.0-3.0, default 1.0)
|
|
39659
|
+
*/
|
|
39660
|
+
masterVolume = 1;
|
|
39661
|
+
/**
|
|
39662
|
+
* The metronome volume. (range: 0.0-3.0, default 0.0)
|
|
39663
|
+
*/
|
|
39664
|
+
metronomeVolume = 0;
|
|
39665
|
+
/**
|
|
39666
|
+
* The range of the song that should be exported. Set this to null
|
|
39667
|
+
* to play the whole song.
|
|
39668
|
+
*/
|
|
39669
|
+
playbackRange;
|
|
39670
|
+
/**
|
|
39671
|
+
* The volume for individual tracks as percentage (range: 0.0-3.0).
|
|
39672
|
+
* @remarks
|
|
39673
|
+
* The key is the track index, and the value is the relative volume.
|
|
39674
|
+
* The configured volume (as per data model) still applies, this is an additional volume control.
|
|
39675
|
+
* If no custom value is set, 100% is used.
|
|
39676
|
+
* No values from the currently active synthesizer are applied.
|
|
39677
|
+
*
|
|
39678
|
+
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
39679
|
+
*/
|
|
39680
|
+
trackVolume = new Map();
|
|
39681
|
+
/**
|
|
39682
|
+
* The additional semitone pitch transpose to apply for individual tracks.
|
|
39683
|
+
* @remarks
|
|
39684
|
+
* The key is the track index, and the value is the number of semitones to apply.
|
|
39685
|
+
* No values from the currently active synthesizer are applied.
|
|
39686
|
+
*
|
|
39687
|
+
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
39688
|
+
*/
|
|
39689
|
+
trackTranspositionPitches = new Map();
|
|
39690
|
+
}
|
|
39691
|
+
/**
|
|
39692
|
+
* Represents a single chunk of audio produced.
|
|
39693
|
+
* @public
|
|
39694
|
+
*/
|
|
39695
|
+
class AudioExportChunk {
|
|
39696
|
+
/**
|
|
39697
|
+
* The generated samples for the requested chunk.
|
|
39698
|
+
*/
|
|
39699
|
+
samples;
|
|
39700
|
+
/**
|
|
39701
|
+
* The current time position within the song in milliseconds.
|
|
39702
|
+
*/
|
|
39703
|
+
currentTime = 0;
|
|
39704
|
+
/**
|
|
39705
|
+
* The total length of the song in milliseconds.
|
|
39706
|
+
*/
|
|
39707
|
+
endTime = 0;
|
|
39708
|
+
/**
|
|
39709
|
+
* The current time position within the song in midi ticks.
|
|
39710
|
+
*/
|
|
39711
|
+
currentTick = 0;
|
|
39712
|
+
/**
|
|
39713
|
+
* The total length of the song in midi ticks.
|
|
39714
|
+
*/
|
|
39715
|
+
endTick = 0;
|
|
39716
|
+
}
|
|
39717
|
+
|
|
39484
39718
|
// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT,
|
|
39485
39719
|
// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont)
|
|
39486
39720
|
// TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny
|
|
@@ -39594,6 +39828,7 @@ class MidiSequencerState {
|
|
|
39594
39828
|
endTime = 0;
|
|
39595
39829
|
currentTempo = 0;
|
|
39596
39830
|
syncPointTempo = 0;
|
|
39831
|
+
metronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
39597
39832
|
}
|
|
39598
39833
|
/**
|
|
39599
39834
|
* This sequencer dispatches midi events to the synthesizer based on the current
|
|
@@ -39606,6 +39841,9 @@ class MidiFileSequencer {
|
|
|
39606
39841
|
_mainState;
|
|
39607
39842
|
_oneTimeState = null;
|
|
39608
39843
|
_countInState = null;
|
|
39844
|
+
get metronomeChannel() {
|
|
39845
|
+
return this._mainState.metronomeChannel;
|
|
39846
|
+
}
|
|
39609
39847
|
get isPlayingMain() {
|
|
39610
39848
|
return this._currentState === this._mainState;
|
|
39611
39849
|
}
|
|
@@ -39689,7 +39927,7 @@ class MidiFileSequencer {
|
|
|
39689
39927
|
const metronomeVolume = this._synthesizer.metronomeVolume;
|
|
39690
39928
|
this._synthesizer.noteOffAll(true);
|
|
39691
39929
|
this._synthesizer.resetSoft();
|
|
39692
|
-
this._synthesizer.setupMetronomeChannel(metronomeVolume);
|
|
39930
|
+
this._synthesizer.setupMetronomeChannel(this.metronomeChannel, metronomeVolume);
|
|
39693
39931
|
}
|
|
39694
39932
|
this._mainSilentProcess(timePosition);
|
|
39695
39933
|
}
|
|
@@ -39741,6 +39979,7 @@ class MidiFileSequencer {
|
|
|
39741
39979
|
let metronomeLengthInMillis = 0;
|
|
39742
39980
|
let metronomeTick = midiFile.tickShift; // shift metronome to content
|
|
39743
39981
|
let metronomeTime = 0.0;
|
|
39982
|
+
let maxChannel = 0;
|
|
39744
39983
|
let previousTick = 0;
|
|
39745
39984
|
for (const mEvent of midiFile.events) {
|
|
39746
39985
|
const synthData = new SynthEvent(state.synthData.length, mEvent);
|
|
@@ -39782,6 +40021,9 @@ class MidiFileSequencer {
|
|
|
39782
40021
|
if (!state.firstProgramEventPerChannel.has(channel)) {
|
|
39783
40022
|
state.firstProgramEventPerChannel.set(channel, synthData);
|
|
39784
40023
|
}
|
|
40024
|
+
if (channel > maxChannel) {
|
|
40025
|
+
maxChannel = channel;
|
|
40026
|
+
}
|
|
39785
40027
|
const isPercussion = channel === SynthConstants.PercussionChannel;
|
|
39786
40028
|
if (!isPercussion) {
|
|
39787
40029
|
this.instrumentPrograms.add(programChange.program);
|
|
@@ -39793,6 +40035,9 @@ class MidiFileSequencer {
|
|
|
39793
40035
|
if (isPercussion) {
|
|
39794
40036
|
this.percussionKeys.add(noteOn.noteKey);
|
|
39795
40037
|
}
|
|
40038
|
+
if (noteOn.channel > maxChannel) {
|
|
40039
|
+
maxChannel = noteOn.channel;
|
|
40040
|
+
}
|
|
39796
40041
|
}
|
|
39797
40042
|
}
|
|
39798
40043
|
state.currentTempo = state.tempoChanges.length > 0 ? state.tempoChanges[0].bpm : bpm;
|
|
@@ -39808,6 +40053,7 @@ class MidiFileSequencer {
|
|
|
39808
40053
|
});
|
|
39809
40054
|
state.endTime = absTime;
|
|
39810
40055
|
state.endTick = absTick;
|
|
40056
|
+
state.metronomeChannel = maxChannel + 1;
|
|
39811
40057
|
return state;
|
|
39812
40058
|
}
|
|
39813
40059
|
fillMidiEventQueue() {
|
|
@@ -44013,6 +44259,11 @@ class Voice {
|
|
|
44013
44259
|
if (dynamicGain) {
|
|
44014
44260
|
noteGain = SynthHelper.decibelsToGain(this.noteGainDb + this.modLfo.level * tmpModLfoToVolume);
|
|
44015
44261
|
}
|
|
44262
|
+
// Update EG.
|
|
44263
|
+
this.ampEnv.process(blockSamples, f.outSampleRate);
|
|
44264
|
+
if (updateModEnv) {
|
|
44265
|
+
this.modEnv.process(blockSamples, f.outSampleRate);
|
|
44266
|
+
}
|
|
44016
44267
|
gainMono = noteGain * this.ampEnv.level;
|
|
44017
44268
|
if (isMuted) {
|
|
44018
44269
|
gainMono = 0;
|
|
@@ -44020,11 +44271,6 @@ class Voice {
|
|
|
44020
44271
|
else {
|
|
44021
44272
|
gainMono *= this.mixVolume;
|
|
44022
44273
|
}
|
|
44023
|
-
// Update EG.
|
|
44024
|
-
this.ampEnv.process(blockSamples, f.outSampleRate);
|
|
44025
|
-
if (updateModEnv) {
|
|
44026
|
-
this.modEnv.process(blockSamples, f.outSampleRate);
|
|
44027
|
-
}
|
|
44028
44274
|
// Update LFOs.
|
|
44029
44275
|
if (updateModLFO) {
|
|
44030
44276
|
this.modLfo.process(blockSamples);
|
|
@@ -44103,7 +44349,12 @@ class Voice {
|
|
|
44103
44349
|
}
|
|
44104
44350
|
break;
|
|
44105
44351
|
}
|
|
44106
|
-
|
|
44352
|
+
const inaudible = this.ampEnv.segment === VoiceEnvelopeSegment.Release &&
|
|
44353
|
+
Math.abs(gainMono) < SynthConstants.AudibleLevelThreshold;
|
|
44354
|
+
if (tmpSourceSamplePosition >= tmpSampleEndDbl ||
|
|
44355
|
+
this.ampEnv.segment === VoiceEnvelopeSegment.Done ||
|
|
44356
|
+
// Check if voice is inaudible during release to terminate early
|
|
44357
|
+
inaudible) {
|
|
44107
44358
|
this.kill();
|
|
44108
44359
|
return;
|
|
44109
44360
|
}
|
|
@@ -44118,75 +44369,6 @@ class Voice {
|
|
|
44118
44369
|
}
|
|
44119
44370
|
}
|
|
44120
44371
|
|
|
44121
|
-
/**
|
|
44122
|
-
* @internal
|
|
44123
|
-
*/
|
|
44124
|
-
class QueueItem {
|
|
44125
|
-
value;
|
|
44126
|
-
next;
|
|
44127
|
-
constructor(value) {
|
|
44128
|
-
this.value = value;
|
|
44129
|
-
}
|
|
44130
|
-
}
|
|
44131
|
-
/**
|
|
44132
|
-
* @internal
|
|
44133
|
-
*/
|
|
44134
|
-
class Queue {
|
|
44135
|
-
_head;
|
|
44136
|
-
_tail;
|
|
44137
|
-
get isEmpty() {
|
|
44138
|
-
return this._head === undefined;
|
|
44139
|
-
}
|
|
44140
|
-
clear() {
|
|
44141
|
-
this._head = undefined;
|
|
44142
|
-
this._tail = undefined;
|
|
44143
|
-
}
|
|
44144
|
-
enqueue(item) {
|
|
44145
|
-
const queueItem = new QueueItem(item);
|
|
44146
|
-
if (this._tail) {
|
|
44147
|
-
// not empty -> add after tail
|
|
44148
|
-
this._tail.next = queueItem;
|
|
44149
|
-
this._tail = queueItem;
|
|
44150
|
-
}
|
|
44151
|
-
else {
|
|
44152
|
-
// empty -> new item takes head and tail
|
|
44153
|
-
this._head = queueItem;
|
|
44154
|
-
this._tail = queueItem;
|
|
44155
|
-
}
|
|
44156
|
-
}
|
|
44157
|
-
enqueueFront(item) {
|
|
44158
|
-
const queueItem = new QueueItem(item);
|
|
44159
|
-
queueItem.next = this._head;
|
|
44160
|
-
if (this._head) {
|
|
44161
|
-
this._head = queueItem;
|
|
44162
|
-
}
|
|
44163
|
-
else {
|
|
44164
|
-
this._head = queueItem;
|
|
44165
|
-
this._tail = queueItem;
|
|
44166
|
-
}
|
|
44167
|
-
}
|
|
44168
|
-
peek() {
|
|
44169
|
-
const head = this._head;
|
|
44170
|
-
if (!head) {
|
|
44171
|
-
return undefined;
|
|
44172
|
-
}
|
|
44173
|
-
return head.value;
|
|
44174
|
-
}
|
|
44175
|
-
dequeue() {
|
|
44176
|
-
const head = this._head;
|
|
44177
|
-
if (!head) {
|
|
44178
|
-
return undefined;
|
|
44179
|
-
}
|
|
44180
|
-
const newHead = head.next;
|
|
44181
|
-
this._head = newHead;
|
|
44182
|
-
// last item removed?
|
|
44183
|
-
if (!newHead) {
|
|
44184
|
-
this._tail = undefined;
|
|
44185
|
-
}
|
|
44186
|
-
return head.value;
|
|
44187
|
-
}
|
|
44188
|
-
}
|
|
44189
|
-
|
|
44190
44372
|
/**
|
|
44191
44373
|
* Lists all midi controllers.
|
|
44192
44374
|
* @public
|
|
@@ -44343,6 +44525,7 @@ class TinySoundFont {
|
|
|
44343
44525
|
currentTempo = 0;
|
|
44344
44526
|
timeSignatureNumerator = 0;
|
|
44345
44527
|
timeSignatureDenominator = 0;
|
|
44528
|
+
_metronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
44346
44529
|
constructor(sampleRate) {
|
|
44347
44530
|
this.outSampleRate = sampleRate;
|
|
44348
44531
|
}
|
|
@@ -44447,8 +44630,8 @@ class TinySoundFont {
|
|
|
44447
44630
|
while (!this._midiEventQueue.isEmpty) {
|
|
44448
44631
|
const m = this._midiEventQueue.dequeue();
|
|
44449
44632
|
if (m.isMetronome && this.metronomeVolume > 0) {
|
|
44450
|
-
this.channelNoteOff(
|
|
44451
|
-
this.channelNoteOn(
|
|
44633
|
+
this.channelNoteOff(this._metronomeChannel, SynthConstants.MetronomeKey);
|
|
44634
|
+
this.channelNoteOn(this._metronomeChannel, SynthConstants.MetronomeKey, 95 / 127);
|
|
44452
44635
|
}
|
|
44453
44636
|
else if (m.event) {
|
|
44454
44637
|
this.processMidiMessage(m.event);
|
|
@@ -44462,7 +44645,7 @@ class TinySoundFont {
|
|
|
44462
44645
|
// channel is muted if it is either explicitley muted, or another channel is set to solo but not this one.
|
|
44463
44646
|
// exception. metronome is implicitly added in solo
|
|
44464
44647
|
const isChannelMuted = this._mutedChannels.has(channel) ||
|
|
44465
|
-
(anySolo && channel !==
|
|
44648
|
+
(anySolo && channel !== this._metronomeChannel && !this._soloChannels.has(channel));
|
|
44466
44649
|
if (!buffer) {
|
|
44467
44650
|
voice.kill();
|
|
44468
44651
|
}
|
|
@@ -44516,16 +44699,17 @@ class TinySoundFont {
|
|
|
44516
44699
|
}
|
|
44517
44700
|
}
|
|
44518
44701
|
get metronomeVolume() {
|
|
44519
|
-
return this.channelGetMixVolume(
|
|
44702
|
+
return this.channelGetMixVolume(this._metronomeChannel);
|
|
44520
44703
|
}
|
|
44521
44704
|
set metronomeVolume(value) {
|
|
44522
|
-
this.setupMetronomeChannel(value);
|
|
44705
|
+
this.setupMetronomeChannel(this._metronomeChannel, value);
|
|
44523
44706
|
}
|
|
44524
|
-
setupMetronomeChannel(volume) {
|
|
44525
|
-
this.
|
|
44707
|
+
setupMetronomeChannel(channel, volume) {
|
|
44708
|
+
this._metronomeChannel = channel;
|
|
44709
|
+
this.channelSetMixVolume(channel, volume);
|
|
44526
44710
|
if (volume > 0) {
|
|
44527
|
-
this.channelSetVolume(
|
|
44528
|
-
this.channelSetPresetNumber(
|
|
44711
|
+
this.channelSetVolume(channel, 1);
|
|
44712
|
+
this.channelSetPresetNumber(channel, 0, true);
|
|
44529
44713
|
}
|
|
44530
44714
|
}
|
|
44531
44715
|
get masterVolume() {
|
|
@@ -45605,95 +45789,6 @@ class TinySoundFont {
|
|
|
45605
45789
|
}
|
|
45606
45790
|
}
|
|
45607
45791
|
|
|
45608
|
-
/**
|
|
45609
|
-
* The options controlling how to export the audio.
|
|
45610
|
-
* @public
|
|
45611
|
-
*/
|
|
45612
|
-
class AudioExportOptions {
|
|
45613
|
-
/**
|
|
45614
|
-
* The soundfonts to load and use for generating the audio.
|
|
45615
|
-
* If not provided, the already loaded soundfonts of the synthesizer will be used.
|
|
45616
|
-
* If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
|
|
45617
|
-
*/
|
|
45618
|
-
soundFonts;
|
|
45619
|
-
/**
|
|
45620
|
-
* The output sample rate.
|
|
45621
|
-
* @default `44100`
|
|
45622
|
-
*/
|
|
45623
|
-
sampleRate = 44100;
|
|
45624
|
-
/**
|
|
45625
|
-
* Whether to respect sync point information during export.
|
|
45626
|
-
* @default `true`
|
|
45627
|
-
* @remarks
|
|
45628
|
-
* If the song contains sync point information for synchronization with an external media,
|
|
45629
|
-
* this option allows controlling whether the synthesized audio is aligned with these points.
|
|
45630
|
-
*
|
|
45631
|
-
* This is useful when mixing the exported audio together with external media, keeping the same timing.
|
|
45632
|
-
*
|
|
45633
|
-
* Disable this option if you want the original/exact timing as per music sheet in the exported audio.
|
|
45634
|
-
*/
|
|
45635
|
-
useSyncPoints = false;
|
|
45636
|
-
/**
|
|
45637
|
-
* The current master volume as percentage. (range: 0.0-3.0, default 1.0)
|
|
45638
|
-
*/
|
|
45639
|
-
masterVolume = 1;
|
|
45640
|
-
/**
|
|
45641
|
-
* The metronome volume. (range: 0.0-3.0, default 0.0)
|
|
45642
|
-
*/
|
|
45643
|
-
metronomeVolume = 0;
|
|
45644
|
-
/**
|
|
45645
|
-
* The range of the song that should be exported. Set this to null
|
|
45646
|
-
* to play the whole song.
|
|
45647
|
-
*/
|
|
45648
|
-
playbackRange;
|
|
45649
|
-
/**
|
|
45650
|
-
* The volume for individual tracks as percentage (range: 0.0-3.0).
|
|
45651
|
-
* @remarks
|
|
45652
|
-
* The key is the track index, and the value is the relative volume.
|
|
45653
|
-
* The configured volume (as per data model) still applies, this is an additional volume control.
|
|
45654
|
-
* If no custom value is set, 100% is used.
|
|
45655
|
-
* No values from the currently active synthesizer are applied.
|
|
45656
|
-
*
|
|
45657
|
-
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
45658
|
-
*/
|
|
45659
|
-
trackVolume = new Map();
|
|
45660
|
-
/**
|
|
45661
|
-
* The additional semitone pitch transpose to apply for individual tracks.
|
|
45662
|
-
* @remarks
|
|
45663
|
-
* The key is the track index, and the value is the number of semitones to apply.
|
|
45664
|
-
* No values from the currently active synthesizer are applied.
|
|
45665
|
-
*
|
|
45666
|
-
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
45667
|
-
*/
|
|
45668
|
-
trackTranspositionPitches = new Map();
|
|
45669
|
-
}
|
|
45670
|
-
/**
|
|
45671
|
-
* Represents a single chunk of audio produced.
|
|
45672
|
-
* @public
|
|
45673
|
-
*/
|
|
45674
|
-
class AudioExportChunk {
|
|
45675
|
-
/**
|
|
45676
|
-
* The generated samples for the requested chunk.
|
|
45677
|
-
*/
|
|
45678
|
-
samples;
|
|
45679
|
-
/**
|
|
45680
|
-
* The current time position within the song in milliseconds.
|
|
45681
|
-
*/
|
|
45682
|
-
currentTime = 0;
|
|
45683
|
-
/**
|
|
45684
|
-
* The total length of the song in milliseconds.
|
|
45685
|
-
*/
|
|
45686
|
-
endTime = 0;
|
|
45687
|
-
/**
|
|
45688
|
-
* The current time position within the song in midi ticks.
|
|
45689
|
-
*/
|
|
45690
|
-
currentTick = 0;
|
|
45691
|
-
/**
|
|
45692
|
-
* The total length of the song in midi ticks.
|
|
45693
|
-
*/
|
|
45694
|
-
endTick = 0;
|
|
45695
|
-
}
|
|
45696
|
-
|
|
45697
45792
|
/**
|
|
45698
45793
|
* This is the base class for synthesizer components which can be used to
|
|
45699
45794
|
* play a {@link MidiFile} via a {@link ISynthOutput}.
|
|
@@ -45902,6 +45997,17 @@ class AlphaSynthBase {
|
|
|
45902
45997
|
}
|
|
45903
45998
|
this._notPlayedSamples += samples.length;
|
|
45904
45999
|
this.output.addSamples(samples);
|
|
46000
|
+
// if the sequencer finished, we instantly force a noteOff on all
|
|
46001
|
+
// voices to complete playback and stop voices fast.
|
|
46002
|
+
// Doing this in the samplePlayed callback is too late as we might
|
|
46003
|
+
// continue generating audio for long-release notes (especially percussion like cymbals)
|
|
46004
|
+
// we still have checkForFinish which takes care of the counterpart
|
|
46005
|
+
// on the sample played area to ensure we seek back.
|
|
46006
|
+
// but thanks to this code we ensure the output will complete fast as we won't
|
|
46007
|
+
// be adding more samples beside a 0.1s ramp-down
|
|
46008
|
+
if (this.sequencer.isFinished) {
|
|
46009
|
+
this.synthesizer.noteOffAll(true);
|
|
46010
|
+
}
|
|
45905
46011
|
}
|
|
45906
46012
|
else {
|
|
45907
46013
|
// Tell output that there is no data left for it.
|
|
@@ -45918,7 +46024,7 @@ class AlphaSynthBase {
|
|
|
45918
46024
|
if (this._countInVolume > 0) {
|
|
45919
46025
|
Logger.debug('AlphaSynth', 'Starting countin');
|
|
45920
46026
|
this.sequencer.startCountIn();
|
|
45921
|
-
this.synthesizer.setupMetronomeChannel(this._countInVolume);
|
|
46027
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this._countInVolume);
|
|
45922
46028
|
this.updateTimePosition(0, true);
|
|
45923
46029
|
}
|
|
45924
46030
|
this.output.play();
|
|
@@ -45930,7 +46036,7 @@ class AlphaSynthBase {
|
|
|
45930
46036
|
this._stopOneTimeMidi();
|
|
45931
46037
|
}
|
|
45932
46038
|
Logger.debug('AlphaSynth', 'Starting playback');
|
|
45933
|
-
this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
|
|
46039
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
|
|
45934
46040
|
this._synthStopping = false;
|
|
45935
46041
|
this.state = PlayerState.Playing;
|
|
45936
46042
|
this.stateChanged.trigger(new PlayerStateChangedEventArgs(this.state, false));
|
|
@@ -46014,7 +46120,7 @@ class AlphaSynthBase {
|
|
|
46014
46120
|
}
|
|
46015
46121
|
_checkReadyForPlayback() {
|
|
46016
46122
|
if (this.isReadyForPlayback) {
|
|
46017
|
-
this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
|
|
46123
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
|
|
46018
46124
|
const programs = this.sequencer.instrumentPrograms;
|
|
46019
46125
|
const percussionKeys = this.sequencer.percussionKeys;
|
|
46020
46126
|
let append = false;
|
|
@@ -46355,7 +46461,7 @@ class AlphaSynthAudioExporter {
|
|
|
46355
46461
|
_generatedAudioCurrentTime = 0;
|
|
46356
46462
|
_generatedAudioEndTime = 0;
|
|
46357
46463
|
setup() {
|
|
46358
|
-
this._synth.setupMetronomeChannel(this._synth.metronomeVolume);
|
|
46464
|
+
this._synth.setupMetronomeChannel(this._sequencer.metronomeChannel, this._synth.metronomeVolume);
|
|
46359
46465
|
const syncPoints = this._sequencer.currentSyncPoints;
|
|
46360
46466
|
const alphaTabEndTime = this._sequencer.currentEndTime;
|
|
46361
46467
|
if (syncPoints.length === 0) {
|
|
@@ -46437,7 +46543,7 @@ class BackingTrackAudioSynthesizer {
|
|
|
46437
46543
|
}
|
|
46438
46544
|
loadPresets(_hydra, _instrumentPrograms, _percussionKeys, _append) {
|
|
46439
46545
|
}
|
|
46440
|
-
setupMetronomeChannel(_metronomeVolume) {
|
|
46546
|
+
setupMetronomeChannel(_metronomeChannel, _metronomeVolume) {
|
|
46441
46547
|
}
|
|
46442
46548
|
synthesizeSilent(_sampleCount) {
|
|
46443
46549
|
this.fakeSynthesize();
|
|
@@ -46804,7 +46910,15 @@ class ScoreRenderer {
|
|
|
46804
46910
|
Logger.warning('Rendering', 'AlphaTab skipped rendering because of width=0 (element invisible)', null);
|
|
46805
46911
|
return;
|
|
46806
46912
|
}
|
|
46807
|
-
|
|
46913
|
+
// For partial renders we preserve the existing lookup so bars outside the re-layouted
|
|
46914
|
+
// range keep their already-scaled bounds - the layout will clear the changed range
|
|
46915
|
+
// before the paint pass re-registers fresh entries for it.
|
|
46916
|
+
if (renderHints?.firstChangedMasterBar !== undefined && this.boundsLookup) {
|
|
46917
|
+
this.boundsLookup.resetForPartialUpdate();
|
|
46918
|
+
}
|
|
46919
|
+
else {
|
|
46920
|
+
this.boundsLookup = new BoundsLookup();
|
|
46921
|
+
}
|
|
46808
46922
|
this._recreateCanvas();
|
|
46809
46923
|
this.canvas.lineWidth = 1;
|
|
46810
46924
|
this.canvas.settings = this.settings;
|
|
@@ -47871,6 +47985,7 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
|
|
|
47871
47985
|
_bufferTimeInMilliseconds = 0;
|
|
47872
47986
|
_settings;
|
|
47873
47987
|
_boundHandleMessage;
|
|
47988
|
+
_pendingEvents;
|
|
47874
47989
|
constructor(settings) {
|
|
47875
47990
|
super();
|
|
47876
47991
|
this._settings = settings;
|
|
@@ -47898,6 +48013,13 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
|
|
|
47898
48013
|
this.source.connect(this._worklet);
|
|
47899
48014
|
this.source.start(0);
|
|
47900
48015
|
this._worklet.connect(ctx.destination);
|
|
48016
|
+
const pending = this._pendingEvents;
|
|
48017
|
+
if (pending) {
|
|
48018
|
+
for (const e of pending) {
|
|
48019
|
+
this._worklet.port.postMessage(e);
|
|
48020
|
+
}
|
|
48021
|
+
this._pendingEvents = undefined;
|
|
48022
|
+
}
|
|
47901
48023
|
}, (reason) => {
|
|
47902
48024
|
Logger.error('WebAudio', `Audio Worklet creation failed: reason=${reason}`);
|
|
47903
48025
|
});
|
|
@@ -47924,15 +48046,26 @@ class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
|
|
|
47924
48046
|
this._worklet.disconnect();
|
|
47925
48047
|
}
|
|
47926
48048
|
this._worklet = null;
|
|
48049
|
+
this._pendingEvents = undefined;
|
|
48050
|
+
}
|
|
48051
|
+
_postWorkerMessage(message) {
|
|
48052
|
+
const worklet = this._worklet;
|
|
48053
|
+
if (worklet) {
|
|
48054
|
+
worklet.port.postMessage(message);
|
|
48055
|
+
}
|
|
48056
|
+
else {
|
|
48057
|
+
this._pendingEvents ??= [];
|
|
48058
|
+
this._pendingEvents.push(message);
|
|
48059
|
+
}
|
|
47927
48060
|
}
|
|
47928
48061
|
addSamples(f) {
|
|
47929
|
-
this.
|
|
48062
|
+
this._postWorkerMessage({
|
|
47930
48063
|
cmd: 'alphaSynth.output.addSamples',
|
|
47931
48064
|
samples: Environment.prepareForPostMessage(f)
|
|
47932
48065
|
});
|
|
47933
48066
|
}
|
|
47934
48067
|
resetSamples() {
|
|
47935
|
-
this.
|
|
48068
|
+
this._postWorkerMessage({
|
|
47936
48069
|
cmd: 'alphaSynth.output.resetSamples'
|
|
47937
48070
|
});
|
|
47938
48071
|
}
|
|
@@ -48659,9 +48792,6 @@ class BeatTickLookup {
|
|
|
48659
48792
|
* @param beat The beat to add.
|
|
48660
48793
|
*/
|
|
48661
48794
|
highlightBeat(beat, playbackStart) {
|
|
48662
|
-
if (beat.isEmpty && !beat.voice.isEmpty) {
|
|
48663
|
-
return;
|
|
48664
|
-
}
|
|
48665
48795
|
if (!this._highlightedBeats.has(beat.id)) {
|
|
48666
48796
|
this._highlightedBeats.set(beat.id, true);
|
|
48667
48797
|
this.highlightedBeats.push(new BeatTickLookupItem(beat, playbackStart));
|
|
@@ -50129,7 +50259,13 @@ class MidiFileGenerator {
|
|
|
50129
50259
|
let beatStart = beat.playbackStart;
|
|
50130
50260
|
let audioDuration = beat.playbackDuration;
|
|
50131
50261
|
const masterBarDuration = beat.voice.bar.masterBar.calculateDuration();
|
|
50132
|
-
|
|
50262
|
+
// For a bar whose voice contains a single empty beat (the typical "whole-bar rest"
|
|
50263
|
+
// placeholder inserted during score.finish), extend the beat's audio duration to cover
|
|
50264
|
+
// the full bar so cursor navigation has a beat to follow across the whole bar. Don't
|
|
50265
|
+
// apply this when the voice has multiple beats: those represent explicit rhythmic
|
|
50266
|
+
// subdivisions even when each beat is empty (e.g. a recording grid of placeholder
|
|
50267
|
+
// slots), and overriding would make every beat overlap the whole bar.
|
|
50268
|
+
if (beat.voice.bar.isEmpty && beat.voice.beats.length === 1) {
|
|
50133
50269
|
audioDuration = masterBarDuration;
|
|
50134
50270
|
}
|
|
50135
50271
|
else if (beat.voice.bar.masterBar.tripletFeel !== TripletFeel.NoTripletFeel &&
|
|
@@ -54750,7 +54886,8 @@ class AlphaTabApiBase {
|
|
|
54750
54886
|
this._isInitialBeatCursorUpdate ||
|
|
54751
54887
|
barBounds.y !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.y ||
|
|
54752
54888
|
startBeatX < previousBeatBounds.onNotesX ||
|
|
54753
|
-
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1
|
|
54889
|
+
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1 ||
|
|
54890
|
+
barBounds.h !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.h;
|
|
54754
54891
|
if (jumpCursor) {
|
|
54755
54892
|
cursorHandler.placeBeatCursor(beatCursor, beatBoundings, startBeatX);
|
|
54756
54893
|
}
|
|
@@ -55805,6 +55942,9 @@ class AlphaTabApiBase {
|
|
|
55805
55942
|
this._beatVisibilityChecker.bounds = this.boundsLookup;
|
|
55806
55943
|
this._currentBeat = null;
|
|
55807
55944
|
this._cursorUpdateTick(this._previousTick, false, 1, true, true);
|
|
55945
|
+
if (this._selectionStart) {
|
|
55946
|
+
this.highlightPlaybackRange(this._selectionStart.beat, this._selectionEnd.beat);
|
|
55947
|
+
}
|
|
55808
55948
|
this.postRenderFinished.trigger();
|
|
55809
55949
|
this.uiFacade.triggerEvent(this.container, 'postRenderFinished', null);
|
|
55810
55950
|
}
|
|
@@ -57987,8 +58127,9 @@ class AlphaTabWebWorker {
|
|
|
57987
58127
|
break;
|
|
57988
58128
|
case 'alphaTab.renderScore':
|
|
57989
58129
|
this._updateFontSizes(data.fontSizes);
|
|
58130
|
+
const renderHints = data.renderHints;
|
|
57990
58131
|
const score = data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
|
|
57991
|
-
this._renderMultiple(score, data.trackIndexes);
|
|
58132
|
+
this._renderMultiple(score, data.trackIndexes, renderHints);
|
|
57992
58133
|
break;
|
|
57993
58134
|
case 'alphaTab.updateSettings':
|
|
57994
58135
|
this._updateSettings(data.settings);
|
|
@@ -62861,6 +63002,15 @@ class BarRendererBase {
|
|
|
62861
63002
|
}
|
|
62862
63003
|
return false;
|
|
62863
63004
|
}
|
|
63005
|
+
/**
|
|
63006
|
+
* The fixed-overhead width of this renderer: glyphs that do not stretch when
|
|
63007
|
+
* the bar is scaled (clef, key signature, time signature, barlines, courtesy
|
|
63008
|
+
* accidentals, etc). Treated as a fixed allocation by the system-level layout
|
|
63009
|
+
* before distributing remaining width across bars by {@link Bar.displayScale}.
|
|
63010
|
+
*/
|
|
63011
|
+
get fixedOverhead() {
|
|
63012
|
+
return this._preBeatGlyphs.width + this._postBeatGlyphs.width;
|
|
63013
|
+
}
|
|
62864
63014
|
scaleToWidth(width) {
|
|
62865
63015
|
// preBeat and postBeat glyphs do not get resized
|
|
62866
63016
|
const containerWidth = width - this._preBeatGlyphs.width - this._postBeatGlyphs.width;
|
|
@@ -65419,6 +65569,22 @@ class BarLayoutingInfo {
|
|
|
65419
65569
|
postBeatSize = 0;
|
|
65420
65570
|
minStretchForce = 0;
|
|
65421
65571
|
totalSpringConstant = 0;
|
|
65572
|
+
/**
|
|
65573
|
+
* The smallest note duration encountered within this bar's springs, used as the reference in
|
|
65574
|
+
* the Gourlay stretch formula. Read by the owning {@link StaffSystem} so that the system can
|
|
65575
|
+
* aggregate a shared minimum across all bars and trigger a reconcile if an added bar introduces
|
|
65576
|
+
* a shorter duration than previously seen.
|
|
65577
|
+
*/
|
|
65578
|
+
get localMinDuration() {
|
|
65579
|
+
return this._minDuration;
|
|
65580
|
+
}
|
|
65581
|
+
/**
|
|
65582
|
+
* The minimum-duration reference against which the spring constants currently held by this info
|
|
65583
|
+
* were computed. Set by {@link finish} and {@link recomputeSpringConstants}. The owning
|
|
65584
|
+
* StaffSystem compares this against its system-wide minimum to decide whether spring constants
|
|
65585
|
+
* need re-derivation.
|
|
65586
|
+
*/
|
|
65587
|
+
computedWithMinDuration = 0;
|
|
65422
65588
|
_updateMinStretchForce(force) {
|
|
65423
65589
|
if (this.minStretchForce < force) {
|
|
65424
65590
|
this.minStretchForce = force;
|
|
@@ -65588,10 +65754,26 @@ class BarLayoutingInfo {
|
|
|
65588
65754
|
this._incompleteGraceRodsWidth += sp.preBeatWidth + sp.postSpringWidth;
|
|
65589
65755
|
}
|
|
65590
65756
|
}
|
|
65591
|
-
this._calculateSpringConstants();
|
|
65757
|
+
this._calculateSpringConstants(this._minDuration);
|
|
65758
|
+
this.computedWithMinDuration = this._minDuration;
|
|
65592
65759
|
this.version++;
|
|
65593
65760
|
}
|
|
65594
|
-
|
|
65761
|
+
/**
|
|
65762
|
+
* Re-derives the spring constants (and {@link minStretchForce} / {@link totalSpringConstant})
|
|
65763
|
+
* using a caller-supplied minimum-duration reference rather than this bar's local minimum.
|
|
65764
|
+
*
|
|
65765
|
+
* Called by {@link StaffSystem.reconcileMinDurationIfDirty} when a bar added later to the
|
|
65766
|
+
* system introduced a shorter note than previously seen, invalidating this bar's spring
|
|
65767
|
+
* constants. Grace-rod data is not recomputed — it is independent of the minimum-duration
|
|
65768
|
+
* reference. The internal {@link version} is bumped so downstream consumers (e.g.
|
|
65769
|
+
* {@link BarRendererBase.applyLayoutingInfo}) pick up the refreshed positions.
|
|
65770
|
+
*/
|
|
65771
|
+
recomputeSpringConstants(minDuration) {
|
|
65772
|
+
this._calculateSpringConstants(minDuration);
|
|
65773
|
+
this.computedWithMinDuration = minDuration;
|
|
65774
|
+
this.version++;
|
|
65775
|
+
}
|
|
65776
|
+
_calculateSpringConstants(minDuration) {
|
|
65595
65777
|
let totalSpringConstant = 0;
|
|
65596
65778
|
const sortedSprings = this._timeSortedSprings;
|
|
65597
65779
|
if (sortedSprings.length === 0) {
|
|
@@ -65609,7 +65791,7 @@ class BarLayoutingInfo {
|
|
|
65609
65791
|
const nextSpring = sortedSprings[i + 1];
|
|
65610
65792
|
duration = Math.abs(nextSpring.timePosition - currentSpring.timePosition);
|
|
65611
65793
|
}
|
|
65612
|
-
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration);
|
|
65794
|
+
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration, minDuration);
|
|
65613
65795
|
totalSpringConstant += 1 / currentSpring.springConstant;
|
|
65614
65796
|
}
|
|
65615
65797
|
this.totalSpringConstant = 1 / totalSpringConstant;
|
|
@@ -65669,7 +65851,7 @@ class BarLayoutingInfo {
|
|
|
65669
65851
|
// springX += this.calculateWidth(force, spring.springConstant);
|
|
65670
65852
|
// }
|
|
65671
65853
|
// }
|
|
65672
|
-
_calculateSpringConstant(spring, duration) {
|
|
65854
|
+
_calculateSpringConstant(spring, duration, minDuration) {
|
|
65673
65855
|
if (duration <= 0) {
|
|
65674
65856
|
duration = MidiUtils.toTicks(Duration.TwoHundredFiftySixth);
|
|
65675
65857
|
}
|
|
@@ -65677,7 +65859,6 @@ class BarLayoutingInfo {
|
|
|
65677
65859
|
spring.smallestDuration = duration;
|
|
65678
65860
|
}
|
|
65679
65861
|
const smallestDuration = spring.smallestDuration;
|
|
65680
|
-
const minDuration = this._minDuration;
|
|
65681
65862
|
const minDurationWidth = BarLayoutingInfo._defaultMinDurationWidth;
|
|
65682
65863
|
const phi = 1 + 0.85 * Math.log2(duration / minDuration);
|
|
65683
65864
|
return (smallestDuration / duration) * (1 / (phi * minDurationWidth));
|
|
@@ -65740,6 +65921,18 @@ class MasterBarsRenderers {
|
|
|
65740
65921
|
canWrap = true;
|
|
65741
65922
|
masterBar;
|
|
65742
65923
|
additionalMultiBarRestIndexes = null;
|
|
65924
|
+
/**
|
|
65925
|
+
* Max fixed overhead (prefix + postfix glyph width) across all staves of this bar.
|
|
65926
|
+
* Used by the layout-mode horizontal scaling pass to carve out the fixed-overhead bucket
|
|
65927
|
+
* before distributing staff width across bars.
|
|
65928
|
+
*/
|
|
65929
|
+
maxFixedOverhead = 0;
|
|
65930
|
+
/**
|
|
65931
|
+
* Max natural content width (computedWidth - fixedOverhead) across all staves of this bar.
|
|
65932
|
+
* Used as the bar weight when the layout ignores {@link MasterBar.displayScale} (e.g.
|
|
65933
|
+
* Page layout with `SystemsLayoutMode.Automatic`).
|
|
65934
|
+
*/
|
|
65935
|
+
maxContentWidth = 0;
|
|
65743
65936
|
get lastMasterBarIndex() {
|
|
65744
65937
|
if (this.additionalMultiBarRestIndexes) {
|
|
65745
65938
|
return this.additionalMultiBarRestIndexes[this.additionalMultiBarRestIndexes.length - 1];
|
|
@@ -65921,6 +66114,45 @@ class StaffSystem {
|
|
|
65921
66114
|
* This value is mainly used in the parchment style layout for correct scaling of the bars.
|
|
65922
66115
|
*/
|
|
65923
66116
|
totalBarDisplayScale = 0;
|
|
66117
|
+
/**
|
|
66118
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxFixedOverhead} across the system. The layout-mode
|
|
66119
|
+
* horizontal scaling pass subtracts this from the available staff width before distributing the
|
|
66120
|
+
* remainder across bars.
|
|
66121
|
+
*/
|
|
66122
|
+
totalFixedOverhead = 0;
|
|
66123
|
+
/**
|
|
66124
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxContentWidth} across the system. Used as the
|
|
66125
|
+
* denominator when distributing staff width in modes that weight bars by natural content width
|
|
66126
|
+
* (Page layout with `SystemsLayoutMode.Automatic`).
|
|
66127
|
+
*/
|
|
66128
|
+
totalContentWidth = 0;
|
|
66129
|
+
/**
|
|
66130
|
+
* Shortest note duration (in ticks) across every bar that has been added to this system, used
|
|
66131
|
+
* as the common reference in the Gourlay stretch formula so that rhythmically-equivalent beats
|
|
66132
|
+
* in different bars of the same system align column-wise.
|
|
66133
|
+
*
|
|
66134
|
+
* `-1` means "no bar added yet". The value only moves downward during system assembly; when a
|
|
66135
|
+
* new bar introduces a shorter minimum, {@link isMinDurationDirty} is set so that
|
|
66136
|
+
* {@link reconcileMinDurationIfDirty} can re-derive spring constants on the previously-added
|
|
66137
|
+
* bars before layout distribution runs.
|
|
66138
|
+
*/
|
|
66139
|
+
minDuration = -1;
|
|
66140
|
+
/**
|
|
66141
|
+
* Set when a bar added to this system introduced a shorter {@link minDuration} than previously
|
|
66142
|
+
* seen, leaving earlier bars' spring constants stale. Consumed by
|
|
66143
|
+
* {@link reconcileMinDurationIfDirty} which is called from `VerticalLayoutBase._fitSystem`
|
|
66144
|
+
* once the system is fully assembled.
|
|
66145
|
+
*/
|
|
66146
|
+
isMinDurationDirty = false;
|
|
66147
|
+
/**
|
|
66148
|
+
* Whether this system coordinates a shared minimum-duration reference across its bars for the
|
|
66149
|
+
* Gourlay stretch formula. Defaults to `true` for page-style and parchment layouts where bars
|
|
66150
|
+
* of a system fight for a common staff width. Set to `false` for horizontal layouts where each
|
|
66151
|
+
* bar is sized independently (by `bar.displayWidth` or its intrinsic width) and there is no
|
|
66152
|
+
* column-alignment concern - each bar keeps its local minimum so pre-existing rendering is
|
|
66153
|
+
* preserved.
|
|
66154
|
+
*/
|
|
66155
|
+
shareMinDurationAcrossBars = true;
|
|
65924
66156
|
isLast = false;
|
|
65925
66157
|
masterBarsRenderers = [];
|
|
65926
66158
|
staves = [];
|
|
@@ -65982,6 +66214,9 @@ class StaffSystem {
|
|
|
65982
66214
|
}
|
|
65983
66215
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
65984
66216
|
this._calculateAccoladeSpacing(tracks);
|
|
66217
|
+
// On the resize path the layoutingInfo was finalized in a previous layout pass, so we
|
|
66218
|
+
// only need to check whether its min-duration reference still matches the new system's.
|
|
66219
|
+
this._trackSystemMinDuration(renderers.layoutingInfo);
|
|
65985
66220
|
this._applyLayoutAndUpdateWidth();
|
|
65986
66221
|
return renderers;
|
|
65987
66222
|
}
|
|
@@ -66037,10 +66272,89 @@ class StaffSystem {
|
|
|
66037
66272
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66038
66273
|
this._calculateAccoladeSpacing(tracks);
|
|
66039
66274
|
barLayoutingInfo.finish();
|
|
66275
|
+
// Reconcile against the system-wide minimum-duration reference now that springs are
|
|
66276
|
+
// finalized. If this bar introduced a shorter note, earlier bars become stale (flagged
|
|
66277
|
+
// for bulk reconcile at fit time). If the system already had a shorter min than this
|
|
66278
|
+
// bar's local one, this bar's spring constants are recomputed immediately so the width
|
|
66279
|
+
// we return below reflects the shared reference.
|
|
66280
|
+
this._trackSystemMinDuration(barLayoutingInfo);
|
|
66040
66281
|
// ensure same widths of new renderer
|
|
66041
66282
|
result.width = this._applyLayoutAndUpdateWidth();
|
|
66042
66283
|
return result;
|
|
66043
66284
|
}
|
|
66285
|
+
/**
|
|
66286
|
+
* Updates {@link minDuration} and {@link isMinDurationDirty} when a bar is added, and brings
|
|
66287
|
+
* the just-added bar's {@link BarLayoutingInfo} in line with the current system minimum if the
|
|
66288
|
+
* system already saw a shorter reference. The bulk reconcile over previously-added bars is
|
|
66289
|
+
* deferred to {@link reconcileMinDurationIfDirty} (called from `_fitSystem`) to avoid
|
|
66290
|
+
* re-iterating the system every time a bar is appended.
|
|
66291
|
+
*/
|
|
66292
|
+
_trackSystemMinDuration(info) {
|
|
66293
|
+
if (!this.shareMinDurationAcrossBars) {
|
|
66294
|
+
return;
|
|
66295
|
+
}
|
|
66296
|
+
const localMin = info.localMinDuration;
|
|
66297
|
+
if (this.minDuration === -1 || localMin < this.minDuration) {
|
|
66298
|
+
// this bar shortens the system minimum; earlier bars (if any) are now stale
|
|
66299
|
+
if (this.masterBarsRenderers.length > 1 && localMin !== this.minDuration) {
|
|
66300
|
+
this.isMinDurationDirty = true;
|
|
66301
|
+
}
|
|
66302
|
+
this.minDuration = localMin;
|
|
66303
|
+
}
|
|
66304
|
+
if (info.computedWithMinDuration > this.minDuration) {
|
|
66305
|
+
// this bar was initialized against a larger (local) min than the system carries; pull
|
|
66306
|
+
// it down to the system reference so its computedWidth reflects the shared spacing.
|
|
66307
|
+
info.recomputeSpringConstants(this.minDuration);
|
|
66308
|
+
}
|
|
66309
|
+
}
|
|
66310
|
+
/**
|
|
66311
|
+
* Re-derives spring constants on bars whose {@link BarLayoutingInfo.computedWithMinDuration}
|
|
66312
|
+
* is out of sync with the current {@link minDuration}, and rebuilds the cached system totals
|
|
66313
|
+
* (widths, {@link totalFixedOverhead}, {@link totalContentWidth}) from the refreshed bar
|
|
66314
|
+
* widths. Called from `VerticalLayoutBase._fitSystem` after the system is fully assembled and
|
|
66315
|
+
* before distribution runs. No-op when {@link isMinDurationDirty} is false.
|
|
66316
|
+
*/
|
|
66317
|
+
reconcileMinDurationIfDirty() {
|
|
66318
|
+
if (!this.isMinDurationDirty) {
|
|
66319
|
+
return;
|
|
66320
|
+
}
|
|
66321
|
+
let systemWidth = this.accoladeWidth;
|
|
66322
|
+
let totalFixedOverhead = 0;
|
|
66323
|
+
let totalContentWidth = 0;
|
|
66324
|
+
for (const mb of this.masterBarsRenderers) {
|
|
66325
|
+
if (mb.layoutingInfo.computedWithMinDuration > this.minDuration) {
|
|
66326
|
+
mb.layoutingInfo.recomputeSpringConstants(this.minDuration);
|
|
66327
|
+
}
|
|
66328
|
+
let maxPrefix = 0;
|
|
66329
|
+
let maxContent = 0;
|
|
66330
|
+
let realWidth = 0;
|
|
66331
|
+
for (const r of mb.renderers) {
|
|
66332
|
+
r.applyLayoutingInfo();
|
|
66333
|
+
if (r.computedWidth > realWidth) {
|
|
66334
|
+
realWidth = r.computedWidth;
|
|
66335
|
+
}
|
|
66336
|
+
const overhead = r.fixedOverhead;
|
|
66337
|
+
if (overhead > maxPrefix) {
|
|
66338
|
+
maxPrefix = overhead;
|
|
66339
|
+
}
|
|
66340
|
+
const content = Math.max(0, r.computedWidth - overhead);
|
|
66341
|
+
if (content > maxContent) {
|
|
66342
|
+
maxContent = content;
|
|
66343
|
+
}
|
|
66344
|
+
}
|
|
66345
|
+
mb.maxFixedOverhead = maxPrefix;
|
|
66346
|
+
mb.maxContentWidth = maxContent;
|
|
66347
|
+
mb.width = realWidth;
|
|
66348
|
+
systemWidth += realWidth;
|
|
66349
|
+
totalFixedOverhead += maxPrefix;
|
|
66350
|
+
totalContentWidth += maxContent;
|
|
66351
|
+
}
|
|
66352
|
+
this.width = systemWidth;
|
|
66353
|
+
this.computedWidth = systemWidth;
|
|
66354
|
+
this.totalFixedOverhead = totalFixedOverhead;
|
|
66355
|
+
this.totalContentWidth = totalContentWidth;
|
|
66356
|
+
this.isMinDurationDirty = false;
|
|
66357
|
+
}
|
|
66044
66358
|
getBarDisplayScale(renderer) {
|
|
66045
66359
|
return this.staves.length > 1 ? renderer.bar.masterBar.displayScale : renderer.bar.displayScale;
|
|
66046
66360
|
}
|
|
@@ -66079,12 +66393,16 @@ class StaffSystem {
|
|
|
66079
66393
|
this.width -= width;
|
|
66080
66394
|
this.computedWidth -= width;
|
|
66081
66395
|
this.totalBarDisplayScale -= barDisplayScale;
|
|
66396
|
+
this.totalFixedOverhead -= toRemove.maxFixedOverhead;
|
|
66397
|
+
this.totalContentWidth -= toRemove.maxContentWidth;
|
|
66082
66398
|
return toRemove;
|
|
66083
66399
|
}
|
|
66084
66400
|
return null;
|
|
66085
66401
|
}
|
|
66086
66402
|
_applyLayoutAndUpdateWidth() {
|
|
66087
66403
|
let realWidth = 0;
|
|
66404
|
+
let maxFixedOverhead = 0;
|
|
66405
|
+
let maxContentWidth = 0;
|
|
66088
66406
|
let barDisplayScale = 0;
|
|
66089
66407
|
for (const s of this.allStaves) {
|
|
66090
66408
|
const last = s.barRenderers[s.barRenderers.length - 1];
|
|
@@ -66093,8 +66411,21 @@ class StaffSystem {
|
|
|
66093
66411
|
if (last.computedWidth > realWidth) {
|
|
66094
66412
|
realWidth = last.computedWidth;
|
|
66095
66413
|
}
|
|
66414
|
+
const overhead = last.fixedOverhead;
|
|
66415
|
+
if (overhead > maxFixedOverhead) {
|
|
66416
|
+
maxFixedOverhead = overhead;
|
|
66417
|
+
}
|
|
66418
|
+
const content = Math.max(0, last.computedWidth - overhead);
|
|
66419
|
+
if (content > maxContentWidth) {
|
|
66420
|
+
maxContentWidth = content;
|
|
66421
|
+
}
|
|
66096
66422
|
}
|
|
66423
|
+
const renderers = this.masterBarsRenderers[this.masterBarsRenderers.length - 1];
|
|
66424
|
+
renderers.maxFixedOverhead = maxFixedOverhead;
|
|
66425
|
+
renderers.maxContentWidth = maxContentWidth;
|
|
66097
66426
|
this.totalBarDisplayScale += barDisplayScale;
|
|
66427
|
+
this.totalFixedOverhead += maxFixedOverhead;
|
|
66428
|
+
this.totalContentWidth += maxContentWidth;
|
|
66098
66429
|
this.width += realWidth;
|
|
66099
66430
|
this.computedWidth += realWidth;
|
|
66100
66431
|
return realWidth;
|
|
@@ -66592,17 +66923,6 @@ class StaffSystem {
|
|
|
66592
66923
|
}
|
|
66593
66924
|
}
|
|
66594
66925
|
|
|
66595
|
-
/**
|
|
66596
|
-
* @internal
|
|
66597
|
-
*/
|
|
66598
|
-
class LazyPartial {
|
|
66599
|
-
args;
|
|
66600
|
-
renderCallback;
|
|
66601
|
-
constructor(args, renderCallback) {
|
|
66602
|
-
this.args = args;
|
|
66603
|
-
this.renderCallback = renderCallback;
|
|
66604
|
-
}
|
|
66605
|
-
}
|
|
66606
66926
|
/**
|
|
66607
66927
|
* This is the base class for creating new layouting engines for the score renderer.
|
|
66608
66928
|
* @internal
|
|
@@ -66633,15 +66953,21 @@ class ScoreLayout {
|
|
|
66633
66953
|
this.doResize();
|
|
66634
66954
|
}
|
|
66635
66955
|
layoutAndRender(renderHints) {
|
|
66636
|
-
this._lazyPartials.clear();
|
|
66637
66956
|
this.slurRegistry.clear();
|
|
66638
|
-
this.beamingRuleLookups.clear();
|
|
66639
|
-
this._barRendererLookup.clear();
|
|
66640
|
-
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66641
66957
|
const score = this.renderer.score;
|
|
66642
66958
|
this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
|
|
66643
66959
|
this.lastBarIndex = ModelUtils.computeLastDisplayedBarIndex(score, this.renderer.settings, this.firstBarIndex);
|
|
66644
66960
|
this.multiBarRestInfo = ModelUtils.buildMultiBarRestInfo(this.renderer.tracks, this.firstBarIndex, this.lastBarIndex);
|
|
66961
|
+
const firstChangedMasterBar = renderHints?.firstChangedMasterBar;
|
|
66962
|
+
if (firstChangedMasterBar !== undefined) {
|
|
66963
|
+
if (this.doUpdateForBars(renderHints)) {
|
|
66964
|
+
return;
|
|
66965
|
+
}
|
|
66966
|
+
}
|
|
66967
|
+
this._lazyPartials.clear();
|
|
66968
|
+
this.beamingRuleLookups.clear();
|
|
66969
|
+
this._barRendererLookup.clear();
|
|
66970
|
+
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66645
66971
|
this.pagePadding = this.renderer.settings.display.padding.map(p => p / this.renderer.settings.display.scale);
|
|
66646
66972
|
if (!this.pagePadding) {
|
|
66647
66973
|
this.pagePadding = [0, 0, 0, 0];
|
|
@@ -66656,6 +66982,9 @@ class ScoreLayout {
|
|
|
66656
66982
|
this.doLayoutAndRender(renderHints);
|
|
66657
66983
|
}
|
|
66658
66984
|
_lazyPartials = new Map();
|
|
66985
|
+
getExistingPartialArgs(id) {
|
|
66986
|
+
return this._lazyPartials.has(id) ? this._lazyPartials.get(id).args : undefined;
|
|
66987
|
+
}
|
|
66659
66988
|
registerPartial(args, callback) {
|
|
66660
66989
|
if (args.height === 0) {
|
|
66661
66990
|
return;
|
|
@@ -66674,7 +67003,11 @@ class ScoreLayout {
|
|
|
66674
67003
|
}
|
|
66675
67004
|
else {
|
|
66676
67005
|
// in case of lazy loading -> first register lazy, then notify
|
|
66677
|
-
|
|
67006
|
+
const partial = {
|
|
67007
|
+
args,
|
|
67008
|
+
renderCallback: callback
|
|
67009
|
+
};
|
|
67010
|
+
this._lazyPartials.set(args.id, partial);
|
|
66678
67011
|
this.renderer.partialLayoutFinished.trigger(args);
|
|
66679
67012
|
}
|
|
66680
67013
|
}
|
|
@@ -66969,7 +67302,7 @@ class ScoreLayout {
|
|
|
66969
67302
|
glyph.textAlign = TextAlign.Left;
|
|
66970
67303
|
}
|
|
66971
67304
|
}
|
|
66972
|
-
|
|
67305
|
+
_layoutAndRenderAnnotation(y) {
|
|
66973
67306
|
// attention, you are not allowed to remove change this notice within any version of this library without permission!
|
|
66974
67307
|
const msg = 'rendered by alphaTab';
|
|
66975
67308
|
const resources = this.renderer.settings.display.resources;
|
|
@@ -67033,6 +67366,12 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67033
67366
|
}
|
|
67034
67367
|
doResize() {
|
|
67035
67368
|
}
|
|
67369
|
+
doUpdateForBars(_renderHints) {
|
|
67370
|
+
// not supported yet, modifications likely cause anyhow full updates
|
|
67371
|
+
// as we do not optimize effect bands yet. with effect bands being more
|
|
67372
|
+
// isolated in bars we could try updating dynamically
|
|
67373
|
+
return false;
|
|
67374
|
+
}
|
|
67036
67375
|
doLayoutAndRender(renderHints) {
|
|
67037
67376
|
const score = this.renderer.score;
|
|
67038
67377
|
let startIndex = this.renderer.settings.display.startBar;
|
|
@@ -67046,6 +67385,11 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67046
67385
|
endBarIndex = startIndex + endBarIndex - 1; // map count to array index
|
|
67047
67386
|
endBarIndex = Math.min(score.masterBars.length - 1, Math.max(0, endBarIndex));
|
|
67048
67387
|
this._system = this.createEmptyStaffSystem(0);
|
|
67388
|
+
// Each bar in horizontal layout is sized independently (by bar.displayWidth or the bar's
|
|
67389
|
+
// intrinsic width), so there is no shared staff width to distribute across bars. Keep each
|
|
67390
|
+
// bar's spring constants referenced against its own local minimum-duration so rendering
|
|
67391
|
+
// matches the historical per-bar behaviour.
|
|
67392
|
+
this._system.shareMinDurationAcrossBars = false;
|
|
67049
67393
|
this._system.isLast = true;
|
|
67050
67394
|
this._system.x = this.pagePadding[0];
|
|
67051
67395
|
this._system.y = this.pagePadding[1];
|
|
@@ -67107,7 +67451,7 @@ class HorizontalScreenLayout extends ScoreLayout {
|
|
|
67107
67451
|
currentBarIndex += partial.masterBars.length;
|
|
67108
67452
|
}
|
|
67109
67453
|
this.height = this.layoutAndRenderBottomScoreInfo(this.height);
|
|
67110
|
-
this.height = this.
|
|
67454
|
+
this.height = this._layoutAndRenderAnnotation(this.height);
|
|
67111
67455
|
this.height += this.pagePadding[3];
|
|
67112
67456
|
this.height *= this.renderer.settings.display.scale;
|
|
67113
67457
|
}
|
|
@@ -67174,11 +67518,16 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67174
67518
|
_allMasterBarRenderers = [];
|
|
67175
67519
|
_barsFromPreviousSystem = [];
|
|
67176
67520
|
_reuseViewPort = false;
|
|
67521
|
+
_preSystemPartialIds = [];
|
|
67522
|
+
_systemPartialIds = [];
|
|
67177
67523
|
doLayoutAndRender(renderHints) {
|
|
67178
67524
|
let y = this.pagePadding[1];
|
|
67179
67525
|
this.width = this.renderer.width;
|
|
67180
67526
|
this._allMasterBarRenderers = [];
|
|
67527
|
+
this._preSystemPartialIds = [];
|
|
67528
|
+
this._systemPartialIds = [];
|
|
67181
67529
|
this._reuseViewPort = renderHints?.reuseViewport ?? false;
|
|
67530
|
+
this._systems = [];
|
|
67182
67531
|
//
|
|
67183
67532
|
// 1. Score Info
|
|
67184
67533
|
y = this._layoutAndRenderScoreInfo(y, -1);
|
|
@@ -67190,15 +67539,23 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67190
67539
|
y = this._layoutAndRenderChordDiagrams(y, -1);
|
|
67191
67540
|
//
|
|
67192
67541
|
// 4. One result per StaffSystem
|
|
67193
|
-
y = this._layoutAndRenderScore(y);
|
|
67542
|
+
y = this._layoutAndRenderScore(y, this.firstBarIndex);
|
|
67194
67543
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67195
|
-
y = this.
|
|
67544
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67196
67545
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67197
67546
|
}
|
|
67198
67547
|
registerPartial(args, callback) {
|
|
67199
67548
|
args.reuseViewport = this._reuseViewPort;
|
|
67200
67549
|
super.registerPartial(args, callback);
|
|
67201
67550
|
}
|
|
67551
|
+
reregisterPartial(id) {
|
|
67552
|
+
const args = this.getExistingPartialArgs(id);
|
|
67553
|
+
if (!args) {
|
|
67554
|
+
return;
|
|
67555
|
+
}
|
|
67556
|
+
args.reuseViewport = this._reuseViewPort;
|
|
67557
|
+
this.renderer.partialLayoutFinished.trigger(args);
|
|
67558
|
+
}
|
|
67202
67559
|
get supportsResize() {
|
|
67203
67560
|
return true;
|
|
67204
67561
|
}
|
|
@@ -67209,6 +67566,47 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67209
67566
|
}
|
|
67210
67567
|
return x;
|
|
67211
67568
|
}
|
|
67569
|
+
doUpdateForBars(renderHints) {
|
|
67570
|
+
this._reuseViewPort = renderHints.reuseViewport ?? false;
|
|
67571
|
+
const firstModifiedMasterBar = renderHints.firstChangedMasterBar;
|
|
67572
|
+
// first update existing systems as needed
|
|
67573
|
+
const systemIndex = this._systems.findIndex(s => {
|
|
67574
|
+
const first = s.masterBarsRenderers[0].masterBar.index;
|
|
67575
|
+
const last = s.masterBarsRenderers[s.masterBarsRenderers.length - 1].masterBar.index;
|
|
67576
|
+
return first <= firstModifiedMasterBar && firstModifiedMasterBar <= last;
|
|
67577
|
+
});
|
|
67578
|
+
if (systemIndex === -1 || !this.renderer.settings.core.enableLazyLoading) {
|
|
67579
|
+
return false;
|
|
67580
|
+
}
|
|
67581
|
+
// Bars from the start of the re-layouted system onward will be re-registered during the
|
|
67582
|
+
// paint pass. Clear their old entries from the preserved BoundsLookup so registration
|
|
67583
|
+
// produces a clean, complete lookup after this render finishes.
|
|
67584
|
+
const firstRebuiltBarIndex = this._systems[systemIndex].masterBarsRenderers[0].masterBar.index;
|
|
67585
|
+
this.renderer.boundsLookup.clearFromMasterBar(firstRebuiltBarIndex);
|
|
67586
|
+
// for now we do a full relayout from the first modified masterbar
|
|
67587
|
+
// there is a lot of room for even more performant updates, but they come
|
|
67588
|
+
// at a risk that features break.
|
|
67589
|
+
// e.g. we could only shift systems where the content didn't change,
|
|
67590
|
+
// but we might still have ties/slurs which have to be updated.
|
|
67591
|
+
const removeSystems = this._systems.splice(systemIndex, this._systems.length - systemIndex);
|
|
67592
|
+
this._systemPartialIds.splice(systemIndex, this._systemPartialIds.length - systemIndex);
|
|
67593
|
+
const system = removeSystems[0];
|
|
67594
|
+
let y = system.y;
|
|
67595
|
+
const firstBarIndex = system.masterBarsRenderers[0].masterBar.index;
|
|
67596
|
+
// signal all partials which didn't change
|
|
67597
|
+
for (const preSystemPartial of this._preSystemPartialIds) {
|
|
67598
|
+
this.reregisterPartial(preSystemPartial);
|
|
67599
|
+
}
|
|
67600
|
+
for (let i = 0; i < systemIndex; i++) {
|
|
67601
|
+
this.reregisterPartial(this._systemPartialIds[i]);
|
|
67602
|
+
}
|
|
67603
|
+
// new partials for all other prats
|
|
67604
|
+
y = this._layoutAndRenderScore(y, firstBarIndex);
|
|
67605
|
+
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67606
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67607
|
+
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67608
|
+
return true;
|
|
67609
|
+
}
|
|
67212
67610
|
doResize() {
|
|
67213
67611
|
let y = this.pagePadding[1];
|
|
67214
67612
|
this.width = this.renderer.width;
|
|
@@ -67227,7 +67625,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67227
67625
|
// 4. One result per StaffSystem
|
|
67228
67626
|
y = this._resizeAndRenderScore(y, oldHeight);
|
|
67229
67627
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67230
|
-
y = this.
|
|
67628
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67231
67629
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67232
67630
|
}
|
|
67233
67631
|
_layoutAndRenderTunings(y, totalHeight = -1) {
|
|
@@ -67251,6 +67649,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67251
67649
|
canvas.textAlign = TextAlign.Center;
|
|
67252
67650
|
this.tuningGlyph.paint(0, 0, canvas);
|
|
67253
67651
|
});
|
|
67652
|
+
this._preSystemPartialIds.push(e.id);
|
|
67254
67653
|
return y + tuningHeight;
|
|
67255
67654
|
}
|
|
67256
67655
|
_layoutAndRenderChordDiagrams(y, totalHeight = -1) {
|
|
@@ -67274,6 +67673,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67274
67673
|
canvas.textAlign = TextAlign.Center;
|
|
67275
67674
|
this.chordDiagrams.paint(0, 0, canvas);
|
|
67276
67675
|
});
|
|
67676
|
+
this._preSystemPartialIds.push(e.id);
|
|
67277
67677
|
return y + diagramHeight;
|
|
67278
67678
|
}
|
|
67279
67679
|
_layoutAndRenderScoreInfo(y, totalHeight = -1) {
|
|
@@ -67316,12 +67716,14 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67316
67716
|
g.paint(0, 0, canvas);
|
|
67317
67717
|
}
|
|
67318
67718
|
});
|
|
67719
|
+
this._preSystemPartialIds.push(e.id);
|
|
67319
67720
|
}
|
|
67320
67721
|
return y + infoHeight;
|
|
67321
67722
|
}
|
|
67322
67723
|
_resizeAndRenderScore(y, oldHeight) {
|
|
67323
67724
|
// if we have a fixed number of bars per row, we only need to refit them.
|
|
67324
67725
|
const barsPerRowActive = this.getBarsPerSystem(0) > 0;
|
|
67726
|
+
this._systemPartialIds = [];
|
|
67325
67727
|
if (barsPerRowActive) {
|
|
67326
67728
|
for (let i = 0; i < this._systems.length; i++) {
|
|
67327
67729
|
const system = this._systems[i];
|
|
@@ -67384,11 +67786,9 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67384
67786
|
}
|
|
67385
67787
|
return y;
|
|
67386
67788
|
}
|
|
67387
|
-
_layoutAndRenderScore(y) {
|
|
67388
|
-
const startIndex = this.firstBarIndex;
|
|
67789
|
+
_layoutAndRenderScore(y, startIndex) {
|
|
67389
67790
|
let currentBarIndex = startIndex;
|
|
67390
67791
|
const endBarIndex = this.lastBarIndex;
|
|
67391
|
-
this._systems = [];
|
|
67392
67792
|
while (currentBarIndex <= endBarIndex) {
|
|
67393
67793
|
// create system and align set proper coordinates
|
|
67394
67794
|
const system = this._createStaffSystem(currentBarIndex, endBarIndex);
|
|
@@ -67423,6 +67823,7 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67423
67823
|
// since we use partial drawing
|
|
67424
67824
|
system.paint(0, -(args.y / this.renderer.settings.display.scale), canvas);
|
|
67425
67825
|
});
|
|
67826
|
+
this._systemPartialIds.push(args.id);
|
|
67426
67827
|
// calculate coordinates for next system
|
|
67427
67828
|
return height;
|
|
67428
67829
|
}
|
|
@@ -67430,6 +67831,10 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67430
67831
|
* Realignes the bars in this line according to the available space
|
|
67431
67832
|
*/
|
|
67432
67833
|
_fitSystem(system) {
|
|
67834
|
+
// If a bar added late in the assembly introduced a shorter note than earlier bars, the
|
|
67835
|
+
// earlier bars' spring constants (and the cached system widths / totals) are stale.
|
|
67836
|
+
// Reconcile now - it's a no-op when nothing changed.
|
|
67837
|
+
system.reconcileMinDurationIfDirty();
|
|
67433
67838
|
if (system.isFull || system.width > this._maxWidth || this.renderer.settings.display.justifyLastSystem) {
|
|
67434
67839
|
this._scaleToWidth(system, this._maxWidth);
|
|
67435
67840
|
}
|
|
@@ -67441,29 +67846,35 @@ class VerticalLayoutBase extends ScoreLayout {
|
|
|
67441
67846
|
_scaleToWidth(system, width) {
|
|
67442
67847
|
const staffWidth = width - system.accoladeWidth;
|
|
67443
67848
|
const shouldApplyBarScale = this.shouldApplyBarScale;
|
|
67444
|
-
|
|
67445
|
-
//
|
|
67446
|
-
//
|
|
67447
|
-
//
|
|
67448
|
-
//
|
|
67449
|
-
//
|
|
67450
|
-
|
|
67451
|
-
|
|
67849
|
+
// Industry fixed-overhead model (Behind Bars, Dorico, Finale, Sibelius, MuseScore, Guitar Pro):
|
|
67850
|
+
// prefix/postfix glyphs (clef, key sig, time sig, barlines) are treated as fixed overhead and the
|
|
67851
|
+
// remaining staff width is distributed across bars by a per-bar weight.
|
|
67852
|
+
//
|
|
67853
|
+
// distributable = staffWidth - totalFixedOverhead
|
|
67854
|
+
// contentShare = distributable / sum(weight)
|
|
67855
|
+
// bar.width = bar.maxFixedOverhead + weight * contentShare
|
|
67856
|
+
//
|
|
67857
|
+
// The weight depends on the layout mode:
|
|
67858
|
+
// - shouldApplyBarScale=true -> weight = bar.displayScale (model-driven, matches Guitar Pro)
|
|
67859
|
+
// displayScale defaults to 1, so an unset value behaves identically
|
|
67860
|
+
// to an explicit 1 (GP omits the property when not customized).
|
|
67861
|
+
// - shouldApplyBarScale=false -> weight = natural content width (automatic, ignores displayScale)
|
|
67862
|
+
//
|
|
67863
|
+
// Per-bar maxFixedOverhead / maxContentWidth and the system-wide totals are maintained incrementally
|
|
67864
|
+
// in StaffSystem._applyLayoutAndUpdateWidth / revertLastBar so this pass can apply directly.
|
|
67865
|
+
const weightTotal = shouldApplyBarScale ? system.totalBarDisplayScale : system.totalContentWidth;
|
|
67866
|
+
const distributable = Math.max(0, staffWidth - system.totalFixedOverhead);
|
|
67867
|
+
const contentShare = weightTotal > 0 ? distributable / weightTotal : 0;
|
|
67452
67868
|
for (const s of system.allStaves) {
|
|
67453
67869
|
s.resetSharedLayoutData();
|
|
67454
|
-
// scale the bars by keeping their respective ratio size
|
|
67455
67870
|
let w = 0;
|
|
67456
|
-
for (
|
|
67871
|
+
for (let i = 0; i < s.barRenderers.length; i++) {
|
|
67872
|
+
const renderer = s.barRenderers[i];
|
|
67873
|
+
const mb = system.masterBarsRenderers[i];
|
|
67457
67874
|
renderer.x = w;
|
|
67458
67875
|
renderer.y = s.topPadding + s.topOverflow;
|
|
67459
|
-
|
|
67460
|
-
|
|
67461
|
-
const barDisplayScale = system.getBarDisplayScale(renderer);
|
|
67462
|
-
actualBarWidth = (barDisplayScale * staffWidth) / totalScale;
|
|
67463
|
-
}
|
|
67464
|
-
else {
|
|
67465
|
-
actualBarWidth = renderer.computedWidth + spacePerBar;
|
|
67466
|
-
}
|
|
67876
|
+
const weight = shouldApplyBarScale ? system.getBarDisplayScale(renderer) : mb.maxContentWidth;
|
|
67877
|
+
const actualBarWidth = mb.maxFixedOverhead + weight * contentShare;
|
|
67467
67878
|
renderer.scaleToWidth(actualBarWidth);
|
|
67468
67879
|
w += renderer.width;
|
|
67469
67880
|
}
|
|
@@ -69210,16 +69621,18 @@ class LineBarRenderer extends BarRendererBase {
|
|
|
69210
69621
|
s = [];
|
|
69211
69622
|
const zero = MusicFontSymbol.Tuplet0;
|
|
69212
69623
|
if (num > 10) {
|
|
69213
|
-
|
|
69214
|
-
s.push((zero +
|
|
69624
|
+
const tens = Math.floor(num / 10);
|
|
69625
|
+
s.push((zero + tens));
|
|
69626
|
+
s.push((zero + (num - 10 * tens)));
|
|
69215
69627
|
}
|
|
69216
69628
|
else {
|
|
69217
69629
|
s.push((zero + num));
|
|
69218
69630
|
}
|
|
69219
69631
|
s.push(MusicFontSymbol.TupletColon);
|
|
69220
69632
|
if (den > 10) {
|
|
69221
|
-
|
|
69222
|
-
s.push((zero +
|
|
69633
|
+
const tens = Math.floor(den / 10);
|
|
69634
|
+
s.push((zero + tens));
|
|
69635
|
+
s.push((zero + (den - 10 * tens)));
|
|
69223
69636
|
}
|
|
69224
69637
|
else {
|
|
69225
69638
|
s.push((zero + den));
|
|
@@ -69285,12 +69698,24 @@ class LineBarRenderer extends BarRendererBase {
|
|
|
69285
69698
|
const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
|
|
69286
69699
|
const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
|
|
69287
69700
|
const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
|
|
69288
|
-
let startY
|
|
69289
|
-
let endY
|
|
69701
|
+
let startY;
|
|
69702
|
+
let endY;
|
|
69290
69703
|
if (isRestOnly) {
|
|
69291
|
-
|
|
69704
|
+
// rests have no stems, so anchor to the actual rest glyph bounds
|
|
69705
|
+
// instead of a stem-adjusted flag position (which would place the bracket
|
|
69706
|
+
// a full quarter-stem length away from the rests).
|
|
69707
|
+
if (direction === BeamDirection.Up) {
|
|
69708
|
+
startY = Math.min(this.getRestY(firstNonRestBeat, NoteYPosition.Top), this.getRestY(lastNonRestBeat, NoteYPosition.Top));
|
|
69709
|
+
}
|
|
69710
|
+
else {
|
|
69711
|
+
startY = Math.max(this.getRestY(firstNonRestBeat, NoteYPosition.Bottom), this.getRestY(lastNonRestBeat, NoteYPosition.Bottom));
|
|
69712
|
+
}
|
|
69292
69713
|
endY = startY;
|
|
69293
69714
|
}
|
|
69715
|
+
else {
|
|
69716
|
+
startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
|
|
69717
|
+
endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
|
|
69718
|
+
}
|
|
69294
69719
|
// align line centered in available space
|
|
69295
69720
|
if (direction === BeamDirection.Down) {
|
|
69296
69721
|
startY += shift;
|
|
@@ -69660,7 +70085,30 @@ class LineBarRenderer extends BarRendererBase {
|
|
|
69660
70085
|
let minNoteY = 0;
|
|
69661
70086
|
for (const v of this.helpers.beamHelpers) {
|
|
69662
70087
|
for (const h of v) {
|
|
69663
|
-
if (!this.shouldPaintBeamingHelper(h))
|
|
70088
|
+
if (!this.shouldPaintBeamingHelper(h)) {
|
|
70089
|
+
// beam is not drawn, but a rest-only tuplet still draws a bracket
|
|
70090
|
+
// anchored to the rest glyph bounds and needs overflow reserved.
|
|
70091
|
+
if (h.hasTuplet && h.isRestBeamHelper) {
|
|
70092
|
+
const tupletGroup = h.beats[0].tupletGroup;
|
|
70093
|
+
const tupletFirst = tupletGroup.beats[0];
|
|
70094
|
+
const tupletLast = tupletGroup.beats[tupletGroup.beats.length - 1];
|
|
70095
|
+
const tupletDirection = this.getTupletBeamDirection(h);
|
|
70096
|
+
if (tupletDirection === BeamDirection.Up) {
|
|
70097
|
+
const restTop = Math.min(this.getRestY(tupletFirst, NoteYPosition.Top), this.getRestY(tupletLast, NoteYPosition.Top));
|
|
70098
|
+
const topY = restTop - this.tupletSize - this.tupletOffset;
|
|
70099
|
+
if (topY < maxNoteY) {
|
|
70100
|
+
maxNoteY = topY;
|
|
70101
|
+
}
|
|
70102
|
+
}
|
|
70103
|
+
else {
|
|
70104
|
+
const restBottom = Math.max(this.getRestY(tupletFirst, NoteYPosition.Bottom), this.getRestY(tupletLast, NoteYPosition.Bottom));
|
|
70105
|
+
const bottomY = restBottom + this.tupletSize + this.tupletOffset;
|
|
70106
|
+
if (bottomY > minNoteY) {
|
|
70107
|
+
minNoteY = bottomY;
|
|
70108
|
+
}
|
|
70109
|
+
}
|
|
70110
|
+
}
|
|
70111
|
+
}
|
|
69664
70112
|
else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
|
|
69665
70113
|
const tupletDirection = this.getTupletBeamDirection(h);
|
|
69666
70114
|
const direction = this.getBeamDirection(h);
|
|
@@ -72198,8 +72646,10 @@ class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
|
|
|
72198
72646
|
const group = new GlyphGroup(0, 0);
|
|
72199
72647
|
group.renderer = this.renderer;
|
|
72200
72648
|
for (const note of this.container.beat.notes) {
|
|
72201
|
-
|
|
72202
|
-
|
|
72649
|
+
if (note.isVisible) {
|
|
72650
|
+
const g = this._createBeatDot(sr.getNoteSteps(note), group);
|
|
72651
|
+
g.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationEffects, note);
|
|
72652
|
+
}
|
|
72203
72653
|
}
|
|
72204
72654
|
this.addEffect(group);
|
|
72205
72655
|
}
|