@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.js
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
|
*
|
|
@@ -209,9 +209,9 @@
|
|
|
209
209
|
* @internal
|
|
210
210
|
*/
|
|
211
211
|
class VersionInfo {
|
|
212
|
-
static version = '1.9.0-alpha.
|
|
213
|
-
static date = '2026-04-
|
|
214
|
-
static commit = '
|
|
212
|
+
static version = '1.9.0-alpha.1785';
|
|
213
|
+
static date = '2026-04-27T03:55:02.662Z';
|
|
214
|
+
static commit = '760ed909a3d8dc36b159d23b4ff6780e95a3daf1';
|
|
215
215
|
static print(print) {
|
|
216
216
|
print(`alphaTab ${VersionInfo.version}`);
|
|
217
217
|
print(`commit: ${VersionInfo.commit}`);
|
|
@@ -3832,7 +3832,6 @@
|
|
|
3832
3832
|
*/
|
|
3833
3833
|
class SynthConstants {
|
|
3834
3834
|
static DefaultChannelCount = 16 + 1;
|
|
3835
|
-
static MetronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
3836
3835
|
static MetronomeKey = 33;
|
|
3837
3836
|
static AudioChannels = 2;
|
|
3838
3837
|
static MinVolume = 0;
|
|
@@ -3856,6 +3855,10 @@
|
|
|
3856
3855
|
static DefaultPitchWheel = SynthConstants.MaxPitchWheel / 2;
|
|
3857
3856
|
static MicroBufferCount = 32;
|
|
3858
3857
|
static MicroBufferSize = 64;
|
|
3858
|
+
/**
|
|
3859
|
+
* approximately -60 dB, which is inaudible to humans
|
|
3860
|
+
*/
|
|
3861
|
+
static AudibleLevelThreshold = 1e-3;
|
|
3859
3862
|
}
|
|
3860
3863
|
|
|
3861
3864
|
/**
|
|
@@ -23820,26 +23823,26 @@
|
|
|
23820
23823
|
case 'HarmonicType':
|
|
23821
23824
|
const htype = c.findChildElement('HType');
|
|
23822
23825
|
if (htype) {
|
|
23823
|
-
switch (htype.innerText) {
|
|
23824
|
-
case '
|
|
23826
|
+
switch (htype.innerText.toLowerCase()) {
|
|
23827
|
+
case 'noharmonic':
|
|
23825
23828
|
note.harmonicType = HarmonicType.None;
|
|
23826
23829
|
break;
|
|
23827
|
-
case '
|
|
23830
|
+
case 'natural':
|
|
23828
23831
|
note.harmonicType = HarmonicType.Natural;
|
|
23829
23832
|
break;
|
|
23830
|
-
case '
|
|
23833
|
+
case 'artificial':
|
|
23831
23834
|
note.harmonicType = HarmonicType.Artificial;
|
|
23832
23835
|
break;
|
|
23833
|
-
case '
|
|
23836
|
+
case 'pinch':
|
|
23834
23837
|
note.harmonicType = HarmonicType.Pinch;
|
|
23835
23838
|
break;
|
|
23836
|
-
case '
|
|
23839
|
+
case 'tap':
|
|
23837
23840
|
note.harmonicType = HarmonicType.Tap;
|
|
23838
23841
|
break;
|
|
23839
|
-
case '
|
|
23842
|
+
case 'semi':
|
|
23840
23843
|
note.harmonicType = HarmonicType.Semi;
|
|
23841
23844
|
break;
|
|
23842
|
-
case '
|
|
23845
|
+
case 'feedback':
|
|
23843
23846
|
note.harmonicType = HarmonicType.Feedback;
|
|
23844
23847
|
break;
|
|
23845
23848
|
}
|
|
@@ -32631,9 +32634,16 @@
|
|
|
32631
32634
|
*
|
|
32632
32635
|
* The page layout does not use `displayWidth`. The use of absolute widths would break the proper alignments needed for this kind of display.
|
|
32633
32636
|
*
|
|
32634
|
-
*
|
|
32635
|
-
*
|
|
32636
|
-
*
|
|
32637
|
+
* In both modes, prefix and postfix glyphs (clef, key signature, time signature, barlines) are treated as fixed overhead: they keep their
|
|
32638
|
+
* natural size and the remaining staff width is distributed across bars by a per-bar weight. This matches the convention used by
|
|
32639
|
+
* Guitar Pro, Dorico, Finale, Sibelius and MuseScore. Bars that carry a system-start prefix or a mid-line clef/key/time-signature change
|
|
32640
|
+
* are therefore visibly wider than plain bars with the same weight. The weight source depends on the mode:
|
|
32641
|
+
*
|
|
32642
|
+
* * `Automatic` (default for `page` layout): weights come from the built-in spacing engine (the natural content width of each bar).
|
|
32643
|
+
* `displayScale` on the model is ignored.
|
|
32644
|
+
* * `UseModelLayout` (and the `parchment` layout): weights come from `bar.displayScale` / `masterBar.displayScale`. An unset
|
|
32645
|
+
* `displayScale` defaults to `1` and behaves identically to an explicit `1`, matching Guitar Pro (which omits the value when the
|
|
32646
|
+
* author hasn't customized it).
|
|
32637
32647
|
*
|
|
32638
32648
|
* ### Horizontal Layout
|
|
32639
32649
|
*
|
|
@@ -38832,14 +38842,25 @@
|
|
|
38832
38842
|
*/
|
|
38833
38843
|
boundsLookup;
|
|
38834
38844
|
/**
|
|
38835
|
-
*
|
|
38845
|
+
* Whether this system's bounds have already been scaled via `finish`. Prevents double-scaling
|
|
38846
|
+
* when the parent `BoundsLookup` is preserved across partial renders and `finish` is invoked
|
|
38847
|
+
* again on a mix of already-scaled (preserved) and newly-registered (natural-coordinate) systems.
|
|
38848
|
+
*/
|
|
38849
|
+
isFinished = false;
|
|
38850
|
+
/**
|
|
38851
|
+
* Finished the lookup for optimized access. Idempotent: once finished, further calls are no-ops
|
|
38852
|
+
* so preserved systems survive partial renders without being re-scaled.
|
|
38836
38853
|
*/
|
|
38837
38854
|
finish(scale = 1) {
|
|
38855
|
+
if (this.isFinished) {
|
|
38856
|
+
return;
|
|
38857
|
+
}
|
|
38838
38858
|
this.realBounds.scaleWith(scale);
|
|
38839
38859
|
this.visualBounds.scaleWith(scale);
|
|
38840
38860
|
for (const t of this.bars) {
|
|
38841
38861
|
t.finish(scale);
|
|
38842
38862
|
}
|
|
38863
|
+
this.isFinished = true;
|
|
38843
38864
|
}
|
|
38844
38865
|
/**
|
|
38845
38866
|
* Adds a new master bar to this lookup.
|
|
@@ -39012,6 +39033,58 @@
|
|
|
39012
39033
|
}
|
|
39013
39034
|
this.isFinished = true;
|
|
39014
39035
|
}
|
|
39036
|
+
/**
|
|
39037
|
+
* Re-opens the lookup for registrations without discarding previously registered bounds.
|
|
39038
|
+
* Used by the renderer when it preserves this lookup across a partial render so that new
|
|
39039
|
+
* bounds for the re-layouted range can be added while preserved systems stay intact.
|
|
39040
|
+
* @internal
|
|
39041
|
+
*/
|
|
39042
|
+
resetForPartialUpdate() {
|
|
39043
|
+
this.isFinished = false;
|
|
39044
|
+
}
|
|
39045
|
+
/**
|
|
39046
|
+
* Removes all entries belonging to the given master bar index and any bars after it.
|
|
39047
|
+
* Used before a partial render re-registers bounds for the re-layouted range, so the
|
|
39048
|
+
* preserved lookup ends up with only the unchanged entries when registration begins.
|
|
39049
|
+
*
|
|
39050
|
+
* Assumes the layout aligns its re-layouted range to system boundaries - i.e. the first
|
|
39051
|
+
* system to clear starts exactly at `masterBarIndex`. Caller is responsible for passing
|
|
39052
|
+
* the first master-bar-index of the first re-layouted system.
|
|
39053
|
+
* @internal
|
|
39054
|
+
*/
|
|
39055
|
+
clearFromMasterBar(masterBarIndex) {
|
|
39056
|
+
// drop staff systems whose bars start at or after the cleared range.
|
|
39057
|
+
let firstRemovedSystem = -1;
|
|
39058
|
+
for (let i = 0; i < this.staffSystems.length; i++) {
|
|
39059
|
+
const systemBars = this.staffSystems[i].bars;
|
|
39060
|
+
if (systemBars.length > 0 && systemBars[0].index >= masterBarIndex) {
|
|
39061
|
+
firstRemovedSystem = i;
|
|
39062
|
+
break;
|
|
39063
|
+
}
|
|
39064
|
+
}
|
|
39065
|
+
if (firstRemovedSystem !== -1) {
|
|
39066
|
+
this.staffSystems.splice(firstRemovedSystem, this.staffSystems.length - firstRemovedSystem);
|
|
39067
|
+
}
|
|
39068
|
+
// drop master bar entries at or beyond the cleared range.
|
|
39069
|
+
for (const key of Array.from(this._masterBarLookup.keys())) {
|
|
39070
|
+
if (key >= masterBarIndex) {
|
|
39071
|
+
this._masterBarLookup.delete(key);
|
|
39072
|
+
}
|
|
39073
|
+
}
|
|
39074
|
+
// drop beat entries whose beats belong to cleared bars.
|
|
39075
|
+
for (const key of Array.from(this._beatLookup.keys())) {
|
|
39076
|
+
const list = this._beatLookup.get(key);
|
|
39077
|
+
const filtered = list.filter(b => b.beat.voice.bar.index < masterBarIndex);
|
|
39078
|
+
if (filtered.length === 0) {
|
|
39079
|
+
this._beatLookup.delete(key);
|
|
39080
|
+
}
|
|
39081
|
+
else if (filtered.length !== list.length) {
|
|
39082
|
+
this._beatLookup.set(key, filtered);
|
|
39083
|
+
}
|
|
39084
|
+
}
|
|
39085
|
+
// drop the in-progress pointer - the next addStaffSystem call will replace it.
|
|
39086
|
+
this._currentStaffSystem = null;
|
|
39087
|
+
}
|
|
39015
39088
|
/**
|
|
39016
39089
|
* Adds a new staff sytem to the lookup.
|
|
39017
39090
|
* @param bounds The staff system bounds to add.
|
|
@@ -39229,8 +39302,11 @@
|
|
|
39229
39302
|
this.renderFinished.trigger(data.result);
|
|
39230
39303
|
break;
|
|
39231
39304
|
case 'alphaTab.postRenderFinished':
|
|
39232
|
-
|
|
39233
|
-
|
|
39305
|
+
const score = this._api.score;
|
|
39306
|
+
if (score && data.boundsLookup) {
|
|
39307
|
+
this.boundsLookup = BoundsLookup.fromJson(data.boundsLookup, this._api.score);
|
|
39308
|
+
this.boundsLookup?.finish();
|
|
39309
|
+
}
|
|
39234
39310
|
this.postRenderFinished.trigger();
|
|
39235
39311
|
break;
|
|
39236
39312
|
case 'alphaTab.error':
|
|
@@ -39487,6 +39563,164 @@
|
|
|
39487
39563
|
}
|
|
39488
39564
|
}
|
|
39489
39565
|
|
|
39566
|
+
/**
|
|
39567
|
+
* @internal
|
|
39568
|
+
*/
|
|
39569
|
+
class QueueItem {
|
|
39570
|
+
value;
|
|
39571
|
+
next;
|
|
39572
|
+
constructor(value) {
|
|
39573
|
+
this.value = value;
|
|
39574
|
+
}
|
|
39575
|
+
}
|
|
39576
|
+
/**
|
|
39577
|
+
* @internal
|
|
39578
|
+
*/
|
|
39579
|
+
class Queue {
|
|
39580
|
+
_head;
|
|
39581
|
+
_tail;
|
|
39582
|
+
get isEmpty() {
|
|
39583
|
+
return this._head === undefined;
|
|
39584
|
+
}
|
|
39585
|
+
clear() {
|
|
39586
|
+
this._head = undefined;
|
|
39587
|
+
this._tail = undefined;
|
|
39588
|
+
}
|
|
39589
|
+
enqueue(item) {
|
|
39590
|
+
const queueItem = new QueueItem(item);
|
|
39591
|
+
if (this._tail) {
|
|
39592
|
+
// not empty -> add after tail
|
|
39593
|
+
this._tail.next = queueItem;
|
|
39594
|
+
this._tail = queueItem;
|
|
39595
|
+
}
|
|
39596
|
+
else {
|
|
39597
|
+
// empty -> new item takes head and tail
|
|
39598
|
+
this._head = queueItem;
|
|
39599
|
+
this._tail = queueItem;
|
|
39600
|
+
}
|
|
39601
|
+
}
|
|
39602
|
+
enqueueFront(item) {
|
|
39603
|
+
const queueItem = new QueueItem(item);
|
|
39604
|
+
queueItem.next = this._head;
|
|
39605
|
+
if (this._head) {
|
|
39606
|
+
this._head = queueItem;
|
|
39607
|
+
}
|
|
39608
|
+
else {
|
|
39609
|
+
this._head = queueItem;
|
|
39610
|
+
this._tail = queueItem;
|
|
39611
|
+
}
|
|
39612
|
+
}
|
|
39613
|
+
peek() {
|
|
39614
|
+
const head = this._head;
|
|
39615
|
+
if (!head) {
|
|
39616
|
+
return undefined;
|
|
39617
|
+
}
|
|
39618
|
+
return head.value;
|
|
39619
|
+
}
|
|
39620
|
+
dequeue() {
|
|
39621
|
+
const head = this._head;
|
|
39622
|
+
if (!head) {
|
|
39623
|
+
return undefined;
|
|
39624
|
+
}
|
|
39625
|
+
const newHead = head.next;
|
|
39626
|
+
this._head = newHead;
|
|
39627
|
+
// last item removed?
|
|
39628
|
+
if (!newHead) {
|
|
39629
|
+
this._tail = undefined;
|
|
39630
|
+
}
|
|
39631
|
+
return head.value;
|
|
39632
|
+
}
|
|
39633
|
+
}
|
|
39634
|
+
|
|
39635
|
+
/**
|
|
39636
|
+
* The options controlling how to export the audio.
|
|
39637
|
+
* @public
|
|
39638
|
+
*/
|
|
39639
|
+
class AudioExportOptions {
|
|
39640
|
+
/**
|
|
39641
|
+
* The soundfonts to load and use for generating the audio.
|
|
39642
|
+
* If not provided, the already loaded soundfonts of the synthesizer will be used.
|
|
39643
|
+
* If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
|
|
39644
|
+
*/
|
|
39645
|
+
soundFonts;
|
|
39646
|
+
/**
|
|
39647
|
+
* The output sample rate.
|
|
39648
|
+
* @default `44100`
|
|
39649
|
+
*/
|
|
39650
|
+
sampleRate = 44100;
|
|
39651
|
+
/**
|
|
39652
|
+
* Whether to respect sync point information during export.
|
|
39653
|
+
* @default `true`
|
|
39654
|
+
* @remarks
|
|
39655
|
+
* If the song contains sync point information for synchronization with an external media,
|
|
39656
|
+
* this option allows controlling whether the synthesized audio is aligned with these points.
|
|
39657
|
+
*
|
|
39658
|
+
* This is useful when mixing the exported audio together with external media, keeping the same timing.
|
|
39659
|
+
*
|
|
39660
|
+
* Disable this option if you want the original/exact timing as per music sheet in the exported audio.
|
|
39661
|
+
*/
|
|
39662
|
+
useSyncPoints = false;
|
|
39663
|
+
/**
|
|
39664
|
+
* The current master volume as percentage. (range: 0.0-3.0, default 1.0)
|
|
39665
|
+
*/
|
|
39666
|
+
masterVolume = 1;
|
|
39667
|
+
/**
|
|
39668
|
+
* The metronome volume. (range: 0.0-3.0, default 0.0)
|
|
39669
|
+
*/
|
|
39670
|
+
metronomeVolume = 0;
|
|
39671
|
+
/**
|
|
39672
|
+
* The range of the song that should be exported. Set this to null
|
|
39673
|
+
* to play the whole song.
|
|
39674
|
+
*/
|
|
39675
|
+
playbackRange;
|
|
39676
|
+
/**
|
|
39677
|
+
* The volume for individual tracks as percentage (range: 0.0-3.0).
|
|
39678
|
+
* @remarks
|
|
39679
|
+
* The key is the track index, and the value is the relative volume.
|
|
39680
|
+
* The configured volume (as per data model) still applies, this is an additional volume control.
|
|
39681
|
+
* If no custom value is set, 100% is used.
|
|
39682
|
+
* No values from the currently active synthesizer are applied.
|
|
39683
|
+
*
|
|
39684
|
+
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
39685
|
+
*/
|
|
39686
|
+
trackVolume = new Map();
|
|
39687
|
+
/**
|
|
39688
|
+
* The additional semitone pitch transpose to apply for individual tracks.
|
|
39689
|
+
* @remarks
|
|
39690
|
+
* The key is the track index, and the value is the number of semitones to apply.
|
|
39691
|
+
* No values from the currently active synthesizer are applied.
|
|
39692
|
+
*
|
|
39693
|
+
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
39694
|
+
*/
|
|
39695
|
+
trackTranspositionPitches = new Map();
|
|
39696
|
+
}
|
|
39697
|
+
/**
|
|
39698
|
+
* Represents a single chunk of audio produced.
|
|
39699
|
+
* @public
|
|
39700
|
+
*/
|
|
39701
|
+
class AudioExportChunk {
|
|
39702
|
+
/**
|
|
39703
|
+
* The generated samples for the requested chunk.
|
|
39704
|
+
*/
|
|
39705
|
+
samples;
|
|
39706
|
+
/**
|
|
39707
|
+
* The current time position within the song in milliseconds.
|
|
39708
|
+
*/
|
|
39709
|
+
currentTime = 0;
|
|
39710
|
+
/**
|
|
39711
|
+
* The total length of the song in milliseconds.
|
|
39712
|
+
*/
|
|
39713
|
+
endTime = 0;
|
|
39714
|
+
/**
|
|
39715
|
+
* The current time position within the song in midi ticks.
|
|
39716
|
+
*/
|
|
39717
|
+
currentTick = 0;
|
|
39718
|
+
/**
|
|
39719
|
+
* The total length of the song in midi ticks.
|
|
39720
|
+
*/
|
|
39721
|
+
endTick = 0;
|
|
39722
|
+
}
|
|
39723
|
+
|
|
39490
39724
|
// The SoundFont loading and Audio Synthesis is based on TinySoundFont, licensed under MIT,
|
|
39491
39725
|
// developed by Bernhard Schelling (https://github.com/schellingb/TinySoundFont)
|
|
39492
39726
|
// TypeScript port for alphaTab: (C) 2020 by Daniel Kuschny
|
|
@@ -39600,6 +39834,7 @@
|
|
|
39600
39834
|
endTime = 0;
|
|
39601
39835
|
currentTempo = 0;
|
|
39602
39836
|
syncPointTempo = 0;
|
|
39837
|
+
metronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
39603
39838
|
}
|
|
39604
39839
|
/**
|
|
39605
39840
|
* This sequencer dispatches midi events to the synthesizer based on the current
|
|
@@ -39612,6 +39847,9 @@
|
|
|
39612
39847
|
_mainState;
|
|
39613
39848
|
_oneTimeState = null;
|
|
39614
39849
|
_countInState = null;
|
|
39850
|
+
get metronomeChannel() {
|
|
39851
|
+
return this._mainState.metronomeChannel;
|
|
39852
|
+
}
|
|
39615
39853
|
get isPlayingMain() {
|
|
39616
39854
|
return this._currentState === this._mainState;
|
|
39617
39855
|
}
|
|
@@ -39695,7 +39933,7 @@
|
|
|
39695
39933
|
const metronomeVolume = this._synthesizer.metronomeVolume;
|
|
39696
39934
|
this._synthesizer.noteOffAll(true);
|
|
39697
39935
|
this._synthesizer.resetSoft();
|
|
39698
|
-
this._synthesizer.setupMetronomeChannel(metronomeVolume);
|
|
39936
|
+
this._synthesizer.setupMetronomeChannel(this.metronomeChannel, metronomeVolume);
|
|
39699
39937
|
}
|
|
39700
39938
|
this._mainSilentProcess(timePosition);
|
|
39701
39939
|
}
|
|
@@ -39747,6 +39985,7 @@
|
|
|
39747
39985
|
let metronomeLengthInMillis = 0;
|
|
39748
39986
|
let metronomeTick = midiFile.tickShift; // shift metronome to content
|
|
39749
39987
|
let metronomeTime = 0.0;
|
|
39988
|
+
let maxChannel = 0;
|
|
39750
39989
|
let previousTick = 0;
|
|
39751
39990
|
for (const mEvent of midiFile.events) {
|
|
39752
39991
|
const synthData = new SynthEvent(state.synthData.length, mEvent);
|
|
@@ -39788,6 +40027,9 @@
|
|
|
39788
40027
|
if (!state.firstProgramEventPerChannel.has(channel)) {
|
|
39789
40028
|
state.firstProgramEventPerChannel.set(channel, synthData);
|
|
39790
40029
|
}
|
|
40030
|
+
if (channel > maxChannel) {
|
|
40031
|
+
maxChannel = channel;
|
|
40032
|
+
}
|
|
39791
40033
|
const isPercussion = channel === SynthConstants.PercussionChannel;
|
|
39792
40034
|
if (!isPercussion) {
|
|
39793
40035
|
this.instrumentPrograms.add(programChange.program);
|
|
@@ -39799,6 +40041,9 @@
|
|
|
39799
40041
|
if (isPercussion) {
|
|
39800
40042
|
this.percussionKeys.add(noteOn.noteKey);
|
|
39801
40043
|
}
|
|
40044
|
+
if (noteOn.channel > maxChannel) {
|
|
40045
|
+
maxChannel = noteOn.channel;
|
|
40046
|
+
}
|
|
39802
40047
|
}
|
|
39803
40048
|
}
|
|
39804
40049
|
state.currentTempo = state.tempoChanges.length > 0 ? state.tempoChanges[0].bpm : bpm;
|
|
@@ -39814,6 +40059,7 @@
|
|
|
39814
40059
|
});
|
|
39815
40060
|
state.endTime = absTime;
|
|
39816
40061
|
state.endTick = absTick;
|
|
40062
|
+
state.metronomeChannel = maxChannel + 1;
|
|
39817
40063
|
return state;
|
|
39818
40064
|
}
|
|
39819
40065
|
fillMidiEventQueue() {
|
|
@@ -44019,6 +44265,11 @@
|
|
|
44019
44265
|
if (dynamicGain) {
|
|
44020
44266
|
noteGain = SynthHelper.decibelsToGain(this.noteGainDb + this.modLfo.level * tmpModLfoToVolume);
|
|
44021
44267
|
}
|
|
44268
|
+
// Update EG.
|
|
44269
|
+
this.ampEnv.process(blockSamples, f.outSampleRate);
|
|
44270
|
+
if (updateModEnv) {
|
|
44271
|
+
this.modEnv.process(blockSamples, f.outSampleRate);
|
|
44272
|
+
}
|
|
44022
44273
|
gainMono = noteGain * this.ampEnv.level;
|
|
44023
44274
|
if (isMuted) {
|
|
44024
44275
|
gainMono = 0;
|
|
@@ -44026,11 +44277,6 @@
|
|
|
44026
44277
|
else {
|
|
44027
44278
|
gainMono *= this.mixVolume;
|
|
44028
44279
|
}
|
|
44029
|
-
// Update EG.
|
|
44030
|
-
this.ampEnv.process(blockSamples, f.outSampleRate);
|
|
44031
|
-
if (updateModEnv) {
|
|
44032
|
-
this.modEnv.process(blockSamples, f.outSampleRate);
|
|
44033
|
-
}
|
|
44034
44280
|
// Update LFOs.
|
|
44035
44281
|
if (updateModLFO) {
|
|
44036
44282
|
this.modLfo.process(blockSamples);
|
|
@@ -44109,7 +44355,12 @@
|
|
|
44109
44355
|
}
|
|
44110
44356
|
break;
|
|
44111
44357
|
}
|
|
44112
|
-
|
|
44358
|
+
const inaudible = this.ampEnv.segment === VoiceEnvelopeSegment.Release &&
|
|
44359
|
+
Math.abs(gainMono) < SynthConstants.AudibleLevelThreshold;
|
|
44360
|
+
if (tmpSourceSamplePosition >= tmpSampleEndDbl ||
|
|
44361
|
+
this.ampEnv.segment === VoiceEnvelopeSegment.Done ||
|
|
44362
|
+
// Check if voice is inaudible during release to terminate early
|
|
44363
|
+
inaudible) {
|
|
44113
44364
|
this.kill();
|
|
44114
44365
|
return;
|
|
44115
44366
|
}
|
|
@@ -44124,75 +44375,6 @@
|
|
|
44124
44375
|
}
|
|
44125
44376
|
}
|
|
44126
44377
|
|
|
44127
|
-
/**
|
|
44128
|
-
* @internal
|
|
44129
|
-
*/
|
|
44130
|
-
class QueueItem {
|
|
44131
|
-
value;
|
|
44132
|
-
next;
|
|
44133
|
-
constructor(value) {
|
|
44134
|
-
this.value = value;
|
|
44135
|
-
}
|
|
44136
|
-
}
|
|
44137
|
-
/**
|
|
44138
|
-
* @internal
|
|
44139
|
-
*/
|
|
44140
|
-
class Queue {
|
|
44141
|
-
_head;
|
|
44142
|
-
_tail;
|
|
44143
|
-
get isEmpty() {
|
|
44144
|
-
return this._head === undefined;
|
|
44145
|
-
}
|
|
44146
|
-
clear() {
|
|
44147
|
-
this._head = undefined;
|
|
44148
|
-
this._tail = undefined;
|
|
44149
|
-
}
|
|
44150
|
-
enqueue(item) {
|
|
44151
|
-
const queueItem = new QueueItem(item);
|
|
44152
|
-
if (this._tail) {
|
|
44153
|
-
// not empty -> add after tail
|
|
44154
|
-
this._tail.next = queueItem;
|
|
44155
|
-
this._tail = queueItem;
|
|
44156
|
-
}
|
|
44157
|
-
else {
|
|
44158
|
-
// empty -> new item takes head and tail
|
|
44159
|
-
this._head = queueItem;
|
|
44160
|
-
this._tail = queueItem;
|
|
44161
|
-
}
|
|
44162
|
-
}
|
|
44163
|
-
enqueueFront(item) {
|
|
44164
|
-
const queueItem = new QueueItem(item);
|
|
44165
|
-
queueItem.next = this._head;
|
|
44166
|
-
if (this._head) {
|
|
44167
|
-
this._head = queueItem;
|
|
44168
|
-
}
|
|
44169
|
-
else {
|
|
44170
|
-
this._head = queueItem;
|
|
44171
|
-
this._tail = queueItem;
|
|
44172
|
-
}
|
|
44173
|
-
}
|
|
44174
|
-
peek() {
|
|
44175
|
-
const head = this._head;
|
|
44176
|
-
if (!head) {
|
|
44177
|
-
return undefined;
|
|
44178
|
-
}
|
|
44179
|
-
return head.value;
|
|
44180
|
-
}
|
|
44181
|
-
dequeue() {
|
|
44182
|
-
const head = this._head;
|
|
44183
|
-
if (!head) {
|
|
44184
|
-
return undefined;
|
|
44185
|
-
}
|
|
44186
|
-
const newHead = head.next;
|
|
44187
|
-
this._head = newHead;
|
|
44188
|
-
// last item removed?
|
|
44189
|
-
if (!newHead) {
|
|
44190
|
-
this._tail = undefined;
|
|
44191
|
-
}
|
|
44192
|
-
return head.value;
|
|
44193
|
-
}
|
|
44194
|
-
}
|
|
44195
|
-
|
|
44196
44378
|
/**
|
|
44197
44379
|
* Lists all midi controllers.
|
|
44198
44380
|
* @public
|
|
@@ -44349,6 +44531,7 @@
|
|
|
44349
44531
|
currentTempo = 0;
|
|
44350
44532
|
timeSignatureNumerator = 0;
|
|
44351
44533
|
timeSignatureDenominator = 0;
|
|
44534
|
+
_metronomeChannel = SynthConstants.DefaultChannelCount - 1;
|
|
44352
44535
|
constructor(sampleRate) {
|
|
44353
44536
|
this.outSampleRate = sampleRate;
|
|
44354
44537
|
}
|
|
@@ -44453,8 +44636,8 @@
|
|
|
44453
44636
|
while (!this._midiEventQueue.isEmpty) {
|
|
44454
44637
|
const m = this._midiEventQueue.dequeue();
|
|
44455
44638
|
if (m.isMetronome && this.metronomeVolume > 0) {
|
|
44456
|
-
this.channelNoteOff(
|
|
44457
|
-
this.channelNoteOn(
|
|
44639
|
+
this.channelNoteOff(this._metronomeChannel, SynthConstants.MetronomeKey);
|
|
44640
|
+
this.channelNoteOn(this._metronomeChannel, SynthConstants.MetronomeKey, 95 / 127);
|
|
44458
44641
|
}
|
|
44459
44642
|
else if (m.event) {
|
|
44460
44643
|
this.processMidiMessage(m.event);
|
|
@@ -44468,7 +44651,7 @@
|
|
|
44468
44651
|
// channel is muted if it is either explicitley muted, or another channel is set to solo but not this one.
|
|
44469
44652
|
// exception. metronome is implicitly added in solo
|
|
44470
44653
|
const isChannelMuted = this._mutedChannels.has(channel) ||
|
|
44471
|
-
(anySolo && channel !==
|
|
44654
|
+
(anySolo && channel !== this._metronomeChannel && !this._soloChannels.has(channel));
|
|
44472
44655
|
if (!buffer) {
|
|
44473
44656
|
voice.kill();
|
|
44474
44657
|
}
|
|
@@ -44522,16 +44705,17 @@
|
|
|
44522
44705
|
}
|
|
44523
44706
|
}
|
|
44524
44707
|
get metronomeVolume() {
|
|
44525
|
-
return this.channelGetMixVolume(
|
|
44708
|
+
return this.channelGetMixVolume(this._metronomeChannel);
|
|
44526
44709
|
}
|
|
44527
44710
|
set metronomeVolume(value) {
|
|
44528
|
-
this.setupMetronomeChannel(value);
|
|
44711
|
+
this.setupMetronomeChannel(this._metronomeChannel, value);
|
|
44529
44712
|
}
|
|
44530
|
-
setupMetronomeChannel(volume) {
|
|
44531
|
-
this.
|
|
44713
|
+
setupMetronomeChannel(channel, volume) {
|
|
44714
|
+
this._metronomeChannel = channel;
|
|
44715
|
+
this.channelSetMixVolume(channel, volume);
|
|
44532
44716
|
if (volume > 0) {
|
|
44533
|
-
this.channelSetVolume(
|
|
44534
|
-
this.channelSetPresetNumber(
|
|
44717
|
+
this.channelSetVolume(channel, 1);
|
|
44718
|
+
this.channelSetPresetNumber(channel, 0, true);
|
|
44535
44719
|
}
|
|
44536
44720
|
}
|
|
44537
44721
|
get masterVolume() {
|
|
@@ -45611,95 +45795,6 @@
|
|
|
45611
45795
|
}
|
|
45612
45796
|
}
|
|
45613
45797
|
|
|
45614
|
-
/**
|
|
45615
|
-
* The options controlling how to export the audio.
|
|
45616
|
-
* @public
|
|
45617
|
-
*/
|
|
45618
|
-
class AudioExportOptions {
|
|
45619
|
-
/**
|
|
45620
|
-
* The soundfonts to load and use for generating the audio.
|
|
45621
|
-
* If not provided, the already loaded soundfonts of the synthesizer will be used.
|
|
45622
|
-
* If no existing synthesizer is initialized, the generated audio might not contain any hearable audio.
|
|
45623
|
-
*/
|
|
45624
|
-
soundFonts;
|
|
45625
|
-
/**
|
|
45626
|
-
* The output sample rate.
|
|
45627
|
-
* @default `44100`
|
|
45628
|
-
*/
|
|
45629
|
-
sampleRate = 44100;
|
|
45630
|
-
/**
|
|
45631
|
-
* Whether to respect sync point information during export.
|
|
45632
|
-
* @default `true`
|
|
45633
|
-
* @remarks
|
|
45634
|
-
* If the song contains sync point information for synchronization with an external media,
|
|
45635
|
-
* this option allows controlling whether the synthesized audio is aligned with these points.
|
|
45636
|
-
*
|
|
45637
|
-
* This is useful when mixing the exported audio together with external media, keeping the same timing.
|
|
45638
|
-
*
|
|
45639
|
-
* Disable this option if you want the original/exact timing as per music sheet in the exported audio.
|
|
45640
|
-
*/
|
|
45641
|
-
useSyncPoints = false;
|
|
45642
|
-
/**
|
|
45643
|
-
* The current master volume as percentage. (range: 0.0-3.0, default 1.0)
|
|
45644
|
-
*/
|
|
45645
|
-
masterVolume = 1;
|
|
45646
|
-
/**
|
|
45647
|
-
* The metronome volume. (range: 0.0-3.0, default 0.0)
|
|
45648
|
-
*/
|
|
45649
|
-
metronomeVolume = 0;
|
|
45650
|
-
/**
|
|
45651
|
-
* The range of the song that should be exported. Set this to null
|
|
45652
|
-
* to play the whole song.
|
|
45653
|
-
*/
|
|
45654
|
-
playbackRange;
|
|
45655
|
-
/**
|
|
45656
|
-
* The volume for individual tracks as percentage (range: 0.0-3.0).
|
|
45657
|
-
* @remarks
|
|
45658
|
-
* The key is the track index, and the value is the relative volume.
|
|
45659
|
-
* The configured volume (as per data model) still applies, this is an additional volume control.
|
|
45660
|
-
* If no custom value is set, 100% is used.
|
|
45661
|
-
* No values from the currently active synthesizer are applied.
|
|
45662
|
-
*
|
|
45663
|
-
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
45664
|
-
*/
|
|
45665
|
-
trackVolume = new Map();
|
|
45666
|
-
/**
|
|
45667
|
-
* The additional semitone pitch transpose to apply for individual tracks.
|
|
45668
|
-
* @remarks
|
|
45669
|
-
* The key is the track index, and the value is the number of semitones to apply.
|
|
45670
|
-
* No values from the currently active synthesizer are applied.
|
|
45671
|
-
*
|
|
45672
|
-
* The meaning of the key changes when used with AlphaSynth directly, in this case the key is the midi channel .
|
|
45673
|
-
*/
|
|
45674
|
-
trackTranspositionPitches = new Map();
|
|
45675
|
-
}
|
|
45676
|
-
/**
|
|
45677
|
-
* Represents a single chunk of audio produced.
|
|
45678
|
-
* @public
|
|
45679
|
-
*/
|
|
45680
|
-
class AudioExportChunk {
|
|
45681
|
-
/**
|
|
45682
|
-
* The generated samples for the requested chunk.
|
|
45683
|
-
*/
|
|
45684
|
-
samples;
|
|
45685
|
-
/**
|
|
45686
|
-
* The current time position within the song in milliseconds.
|
|
45687
|
-
*/
|
|
45688
|
-
currentTime = 0;
|
|
45689
|
-
/**
|
|
45690
|
-
* The total length of the song in milliseconds.
|
|
45691
|
-
*/
|
|
45692
|
-
endTime = 0;
|
|
45693
|
-
/**
|
|
45694
|
-
* The current time position within the song in midi ticks.
|
|
45695
|
-
*/
|
|
45696
|
-
currentTick = 0;
|
|
45697
|
-
/**
|
|
45698
|
-
* The total length of the song in midi ticks.
|
|
45699
|
-
*/
|
|
45700
|
-
endTick = 0;
|
|
45701
|
-
}
|
|
45702
|
-
|
|
45703
45798
|
/**
|
|
45704
45799
|
* This is the base class for synthesizer components which can be used to
|
|
45705
45800
|
* play a {@link MidiFile} via a {@link ISynthOutput}.
|
|
@@ -45908,6 +46003,17 @@
|
|
|
45908
46003
|
}
|
|
45909
46004
|
this._notPlayedSamples += samples.length;
|
|
45910
46005
|
this.output.addSamples(samples);
|
|
46006
|
+
// if the sequencer finished, we instantly force a noteOff on all
|
|
46007
|
+
// voices to complete playback and stop voices fast.
|
|
46008
|
+
// Doing this in the samplePlayed callback is too late as we might
|
|
46009
|
+
// continue generating audio for long-release notes (especially percussion like cymbals)
|
|
46010
|
+
// we still have checkForFinish which takes care of the counterpart
|
|
46011
|
+
// on the sample played area to ensure we seek back.
|
|
46012
|
+
// but thanks to this code we ensure the output will complete fast as we won't
|
|
46013
|
+
// be adding more samples beside a 0.1s ramp-down
|
|
46014
|
+
if (this.sequencer.isFinished) {
|
|
46015
|
+
this.synthesizer.noteOffAll(true);
|
|
46016
|
+
}
|
|
45911
46017
|
}
|
|
45912
46018
|
else {
|
|
45913
46019
|
// Tell output that there is no data left for it.
|
|
@@ -45924,7 +46030,7 @@
|
|
|
45924
46030
|
if (this._countInVolume > 0) {
|
|
45925
46031
|
Logger.debug('AlphaSynth', 'Starting countin');
|
|
45926
46032
|
this.sequencer.startCountIn();
|
|
45927
|
-
this.synthesizer.setupMetronomeChannel(this._countInVolume);
|
|
46033
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this._countInVolume);
|
|
45928
46034
|
this.updateTimePosition(0, true);
|
|
45929
46035
|
}
|
|
45930
46036
|
this.output.play();
|
|
@@ -45936,7 +46042,7 @@
|
|
|
45936
46042
|
this._stopOneTimeMidi();
|
|
45937
46043
|
}
|
|
45938
46044
|
Logger.debug('AlphaSynth', 'Starting playback');
|
|
45939
|
-
this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
|
|
46045
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
|
|
45940
46046
|
this._synthStopping = false;
|
|
45941
46047
|
this.state = PlayerState.Playing;
|
|
45942
46048
|
this.stateChanged.trigger(new PlayerStateChangedEventArgs(this.state, false));
|
|
@@ -46020,7 +46126,7 @@
|
|
|
46020
46126
|
}
|
|
46021
46127
|
_checkReadyForPlayback() {
|
|
46022
46128
|
if (this.isReadyForPlayback) {
|
|
46023
|
-
this.synthesizer.setupMetronomeChannel(this.metronomeVolume);
|
|
46129
|
+
this.synthesizer.setupMetronomeChannel(this.sequencer.metronomeChannel, this.metronomeVolume);
|
|
46024
46130
|
const programs = this.sequencer.instrumentPrograms;
|
|
46025
46131
|
const percussionKeys = this.sequencer.percussionKeys;
|
|
46026
46132
|
let append = false;
|
|
@@ -46361,7 +46467,7 @@
|
|
|
46361
46467
|
_generatedAudioCurrentTime = 0;
|
|
46362
46468
|
_generatedAudioEndTime = 0;
|
|
46363
46469
|
setup() {
|
|
46364
|
-
this._synth.setupMetronomeChannel(this._synth.metronomeVolume);
|
|
46470
|
+
this._synth.setupMetronomeChannel(this._sequencer.metronomeChannel, this._synth.metronomeVolume);
|
|
46365
46471
|
const syncPoints = this._sequencer.currentSyncPoints;
|
|
46366
46472
|
const alphaTabEndTime = this._sequencer.currentEndTime;
|
|
46367
46473
|
if (syncPoints.length === 0) {
|
|
@@ -46443,7 +46549,7 @@
|
|
|
46443
46549
|
}
|
|
46444
46550
|
loadPresets(_hydra, _instrumentPrograms, _percussionKeys, _append) {
|
|
46445
46551
|
}
|
|
46446
|
-
setupMetronomeChannel(_metronomeVolume) {
|
|
46552
|
+
setupMetronomeChannel(_metronomeChannel, _metronomeVolume) {
|
|
46447
46553
|
}
|
|
46448
46554
|
synthesizeSilent(_sampleCount) {
|
|
46449
46555
|
this.fakeSynthesize();
|
|
@@ -46810,7 +46916,15 @@
|
|
|
46810
46916
|
Logger.warning('Rendering', 'AlphaTab skipped rendering because of width=0 (element invisible)', null);
|
|
46811
46917
|
return;
|
|
46812
46918
|
}
|
|
46813
|
-
|
|
46919
|
+
// For partial renders we preserve the existing lookup so bars outside the re-layouted
|
|
46920
|
+
// range keep their already-scaled bounds - the layout will clear the changed range
|
|
46921
|
+
// before the paint pass re-registers fresh entries for it.
|
|
46922
|
+
if (renderHints?.firstChangedMasterBar !== undefined && this.boundsLookup) {
|
|
46923
|
+
this.boundsLookup.resetForPartialUpdate();
|
|
46924
|
+
}
|
|
46925
|
+
else {
|
|
46926
|
+
this.boundsLookup = new BoundsLookup();
|
|
46927
|
+
}
|
|
46814
46928
|
this._recreateCanvas();
|
|
46815
46929
|
this.canvas.lineWidth = 1;
|
|
46816
46930
|
this.canvas.settings = this.settings;
|
|
@@ -47877,6 +47991,7 @@
|
|
|
47877
47991
|
_bufferTimeInMilliseconds = 0;
|
|
47878
47992
|
_settings;
|
|
47879
47993
|
_boundHandleMessage;
|
|
47994
|
+
_pendingEvents;
|
|
47880
47995
|
constructor(settings) {
|
|
47881
47996
|
super();
|
|
47882
47997
|
this._settings = settings;
|
|
@@ -47904,6 +48019,13 @@
|
|
|
47904
48019
|
this.source.connect(this._worklet);
|
|
47905
48020
|
this.source.start(0);
|
|
47906
48021
|
this._worklet.connect(ctx.destination);
|
|
48022
|
+
const pending = this._pendingEvents;
|
|
48023
|
+
if (pending) {
|
|
48024
|
+
for (const e of pending) {
|
|
48025
|
+
this._worklet.port.postMessage(e);
|
|
48026
|
+
}
|
|
48027
|
+
this._pendingEvents = undefined;
|
|
48028
|
+
}
|
|
47907
48029
|
}, (reason) => {
|
|
47908
48030
|
Logger.error('WebAudio', `Audio Worklet creation failed: reason=${reason}`);
|
|
47909
48031
|
});
|
|
@@ -47930,15 +48052,26 @@
|
|
|
47930
48052
|
this._worklet.disconnect();
|
|
47931
48053
|
}
|
|
47932
48054
|
this._worklet = null;
|
|
48055
|
+
this._pendingEvents = undefined;
|
|
48056
|
+
}
|
|
48057
|
+
_postWorkerMessage(message) {
|
|
48058
|
+
const worklet = this._worklet;
|
|
48059
|
+
if (worklet) {
|
|
48060
|
+
worklet.port.postMessage(message);
|
|
48061
|
+
}
|
|
48062
|
+
else {
|
|
48063
|
+
this._pendingEvents ??= [];
|
|
48064
|
+
this._pendingEvents.push(message);
|
|
48065
|
+
}
|
|
47933
48066
|
}
|
|
47934
48067
|
addSamples(f) {
|
|
47935
|
-
this.
|
|
48068
|
+
this._postWorkerMessage({
|
|
47936
48069
|
cmd: 'alphaSynth.output.addSamples',
|
|
47937
48070
|
samples: Environment.prepareForPostMessage(f)
|
|
47938
48071
|
});
|
|
47939
48072
|
}
|
|
47940
48073
|
resetSamples() {
|
|
47941
|
-
this.
|
|
48074
|
+
this._postWorkerMessage({
|
|
47942
48075
|
cmd: 'alphaSynth.output.resetSamples'
|
|
47943
48076
|
});
|
|
47944
48077
|
}
|
|
@@ -48665,9 +48798,6 @@
|
|
|
48665
48798
|
* @param beat The beat to add.
|
|
48666
48799
|
*/
|
|
48667
48800
|
highlightBeat(beat, playbackStart) {
|
|
48668
|
-
if (beat.isEmpty && !beat.voice.isEmpty) {
|
|
48669
|
-
return;
|
|
48670
|
-
}
|
|
48671
48801
|
if (!this._highlightedBeats.has(beat.id)) {
|
|
48672
48802
|
this._highlightedBeats.set(beat.id, true);
|
|
48673
48803
|
this.highlightedBeats.push(new BeatTickLookupItem(beat, playbackStart));
|
|
@@ -50135,7 +50265,13 @@
|
|
|
50135
50265
|
let beatStart = beat.playbackStart;
|
|
50136
50266
|
let audioDuration = beat.playbackDuration;
|
|
50137
50267
|
const masterBarDuration = beat.voice.bar.masterBar.calculateDuration();
|
|
50138
|
-
|
|
50268
|
+
// For a bar whose voice contains a single empty beat (the typical "whole-bar rest"
|
|
50269
|
+
// placeholder inserted during score.finish), extend the beat's audio duration to cover
|
|
50270
|
+
// the full bar so cursor navigation has a beat to follow across the whole bar. Don't
|
|
50271
|
+
// apply this when the voice has multiple beats: those represent explicit rhythmic
|
|
50272
|
+
// subdivisions even when each beat is empty (e.g. a recording grid of placeholder
|
|
50273
|
+
// slots), and overriding would make every beat overlap the whole bar.
|
|
50274
|
+
if (beat.voice.bar.isEmpty && beat.voice.beats.length === 1) {
|
|
50139
50275
|
audioDuration = masterBarDuration;
|
|
50140
50276
|
}
|
|
50141
50277
|
else if (beat.voice.bar.masterBar.tripletFeel !== TripletFeel.NoTripletFeel &&
|
|
@@ -54756,7 +54892,8 @@
|
|
|
54756
54892
|
this._isInitialBeatCursorUpdate ||
|
|
54757
54893
|
barBounds.y !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.y ||
|
|
54758
54894
|
startBeatX < previousBeatBounds.onNotesX ||
|
|
54759
|
-
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1
|
|
54895
|
+
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1 ||
|
|
54896
|
+
barBounds.h !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.h;
|
|
54760
54897
|
if (jumpCursor) {
|
|
54761
54898
|
cursorHandler.placeBeatCursor(beatCursor, beatBoundings, startBeatX);
|
|
54762
54899
|
}
|
|
@@ -55811,6 +55948,9 @@
|
|
|
55811
55948
|
this._beatVisibilityChecker.bounds = this.boundsLookup;
|
|
55812
55949
|
this._currentBeat = null;
|
|
55813
55950
|
this._cursorUpdateTick(this._previousTick, false, 1, true, true);
|
|
55951
|
+
if (this._selectionStart) {
|
|
55952
|
+
this.highlightPlaybackRange(this._selectionStart.beat, this._selectionEnd.beat);
|
|
55953
|
+
}
|
|
55814
55954
|
this.postRenderFinished.trigger();
|
|
55815
55955
|
this.uiFacade.triggerEvent(this.container, 'postRenderFinished', null);
|
|
55816
55956
|
}
|
|
@@ -57993,8 +58133,9 @@
|
|
|
57993
58133
|
break;
|
|
57994
58134
|
case 'alphaTab.renderScore':
|
|
57995
58135
|
this._updateFontSizes(data.fontSizes);
|
|
58136
|
+
const renderHints = data.renderHints;
|
|
57996
58137
|
const score = data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
|
|
57997
|
-
this._renderMultiple(score, data.trackIndexes);
|
|
58138
|
+
this._renderMultiple(score, data.trackIndexes, renderHints);
|
|
57998
58139
|
break;
|
|
57999
58140
|
case 'alphaTab.updateSettings':
|
|
58000
58141
|
this._updateSettings(data.settings);
|
|
@@ -62867,6 +63008,15 @@
|
|
|
62867
63008
|
}
|
|
62868
63009
|
return false;
|
|
62869
63010
|
}
|
|
63011
|
+
/**
|
|
63012
|
+
* The fixed-overhead width of this renderer: glyphs that do not stretch when
|
|
63013
|
+
* the bar is scaled (clef, key signature, time signature, barlines, courtesy
|
|
63014
|
+
* accidentals, etc). Treated as a fixed allocation by the system-level layout
|
|
63015
|
+
* before distributing remaining width across bars by {@link Bar.displayScale}.
|
|
63016
|
+
*/
|
|
63017
|
+
get fixedOverhead() {
|
|
63018
|
+
return this._preBeatGlyphs.width + this._postBeatGlyphs.width;
|
|
63019
|
+
}
|
|
62870
63020
|
scaleToWidth(width) {
|
|
62871
63021
|
// preBeat and postBeat glyphs do not get resized
|
|
62872
63022
|
const containerWidth = width - this._preBeatGlyphs.width - this._postBeatGlyphs.width;
|
|
@@ -65425,6 +65575,22 @@
|
|
|
65425
65575
|
postBeatSize = 0;
|
|
65426
65576
|
minStretchForce = 0;
|
|
65427
65577
|
totalSpringConstant = 0;
|
|
65578
|
+
/**
|
|
65579
|
+
* The smallest note duration encountered within this bar's springs, used as the reference in
|
|
65580
|
+
* the Gourlay stretch formula. Read by the owning {@link StaffSystem} so that the system can
|
|
65581
|
+
* aggregate a shared minimum across all bars and trigger a reconcile if an added bar introduces
|
|
65582
|
+
* a shorter duration than previously seen.
|
|
65583
|
+
*/
|
|
65584
|
+
get localMinDuration() {
|
|
65585
|
+
return this._minDuration;
|
|
65586
|
+
}
|
|
65587
|
+
/**
|
|
65588
|
+
* The minimum-duration reference against which the spring constants currently held by this info
|
|
65589
|
+
* were computed. Set by {@link finish} and {@link recomputeSpringConstants}. The owning
|
|
65590
|
+
* StaffSystem compares this against its system-wide minimum to decide whether spring constants
|
|
65591
|
+
* need re-derivation.
|
|
65592
|
+
*/
|
|
65593
|
+
computedWithMinDuration = 0;
|
|
65428
65594
|
_updateMinStretchForce(force) {
|
|
65429
65595
|
if (this.minStretchForce < force) {
|
|
65430
65596
|
this.minStretchForce = force;
|
|
@@ -65594,10 +65760,26 @@
|
|
|
65594
65760
|
this._incompleteGraceRodsWidth += sp.preBeatWidth + sp.postSpringWidth;
|
|
65595
65761
|
}
|
|
65596
65762
|
}
|
|
65597
|
-
this._calculateSpringConstants();
|
|
65763
|
+
this._calculateSpringConstants(this._minDuration);
|
|
65764
|
+
this.computedWithMinDuration = this._minDuration;
|
|
65598
65765
|
this.version++;
|
|
65599
65766
|
}
|
|
65600
|
-
|
|
65767
|
+
/**
|
|
65768
|
+
* Re-derives the spring constants (and {@link minStretchForce} / {@link totalSpringConstant})
|
|
65769
|
+
* using a caller-supplied minimum-duration reference rather than this bar's local minimum.
|
|
65770
|
+
*
|
|
65771
|
+
* Called by {@link StaffSystem.reconcileMinDurationIfDirty} when a bar added later to the
|
|
65772
|
+
* system introduced a shorter note than previously seen, invalidating this bar's spring
|
|
65773
|
+
* constants. Grace-rod data is not recomputed — it is independent of the minimum-duration
|
|
65774
|
+
* reference. The internal {@link version} is bumped so downstream consumers (e.g.
|
|
65775
|
+
* {@link BarRendererBase.applyLayoutingInfo}) pick up the refreshed positions.
|
|
65776
|
+
*/
|
|
65777
|
+
recomputeSpringConstants(minDuration) {
|
|
65778
|
+
this._calculateSpringConstants(minDuration);
|
|
65779
|
+
this.computedWithMinDuration = minDuration;
|
|
65780
|
+
this.version++;
|
|
65781
|
+
}
|
|
65782
|
+
_calculateSpringConstants(minDuration) {
|
|
65601
65783
|
let totalSpringConstant = 0;
|
|
65602
65784
|
const sortedSprings = this._timeSortedSprings;
|
|
65603
65785
|
if (sortedSprings.length === 0) {
|
|
@@ -65615,7 +65797,7 @@
|
|
|
65615
65797
|
const nextSpring = sortedSprings[i + 1];
|
|
65616
65798
|
duration = Math.abs(nextSpring.timePosition - currentSpring.timePosition);
|
|
65617
65799
|
}
|
|
65618
|
-
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration);
|
|
65800
|
+
currentSpring.springConstant = this._calculateSpringConstant(currentSpring, duration, minDuration);
|
|
65619
65801
|
totalSpringConstant += 1 / currentSpring.springConstant;
|
|
65620
65802
|
}
|
|
65621
65803
|
this.totalSpringConstant = 1 / totalSpringConstant;
|
|
@@ -65675,7 +65857,7 @@
|
|
|
65675
65857
|
// springX += this.calculateWidth(force, spring.springConstant);
|
|
65676
65858
|
// }
|
|
65677
65859
|
// }
|
|
65678
|
-
_calculateSpringConstant(spring, duration) {
|
|
65860
|
+
_calculateSpringConstant(spring, duration, minDuration) {
|
|
65679
65861
|
if (duration <= 0) {
|
|
65680
65862
|
duration = MidiUtils.toTicks(Duration.TwoHundredFiftySixth);
|
|
65681
65863
|
}
|
|
@@ -65683,7 +65865,6 @@
|
|
|
65683
65865
|
spring.smallestDuration = duration;
|
|
65684
65866
|
}
|
|
65685
65867
|
const smallestDuration = spring.smallestDuration;
|
|
65686
|
-
const minDuration = this._minDuration;
|
|
65687
65868
|
const minDurationWidth = BarLayoutingInfo._defaultMinDurationWidth;
|
|
65688
65869
|
const phi = 1 + 0.85 * Math.log2(duration / minDuration);
|
|
65689
65870
|
return (smallestDuration / duration) * (1 / (phi * minDurationWidth));
|
|
@@ -65746,6 +65927,18 @@
|
|
|
65746
65927
|
canWrap = true;
|
|
65747
65928
|
masterBar;
|
|
65748
65929
|
additionalMultiBarRestIndexes = null;
|
|
65930
|
+
/**
|
|
65931
|
+
* Max fixed overhead (prefix + postfix glyph width) across all staves of this bar.
|
|
65932
|
+
* Used by the layout-mode horizontal scaling pass to carve out the fixed-overhead bucket
|
|
65933
|
+
* before distributing staff width across bars.
|
|
65934
|
+
*/
|
|
65935
|
+
maxFixedOverhead = 0;
|
|
65936
|
+
/**
|
|
65937
|
+
* Max natural content width (computedWidth - fixedOverhead) across all staves of this bar.
|
|
65938
|
+
* Used as the bar weight when the layout ignores {@link MasterBar.displayScale} (e.g.
|
|
65939
|
+
* Page layout with `SystemsLayoutMode.Automatic`).
|
|
65940
|
+
*/
|
|
65941
|
+
maxContentWidth = 0;
|
|
65749
65942
|
get lastMasterBarIndex() {
|
|
65750
65943
|
if (this.additionalMultiBarRestIndexes) {
|
|
65751
65944
|
return this.additionalMultiBarRestIndexes[this.additionalMultiBarRestIndexes.length - 1];
|
|
@@ -65927,6 +66120,45 @@
|
|
|
65927
66120
|
* This value is mainly used in the parchment style layout for correct scaling of the bars.
|
|
65928
66121
|
*/
|
|
65929
66122
|
totalBarDisplayScale = 0;
|
|
66123
|
+
/**
|
|
66124
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxFixedOverhead} across the system. The layout-mode
|
|
66125
|
+
* horizontal scaling pass subtracts this from the available staff width before distributing the
|
|
66126
|
+
* remainder across bars.
|
|
66127
|
+
*/
|
|
66128
|
+
totalFixedOverhead = 0;
|
|
66129
|
+
/**
|
|
66130
|
+
* Sum of per-bar {@link MasterBarsRenderers.maxContentWidth} across the system. Used as the
|
|
66131
|
+
* denominator when distributing staff width in modes that weight bars by natural content width
|
|
66132
|
+
* (Page layout with `SystemsLayoutMode.Automatic`).
|
|
66133
|
+
*/
|
|
66134
|
+
totalContentWidth = 0;
|
|
66135
|
+
/**
|
|
66136
|
+
* Shortest note duration (in ticks) across every bar that has been added to this system, used
|
|
66137
|
+
* as the common reference in the Gourlay stretch formula so that rhythmically-equivalent beats
|
|
66138
|
+
* in different bars of the same system align column-wise.
|
|
66139
|
+
*
|
|
66140
|
+
* `-1` means "no bar added yet". The value only moves downward during system assembly; when a
|
|
66141
|
+
* new bar introduces a shorter minimum, {@link isMinDurationDirty} is set so that
|
|
66142
|
+
* {@link reconcileMinDurationIfDirty} can re-derive spring constants on the previously-added
|
|
66143
|
+
* bars before layout distribution runs.
|
|
66144
|
+
*/
|
|
66145
|
+
minDuration = -1;
|
|
66146
|
+
/**
|
|
66147
|
+
* Set when a bar added to this system introduced a shorter {@link minDuration} than previously
|
|
66148
|
+
* seen, leaving earlier bars' spring constants stale. Consumed by
|
|
66149
|
+
* {@link reconcileMinDurationIfDirty} which is called from `VerticalLayoutBase._fitSystem`
|
|
66150
|
+
* once the system is fully assembled.
|
|
66151
|
+
*/
|
|
66152
|
+
isMinDurationDirty = false;
|
|
66153
|
+
/**
|
|
66154
|
+
* Whether this system coordinates a shared minimum-duration reference across its bars for the
|
|
66155
|
+
* Gourlay stretch formula. Defaults to `true` for page-style and parchment layouts where bars
|
|
66156
|
+
* of a system fight for a common staff width. Set to `false` for horizontal layouts where each
|
|
66157
|
+
* bar is sized independently (by `bar.displayWidth` or its intrinsic width) and there is no
|
|
66158
|
+
* column-alignment concern - each bar keeps its local minimum so pre-existing rendering is
|
|
66159
|
+
* preserved.
|
|
66160
|
+
*/
|
|
66161
|
+
shareMinDurationAcrossBars = true;
|
|
65930
66162
|
isLast = false;
|
|
65931
66163
|
masterBarsRenderers = [];
|
|
65932
66164
|
staves = [];
|
|
@@ -65988,6 +66220,9 @@
|
|
|
65988
66220
|
}
|
|
65989
66221
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
65990
66222
|
this._calculateAccoladeSpacing(tracks);
|
|
66223
|
+
// On the resize path the layoutingInfo was finalized in a previous layout pass, so we
|
|
66224
|
+
// only need to check whether its min-duration reference still matches the new system's.
|
|
66225
|
+
this._trackSystemMinDuration(renderers.layoutingInfo);
|
|
65991
66226
|
this._applyLayoutAndUpdateWidth();
|
|
65992
66227
|
return renderers;
|
|
65993
66228
|
}
|
|
@@ -66043,10 +66278,89 @@
|
|
|
66043
66278
|
this.firstVisibleStaff = firstVisibleStaff;
|
|
66044
66279
|
this._calculateAccoladeSpacing(tracks);
|
|
66045
66280
|
barLayoutingInfo.finish();
|
|
66281
|
+
// Reconcile against the system-wide minimum-duration reference now that springs are
|
|
66282
|
+
// finalized. If this bar introduced a shorter note, earlier bars become stale (flagged
|
|
66283
|
+
// for bulk reconcile at fit time). If the system already had a shorter min than this
|
|
66284
|
+
// bar's local one, this bar's spring constants are recomputed immediately so the width
|
|
66285
|
+
// we return below reflects the shared reference.
|
|
66286
|
+
this._trackSystemMinDuration(barLayoutingInfo);
|
|
66046
66287
|
// ensure same widths of new renderer
|
|
66047
66288
|
result.width = this._applyLayoutAndUpdateWidth();
|
|
66048
66289
|
return result;
|
|
66049
66290
|
}
|
|
66291
|
+
/**
|
|
66292
|
+
* Updates {@link minDuration} and {@link isMinDurationDirty} when a bar is added, and brings
|
|
66293
|
+
* the just-added bar's {@link BarLayoutingInfo} in line with the current system minimum if the
|
|
66294
|
+
* system already saw a shorter reference. The bulk reconcile over previously-added bars is
|
|
66295
|
+
* deferred to {@link reconcileMinDurationIfDirty} (called from `_fitSystem`) to avoid
|
|
66296
|
+
* re-iterating the system every time a bar is appended.
|
|
66297
|
+
*/
|
|
66298
|
+
_trackSystemMinDuration(info) {
|
|
66299
|
+
if (!this.shareMinDurationAcrossBars) {
|
|
66300
|
+
return;
|
|
66301
|
+
}
|
|
66302
|
+
const localMin = info.localMinDuration;
|
|
66303
|
+
if (this.minDuration === -1 || localMin < this.minDuration) {
|
|
66304
|
+
// this bar shortens the system minimum; earlier bars (if any) are now stale
|
|
66305
|
+
if (this.masterBarsRenderers.length > 1 && localMin !== this.minDuration) {
|
|
66306
|
+
this.isMinDurationDirty = true;
|
|
66307
|
+
}
|
|
66308
|
+
this.minDuration = localMin;
|
|
66309
|
+
}
|
|
66310
|
+
if (info.computedWithMinDuration > this.minDuration) {
|
|
66311
|
+
// this bar was initialized against a larger (local) min than the system carries; pull
|
|
66312
|
+
// it down to the system reference so its computedWidth reflects the shared spacing.
|
|
66313
|
+
info.recomputeSpringConstants(this.minDuration);
|
|
66314
|
+
}
|
|
66315
|
+
}
|
|
66316
|
+
/**
|
|
66317
|
+
* Re-derives spring constants on bars whose {@link BarLayoutingInfo.computedWithMinDuration}
|
|
66318
|
+
* is out of sync with the current {@link minDuration}, and rebuilds the cached system totals
|
|
66319
|
+
* (widths, {@link totalFixedOverhead}, {@link totalContentWidth}) from the refreshed bar
|
|
66320
|
+
* widths. Called from `VerticalLayoutBase._fitSystem` after the system is fully assembled and
|
|
66321
|
+
* before distribution runs. No-op when {@link isMinDurationDirty} is false.
|
|
66322
|
+
*/
|
|
66323
|
+
reconcileMinDurationIfDirty() {
|
|
66324
|
+
if (!this.isMinDurationDirty) {
|
|
66325
|
+
return;
|
|
66326
|
+
}
|
|
66327
|
+
let systemWidth = this.accoladeWidth;
|
|
66328
|
+
let totalFixedOverhead = 0;
|
|
66329
|
+
let totalContentWidth = 0;
|
|
66330
|
+
for (const mb of this.masterBarsRenderers) {
|
|
66331
|
+
if (mb.layoutingInfo.computedWithMinDuration > this.minDuration) {
|
|
66332
|
+
mb.layoutingInfo.recomputeSpringConstants(this.minDuration);
|
|
66333
|
+
}
|
|
66334
|
+
let maxPrefix = 0;
|
|
66335
|
+
let maxContent = 0;
|
|
66336
|
+
let realWidth = 0;
|
|
66337
|
+
for (const r of mb.renderers) {
|
|
66338
|
+
r.applyLayoutingInfo();
|
|
66339
|
+
if (r.computedWidth > realWidth) {
|
|
66340
|
+
realWidth = r.computedWidth;
|
|
66341
|
+
}
|
|
66342
|
+
const overhead = r.fixedOverhead;
|
|
66343
|
+
if (overhead > maxPrefix) {
|
|
66344
|
+
maxPrefix = overhead;
|
|
66345
|
+
}
|
|
66346
|
+
const content = Math.max(0, r.computedWidth - overhead);
|
|
66347
|
+
if (content > maxContent) {
|
|
66348
|
+
maxContent = content;
|
|
66349
|
+
}
|
|
66350
|
+
}
|
|
66351
|
+
mb.maxFixedOverhead = maxPrefix;
|
|
66352
|
+
mb.maxContentWidth = maxContent;
|
|
66353
|
+
mb.width = realWidth;
|
|
66354
|
+
systemWidth += realWidth;
|
|
66355
|
+
totalFixedOverhead += maxPrefix;
|
|
66356
|
+
totalContentWidth += maxContent;
|
|
66357
|
+
}
|
|
66358
|
+
this.width = systemWidth;
|
|
66359
|
+
this.computedWidth = systemWidth;
|
|
66360
|
+
this.totalFixedOverhead = totalFixedOverhead;
|
|
66361
|
+
this.totalContentWidth = totalContentWidth;
|
|
66362
|
+
this.isMinDurationDirty = false;
|
|
66363
|
+
}
|
|
66050
66364
|
getBarDisplayScale(renderer) {
|
|
66051
66365
|
return this.staves.length > 1 ? renderer.bar.masterBar.displayScale : renderer.bar.displayScale;
|
|
66052
66366
|
}
|
|
@@ -66085,12 +66399,16 @@
|
|
|
66085
66399
|
this.width -= width;
|
|
66086
66400
|
this.computedWidth -= width;
|
|
66087
66401
|
this.totalBarDisplayScale -= barDisplayScale;
|
|
66402
|
+
this.totalFixedOverhead -= toRemove.maxFixedOverhead;
|
|
66403
|
+
this.totalContentWidth -= toRemove.maxContentWidth;
|
|
66088
66404
|
return toRemove;
|
|
66089
66405
|
}
|
|
66090
66406
|
return null;
|
|
66091
66407
|
}
|
|
66092
66408
|
_applyLayoutAndUpdateWidth() {
|
|
66093
66409
|
let realWidth = 0;
|
|
66410
|
+
let maxFixedOverhead = 0;
|
|
66411
|
+
let maxContentWidth = 0;
|
|
66094
66412
|
let barDisplayScale = 0;
|
|
66095
66413
|
for (const s of this.allStaves) {
|
|
66096
66414
|
const last = s.barRenderers[s.barRenderers.length - 1];
|
|
@@ -66099,8 +66417,21 @@
|
|
|
66099
66417
|
if (last.computedWidth > realWidth) {
|
|
66100
66418
|
realWidth = last.computedWidth;
|
|
66101
66419
|
}
|
|
66420
|
+
const overhead = last.fixedOverhead;
|
|
66421
|
+
if (overhead > maxFixedOverhead) {
|
|
66422
|
+
maxFixedOverhead = overhead;
|
|
66423
|
+
}
|
|
66424
|
+
const content = Math.max(0, last.computedWidth - overhead);
|
|
66425
|
+
if (content > maxContentWidth) {
|
|
66426
|
+
maxContentWidth = content;
|
|
66427
|
+
}
|
|
66102
66428
|
}
|
|
66429
|
+
const renderers = this.masterBarsRenderers[this.masterBarsRenderers.length - 1];
|
|
66430
|
+
renderers.maxFixedOverhead = maxFixedOverhead;
|
|
66431
|
+
renderers.maxContentWidth = maxContentWidth;
|
|
66103
66432
|
this.totalBarDisplayScale += barDisplayScale;
|
|
66433
|
+
this.totalFixedOverhead += maxFixedOverhead;
|
|
66434
|
+
this.totalContentWidth += maxContentWidth;
|
|
66104
66435
|
this.width += realWidth;
|
|
66105
66436
|
this.computedWidth += realWidth;
|
|
66106
66437
|
return realWidth;
|
|
@@ -66598,17 +66929,6 @@
|
|
|
66598
66929
|
}
|
|
66599
66930
|
}
|
|
66600
66931
|
|
|
66601
|
-
/**
|
|
66602
|
-
* @internal
|
|
66603
|
-
*/
|
|
66604
|
-
class LazyPartial {
|
|
66605
|
-
args;
|
|
66606
|
-
renderCallback;
|
|
66607
|
-
constructor(args, renderCallback) {
|
|
66608
|
-
this.args = args;
|
|
66609
|
-
this.renderCallback = renderCallback;
|
|
66610
|
-
}
|
|
66611
|
-
}
|
|
66612
66932
|
/**
|
|
66613
66933
|
* This is the base class for creating new layouting engines for the score renderer.
|
|
66614
66934
|
* @internal
|
|
@@ -66639,15 +66959,21 @@
|
|
|
66639
66959
|
this.doResize();
|
|
66640
66960
|
}
|
|
66641
66961
|
layoutAndRender(renderHints) {
|
|
66642
|
-
this._lazyPartials.clear();
|
|
66643
66962
|
this.slurRegistry.clear();
|
|
66644
|
-
this.beamingRuleLookups.clear();
|
|
66645
|
-
this._barRendererLookup.clear();
|
|
66646
|
-
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66647
66963
|
const score = this.renderer.score;
|
|
66648
66964
|
this.firstBarIndex = ModelUtils.computeFirstDisplayedBarIndex(score, this.renderer.settings);
|
|
66649
66965
|
this.lastBarIndex = ModelUtils.computeLastDisplayedBarIndex(score, this.renderer.settings, this.firstBarIndex);
|
|
66650
66966
|
this.multiBarRestInfo = ModelUtils.buildMultiBarRestInfo(this.renderer.tracks, this.firstBarIndex, this.lastBarIndex);
|
|
66967
|
+
const firstChangedMasterBar = renderHints?.firstChangedMasterBar;
|
|
66968
|
+
if (firstChangedMasterBar !== undefined) {
|
|
66969
|
+
if (this.doUpdateForBars(renderHints)) {
|
|
66970
|
+
return;
|
|
66971
|
+
}
|
|
66972
|
+
}
|
|
66973
|
+
this._lazyPartials.clear();
|
|
66974
|
+
this.beamingRuleLookups.clear();
|
|
66975
|
+
this._barRendererLookup.clear();
|
|
66976
|
+
this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile);
|
|
66651
66977
|
this.pagePadding = this.renderer.settings.display.padding.map(p => p / this.renderer.settings.display.scale);
|
|
66652
66978
|
if (!this.pagePadding) {
|
|
66653
66979
|
this.pagePadding = [0, 0, 0, 0];
|
|
@@ -66662,6 +66988,9 @@
|
|
|
66662
66988
|
this.doLayoutAndRender(renderHints);
|
|
66663
66989
|
}
|
|
66664
66990
|
_lazyPartials = new Map();
|
|
66991
|
+
getExistingPartialArgs(id) {
|
|
66992
|
+
return this._lazyPartials.has(id) ? this._lazyPartials.get(id).args : undefined;
|
|
66993
|
+
}
|
|
66665
66994
|
registerPartial(args, callback) {
|
|
66666
66995
|
if (args.height === 0) {
|
|
66667
66996
|
return;
|
|
@@ -66680,7 +67009,11 @@
|
|
|
66680
67009
|
}
|
|
66681
67010
|
else {
|
|
66682
67011
|
// in case of lazy loading -> first register lazy, then notify
|
|
66683
|
-
|
|
67012
|
+
const partial = {
|
|
67013
|
+
args,
|
|
67014
|
+
renderCallback: callback
|
|
67015
|
+
};
|
|
67016
|
+
this._lazyPartials.set(args.id, partial);
|
|
66684
67017
|
this.renderer.partialLayoutFinished.trigger(args);
|
|
66685
67018
|
}
|
|
66686
67019
|
}
|
|
@@ -66975,7 +67308,7 @@
|
|
|
66975
67308
|
glyph.textAlign = TextAlign.Left;
|
|
66976
67309
|
}
|
|
66977
67310
|
}
|
|
66978
|
-
|
|
67311
|
+
_layoutAndRenderAnnotation(y) {
|
|
66979
67312
|
// attention, you are not allowed to remove change this notice within any version of this library without permission!
|
|
66980
67313
|
const msg = 'rendered by alphaTab';
|
|
66981
67314
|
const resources = this.renderer.settings.display.resources;
|
|
@@ -67039,6 +67372,12 @@
|
|
|
67039
67372
|
}
|
|
67040
67373
|
doResize() {
|
|
67041
67374
|
}
|
|
67375
|
+
doUpdateForBars(_renderHints) {
|
|
67376
|
+
// not supported yet, modifications likely cause anyhow full updates
|
|
67377
|
+
// as we do not optimize effect bands yet. with effect bands being more
|
|
67378
|
+
// isolated in bars we could try updating dynamically
|
|
67379
|
+
return false;
|
|
67380
|
+
}
|
|
67042
67381
|
doLayoutAndRender(renderHints) {
|
|
67043
67382
|
const score = this.renderer.score;
|
|
67044
67383
|
let startIndex = this.renderer.settings.display.startBar;
|
|
@@ -67052,6 +67391,11 @@
|
|
|
67052
67391
|
endBarIndex = startIndex + endBarIndex - 1; // map count to array index
|
|
67053
67392
|
endBarIndex = Math.min(score.masterBars.length - 1, Math.max(0, endBarIndex));
|
|
67054
67393
|
this._system = this.createEmptyStaffSystem(0);
|
|
67394
|
+
// Each bar in horizontal layout is sized independently (by bar.displayWidth or the bar's
|
|
67395
|
+
// intrinsic width), so there is no shared staff width to distribute across bars. Keep each
|
|
67396
|
+
// bar's spring constants referenced against its own local minimum-duration so rendering
|
|
67397
|
+
// matches the historical per-bar behaviour.
|
|
67398
|
+
this._system.shareMinDurationAcrossBars = false;
|
|
67055
67399
|
this._system.isLast = true;
|
|
67056
67400
|
this._system.x = this.pagePadding[0];
|
|
67057
67401
|
this._system.y = this.pagePadding[1];
|
|
@@ -67113,7 +67457,7 @@
|
|
|
67113
67457
|
currentBarIndex += partial.masterBars.length;
|
|
67114
67458
|
}
|
|
67115
67459
|
this.height = this.layoutAndRenderBottomScoreInfo(this.height);
|
|
67116
|
-
this.height = this.
|
|
67460
|
+
this.height = this._layoutAndRenderAnnotation(this.height);
|
|
67117
67461
|
this.height += this.pagePadding[3];
|
|
67118
67462
|
this.height *= this.renderer.settings.display.scale;
|
|
67119
67463
|
}
|
|
@@ -67180,11 +67524,16 @@
|
|
|
67180
67524
|
_allMasterBarRenderers = [];
|
|
67181
67525
|
_barsFromPreviousSystem = [];
|
|
67182
67526
|
_reuseViewPort = false;
|
|
67527
|
+
_preSystemPartialIds = [];
|
|
67528
|
+
_systemPartialIds = [];
|
|
67183
67529
|
doLayoutAndRender(renderHints) {
|
|
67184
67530
|
let y = this.pagePadding[1];
|
|
67185
67531
|
this.width = this.renderer.width;
|
|
67186
67532
|
this._allMasterBarRenderers = [];
|
|
67533
|
+
this._preSystemPartialIds = [];
|
|
67534
|
+
this._systemPartialIds = [];
|
|
67187
67535
|
this._reuseViewPort = renderHints?.reuseViewport ?? false;
|
|
67536
|
+
this._systems = [];
|
|
67188
67537
|
//
|
|
67189
67538
|
// 1. Score Info
|
|
67190
67539
|
y = this._layoutAndRenderScoreInfo(y, -1);
|
|
@@ -67196,15 +67545,23 @@
|
|
|
67196
67545
|
y = this._layoutAndRenderChordDiagrams(y, -1);
|
|
67197
67546
|
//
|
|
67198
67547
|
// 4. One result per StaffSystem
|
|
67199
|
-
y = this._layoutAndRenderScore(y);
|
|
67548
|
+
y = this._layoutAndRenderScore(y, this.firstBarIndex);
|
|
67200
67549
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67201
|
-
y = this.
|
|
67550
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67202
67551
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67203
67552
|
}
|
|
67204
67553
|
registerPartial(args, callback) {
|
|
67205
67554
|
args.reuseViewport = this._reuseViewPort;
|
|
67206
67555
|
super.registerPartial(args, callback);
|
|
67207
67556
|
}
|
|
67557
|
+
reregisterPartial(id) {
|
|
67558
|
+
const args = this.getExistingPartialArgs(id);
|
|
67559
|
+
if (!args) {
|
|
67560
|
+
return;
|
|
67561
|
+
}
|
|
67562
|
+
args.reuseViewport = this._reuseViewPort;
|
|
67563
|
+
this.renderer.partialLayoutFinished.trigger(args);
|
|
67564
|
+
}
|
|
67208
67565
|
get supportsResize() {
|
|
67209
67566
|
return true;
|
|
67210
67567
|
}
|
|
@@ -67215,6 +67572,47 @@
|
|
|
67215
67572
|
}
|
|
67216
67573
|
return x;
|
|
67217
67574
|
}
|
|
67575
|
+
doUpdateForBars(renderHints) {
|
|
67576
|
+
this._reuseViewPort = renderHints.reuseViewport ?? false;
|
|
67577
|
+
const firstModifiedMasterBar = renderHints.firstChangedMasterBar;
|
|
67578
|
+
// first update existing systems as needed
|
|
67579
|
+
const systemIndex = this._systems.findIndex(s => {
|
|
67580
|
+
const first = s.masterBarsRenderers[0].masterBar.index;
|
|
67581
|
+
const last = s.masterBarsRenderers[s.masterBarsRenderers.length - 1].masterBar.index;
|
|
67582
|
+
return first <= firstModifiedMasterBar && firstModifiedMasterBar <= last;
|
|
67583
|
+
});
|
|
67584
|
+
if (systemIndex === -1 || !this.renderer.settings.core.enableLazyLoading) {
|
|
67585
|
+
return false;
|
|
67586
|
+
}
|
|
67587
|
+
// Bars from the start of the re-layouted system onward will be re-registered during the
|
|
67588
|
+
// paint pass. Clear their old entries from the preserved BoundsLookup so registration
|
|
67589
|
+
// produces a clean, complete lookup after this render finishes.
|
|
67590
|
+
const firstRebuiltBarIndex = this._systems[systemIndex].masterBarsRenderers[0].masterBar.index;
|
|
67591
|
+
this.renderer.boundsLookup.clearFromMasterBar(firstRebuiltBarIndex);
|
|
67592
|
+
// for now we do a full relayout from the first modified masterbar
|
|
67593
|
+
// there is a lot of room for even more performant updates, but they come
|
|
67594
|
+
// at a risk that features break.
|
|
67595
|
+
// e.g. we could only shift systems where the content didn't change,
|
|
67596
|
+
// but we might still have ties/slurs which have to be updated.
|
|
67597
|
+
const removeSystems = this._systems.splice(systemIndex, this._systems.length - systemIndex);
|
|
67598
|
+
this._systemPartialIds.splice(systemIndex, this._systemPartialIds.length - systemIndex);
|
|
67599
|
+
const system = removeSystems[0];
|
|
67600
|
+
let y = system.y;
|
|
67601
|
+
const firstBarIndex = system.masterBarsRenderers[0].masterBar.index;
|
|
67602
|
+
// signal all partials which didn't change
|
|
67603
|
+
for (const preSystemPartial of this._preSystemPartialIds) {
|
|
67604
|
+
this.reregisterPartial(preSystemPartial);
|
|
67605
|
+
}
|
|
67606
|
+
for (let i = 0; i < systemIndex; i++) {
|
|
67607
|
+
this.reregisterPartial(this._systemPartialIds[i]);
|
|
67608
|
+
}
|
|
67609
|
+
// new partials for all other prats
|
|
67610
|
+
y = this._layoutAndRenderScore(y, firstBarIndex);
|
|
67611
|
+
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67612
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67613
|
+
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67614
|
+
return true;
|
|
67615
|
+
}
|
|
67218
67616
|
doResize() {
|
|
67219
67617
|
let y = this.pagePadding[1];
|
|
67220
67618
|
this.width = this.renderer.width;
|
|
@@ -67233,7 +67631,7 @@
|
|
|
67233
67631
|
// 4. One result per StaffSystem
|
|
67234
67632
|
y = this._resizeAndRenderScore(y, oldHeight);
|
|
67235
67633
|
y = this.layoutAndRenderBottomScoreInfo(y);
|
|
67236
|
-
y = this.
|
|
67634
|
+
y = this._layoutAndRenderAnnotation(y);
|
|
67237
67635
|
this.height = (y + this.pagePadding[3]) * this.renderer.settings.display.scale;
|
|
67238
67636
|
}
|
|
67239
67637
|
_layoutAndRenderTunings(y, totalHeight = -1) {
|
|
@@ -67257,6 +67655,7 @@
|
|
|
67257
67655
|
canvas.textAlign = TextAlign.Center;
|
|
67258
67656
|
this.tuningGlyph.paint(0, 0, canvas);
|
|
67259
67657
|
});
|
|
67658
|
+
this._preSystemPartialIds.push(e.id);
|
|
67260
67659
|
return y + tuningHeight;
|
|
67261
67660
|
}
|
|
67262
67661
|
_layoutAndRenderChordDiagrams(y, totalHeight = -1) {
|
|
@@ -67280,6 +67679,7 @@
|
|
|
67280
67679
|
canvas.textAlign = TextAlign.Center;
|
|
67281
67680
|
this.chordDiagrams.paint(0, 0, canvas);
|
|
67282
67681
|
});
|
|
67682
|
+
this._preSystemPartialIds.push(e.id);
|
|
67283
67683
|
return y + diagramHeight;
|
|
67284
67684
|
}
|
|
67285
67685
|
_layoutAndRenderScoreInfo(y, totalHeight = -1) {
|
|
@@ -67322,12 +67722,14 @@
|
|
|
67322
67722
|
g.paint(0, 0, canvas);
|
|
67323
67723
|
}
|
|
67324
67724
|
});
|
|
67725
|
+
this._preSystemPartialIds.push(e.id);
|
|
67325
67726
|
}
|
|
67326
67727
|
return y + infoHeight;
|
|
67327
67728
|
}
|
|
67328
67729
|
_resizeAndRenderScore(y, oldHeight) {
|
|
67329
67730
|
// if we have a fixed number of bars per row, we only need to refit them.
|
|
67330
67731
|
const barsPerRowActive = this.getBarsPerSystem(0) > 0;
|
|
67732
|
+
this._systemPartialIds = [];
|
|
67331
67733
|
if (barsPerRowActive) {
|
|
67332
67734
|
for (let i = 0; i < this._systems.length; i++) {
|
|
67333
67735
|
const system = this._systems[i];
|
|
@@ -67390,11 +67792,9 @@
|
|
|
67390
67792
|
}
|
|
67391
67793
|
return y;
|
|
67392
67794
|
}
|
|
67393
|
-
_layoutAndRenderScore(y) {
|
|
67394
|
-
const startIndex = this.firstBarIndex;
|
|
67795
|
+
_layoutAndRenderScore(y, startIndex) {
|
|
67395
67796
|
let currentBarIndex = startIndex;
|
|
67396
67797
|
const endBarIndex = this.lastBarIndex;
|
|
67397
|
-
this._systems = [];
|
|
67398
67798
|
while (currentBarIndex <= endBarIndex) {
|
|
67399
67799
|
// create system and align set proper coordinates
|
|
67400
67800
|
const system = this._createStaffSystem(currentBarIndex, endBarIndex);
|
|
@@ -67429,6 +67829,7 @@
|
|
|
67429
67829
|
// since we use partial drawing
|
|
67430
67830
|
system.paint(0, -(args.y / this.renderer.settings.display.scale), canvas);
|
|
67431
67831
|
});
|
|
67832
|
+
this._systemPartialIds.push(args.id);
|
|
67432
67833
|
// calculate coordinates for next system
|
|
67433
67834
|
return height;
|
|
67434
67835
|
}
|
|
@@ -67436,6 +67837,10 @@
|
|
|
67436
67837
|
* Realignes the bars in this line according to the available space
|
|
67437
67838
|
*/
|
|
67438
67839
|
_fitSystem(system) {
|
|
67840
|
+
// If a bar added late in the assembly introduced a shorter note than earlier bars, the
|
|
67841
|
+
// earlier bars' spring constants (and the cached system widths / totals) are stale.
|
|
67842
|
+
// Reconcile now - it's a no-op when nothing changed.
|
|
67843
|
+
system.reconcileMinDurationIfDirty();
|
|
67439
67844
|
if (system.isFull || system.width > this._maxWidth || this.renderer.settings.display.justifyLastSystem) {
|
|
67440
67845
|
this._scaleToWidth(system, this._maxWidth);
|
|
67441
67846
|
}
|
|
@@ -67447,29 +67852,35 @@
|
|
|
67447
67852
|
_scaleToWidth(system, width) {
|
|
67448
67853
|
const staffWidth = width - system.accoladeWidth;
|
|
67449
67854
|
const shouldApplyBarScale = this.shouldApplyBarScale;
|
|
67450
|
-
|
|
67451
|
-
//
|
|
67452
|
-
//
|
|
67453
|
-
//
|
|
67454
|
-
//
|
|
67455
|
-
//
|
|
67456
|
-
|
|
67457
|
-
|
|
67855
|
+
// Industry fixed-overhead model (Behind Bars, Dorico, Finale, Sibelius, MuseScore, Guitar Pro):
|
|
67856
|
+
// prefix/postfix glyphs (clef, key sig, time sig, barlines) are treated as fixed overhead and the
|
|
67857
|
+
// remaining staff width is distributed across bars by a per-bar weight.
|
|
67858
|
+
//
|
|
67859
|
+
// distributable = staffWidth - totalFixedOverhead
|
|
67860
|
+
// contentShare = distributable / sum(weight)
|
|
67861
|
+
// bar.width = bar.maxFixedOverhead + weight * contentShare
|
|
67862
|
+
//
|
|
67863
|
+
// The weight depends on the layout mode:
|
|
67864
|
+
// - shouldApplyBarScale=true -> weight = bar.displayScale (model-driven, matches Guitar Pro)
|
|
67865
|
+
// displayScale defaults to 1, so an unset value behaves identically
|
|
67866
|
+
// to an explicit 1 (GP omits the property when not customized).
|
|
67867
|
+
// - shouldApplyBarScale=false -> weight = natural content width (automatic, ignores displayScale)
|
|
67868
|
+
//
|
|
67869
|
+
// Per-bar maxFixedOverhead / maxContentWidth and the system-wide totals are maintained incrementally
|
|
67870
|
+
// in StaffSystem._applyLayoutAndUpdateWidth / revertLastBar so this pass can apply directly.
|
|
67871
|
+
const weightTotal = shouldApplyBarScale ? system.totalBarDisplayScale : system.totalContentWidth;
|
|
67872
|
+
const distributable = Math.max(0, staffWidth - system.totalFixedOverhead);
|
|
67873
|
+
const contentShare = weightTotal > 0 ? distributable / weightTotal : 0;
|
|
67458
67874
|
for (const s of system.allStaves) {
|
|
67459
67875
|
s.resetSharedLayoutData();
|
|
67460
|
-
// scale the bars by keeping their respective ratio size
|
|
67461
67876
|
let w = 0;
|
|
67462
|
-
for (
|
|
67877
|
+
for (let i = 0; i < s.barRenderers.length; i++) {
|
|
67878
|
+
const renderer = s.barRenderers[i];
|
|
67879
|
+
const mb = system.masterBarsRenderers[i];
|
|
67463
67880
|
renderer.x = w;
|
|
67464
67881
|
renderer.y = s.topPadding + s.topOverflow;
|
|
67465
|
-
|
|
67466
|
-
|
|
67467
|
-
const barDisplayScale = system.getBarDisplayScale(renderer);
|
|
67468
|
-
actualBarWidth = (barDisplayScale * staffWidth) / totalScale;
|
|
67469
|
-
}
|
|
67470
|
-
else {
|
|
67471
|
-
actualBarWidth = renderer.computedWidth + spacePerBar;
|
|
67472
|
-
}
|
|
67882
|
+
const weight = shouldApplyBarScale ? system.getBarDisplayScale(renderer) : mb.maxContentWidth;
|
|
67883
|
+
const actualBarWidth = mb.maxFixedOverhead + weight * contentShare;
|
|
67473
67884
|
renderer.scaleToWidth(actualBarWidth);
|
|
67474
67885
|
w += renderer.width;
|
|
67475
67886
|
}
|
|
@@ -69216,16 +69627,18 @@
|
|
|
69216
69627
|
s = [];
|
|
69217
69628
|
const zero = MusicFontSymbol.Tuplet0;
|
|
69218
69629
|
if (num > 10) {
|
|
69219
|
-
|
|
69220
|
-
s.push((zero +
|
|
69630
|
+
const tens = Math.floor(num / 10);
|
|
69631
|
+
s.push((zero + tens));
|
|
69632
|
+
s.push((zero + (num - 10 * tens)));
|
|
69221
69633
|
}
|
|
69222
69634
|
else {
|
|
69223
69635
|
s.push((zero + num));
|
|
69224
69636
|
}
|
|
69225
69637
|
s.push(MusicFontSymbol.TupletColon);
|
|
69226
69638
|
if (den > 10) {
|
|
69227
|
-
|
|
69228
|
-
s.push((zero +
|
|
69639
|
+
const tens = Math.floor(den / 10);
|
|
69640
|
+
s.push((zero + tens));
|
|
69641
|
+
s.push((zero + (den - 10 * tens)));
|
|
69229
69642
|
}
|
|
69230
69643
|
else {
|
|
69231
69644
|
s.push((zero + den));
|
|
@@ -69291,12 +69704,24 @@
|
|
|
69291
69704
|
const firstNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(firstNonRestBeat);
|
|
69292
69705
|
const lastNonRestBeamingHelper = this.helpers.getBeamingHelperForBeat(lastNonRestBeat);
|
|
69293
69706
|
const direction = this.getTupletBeamDirection(firstNonRestBeamingHelper);
|
|
69294
|
-
let startY
|
|
69295
|
-
let endY
|
|
69707
|
+
let startY;
|
|
69708
|
+
let endY;
|
|
69296
69709
|
if (isRestOnly) {
|
|
69297
|
-
|
|
69710
|
+
// rests have no stems, so anchor to the actual rest glyph bounds
|
|
69711
|
+
// instead of a stem-adjusted flag position (which would place the bracket
|
|
69712
|
+
// a full quarter-stem length away from the rests).
|
|
69713
|
+
if (direction === BeamDirection.Up) {
|
|
69714
|
+
startY = Math.min(this.getRestY(firstNonRestBeat, NoteYPosition.Top), this.getRestY(lastNonRestBeat, NoteYPosition.Top));
|
|
69715
|
+
}
|
|
69716
|
+
else {
|
|
69717
|
+
startY = Math.max(this.getRestY(firstNonRestBeat, NoteYPosition.Bottom), this.getRestY(lastNonRestBeat, NoteYPosition.Bottom));
|
|
69718
|
+
}
|
|
69298
69719
|
endY = startY;
|
|
69299
69720
|
}
|
|
69721
|
+
else {
|
|
69722
|
+
startY = this.calculateBeamYWithDirection(firstNonRestBeamingHelper, startX, direction);
|
|
69723
|
+
endY = this.calculateBeamYWithDirection(lastNonRestBeamingHelper, endX, direction);
|
|
69724
|
+
}
|
|
69300
69725
|
// align line centered in available space
|
|
69301
69726
|
if (direction === BeamDirection.Down) {
|
|
69302
69727
|
startY += shift;
|
|
@@ -69666,7 +70091,30 @@
|
|
|
69666
70091
|
let minNoteY = 0;
|
|
69667
70092
|
for (const v of this.helpers.beamHelpers) {
|
|
69668
70093
|
for (const h of v) {
|
|
69669
|
-
if (!this.shouldPaintBeamingHelper(h))
|
|
70094
|
+
if (!this.shouldPaintBeamingHelper(h)) {
|
|
70095
|
+
// beam is not drawn, but a rest-only tuplet still draws a bracket
|
|
70096
|
+
// anchored to the rest glyph bounds and needs overflow reserved.
|
|
70097
|
+
if (h.hasTuplet && h.isRestBeamHelper) {
|
|
70098
|
+
const tupletGroup = h.beats[0].tupletGroup;
|
|
70099
|
+
const tupletFirst = tupletGroup.beats[0];
|
|
70100
|
+
const tupletLast = tupletGroup.beats[tupletGroup.beats.length - 1];
|
|
70101
|
+
const tupletDirection = this.getTupletBeamDirection(h);
|
|
70102
|
+
if (tupletDirection === BeamDirection.Up) {
|
|
70103
|
+
const restTop = Math.min(this.getRestY(tupletFirst, NoteYPosition.Top), this.getRestY(tupletLast, NoteYPosition.Top));
|
|
70104
|
+
const topY = restTop - this.tupletSize - this.tupletOffset;
|
|
70105
|
+
if (topY < maxNoteY) {
|
|
70106
|
+
maxNoteY = topY;
|
|
70107
|
+
}
|
|
70108
|
+
}
|
|
70109
|
+
else {
|
|
70110
|
+
const restBottom = Math.max(this.getRestY(tupletFirst, NoteYPosition.Bottom), this.getRestY(tupletLast, NoteYPosition.Bottom));
|
|
70111
|
+
const bottomY = restBottom + this.tupletSize + this.tupletOffset;
|
|
70112
|
+
if (bottomY > minNoteY) {
|
|
70113
|
+
minNoteY = bottomY;
|
|
70114
|
+
}
|
|
70115
|
+
}
|
|
70116
|
+
}
|
|
70117
|
+
}
|
|
69670
70118
|
else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
|
|
69671
70119
|
const tupletDirection = this.getTupletBeamDirection(h);
|
|
69672
70120
|
const direction = this.getBeamDirection(h);
|
|
@@ -72204,8 +72652,10 @@
|
|
|
72204
72652
|
const group = new GlyphGroup(0, 0);
|
|
72205
72653
|
group.renderer = this.renderer;
|
|
72206
72654
|
for (const note of this.container.beat.notes) {
|
|
72207
|
-
|
|
72208
|
-
|
|
72655
|
+
if (note.isVisible) {
|
|
72656
|
+
const g = this._createBeatDot(sr.getNoteSteps(note), group);
|
|
72657
|
+
g.colorOverride = ElementStyleHelper.noteColor(sr.resources, NoteSubElement.StandardNotationEffects, note);
|
|
72658
|
+
}
|
|
72209
72659
|
}
|
|
72210
72660
|
this.addEffect(group);
|
|
72211
72661
|
}
|