@coderline/alphatab 1.6.0-alpha.1428 → 1.6.0-alpha.1430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alphaTab.core.min.mjs +2 -2
- package/dist/alphaTab.core.mjs +234 -101
- package/dist/alphaTab.d.ts +48 -25
- package/dist/alphaTab.js +234 -101
- package/dist/alphaTab.min.js +2 -2
- package/dist/alphaTab.min.mjs +1 -1
- package/dist/alphaTab.mjs +1 -1
- package/dist/alphaTab.vite.js +1 -1
- package/dist/alphaTab.vite.mjs +1 -1
- package/dist/alphaTab.webpack.js +1 -1
- package/dist/alphaTab.webpack.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 +1 -1
package/dist/alphaTab.core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* alphaTab v1.6.0-alpha.
|
|
2
|
+
* alphaTab v1.6.0-alpha.1430 (develop, build 1430)
|
|
3
3
|
*
|
|
4
4
|
* Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved.
|
|
5
5
|
*
|
|
@@ -1411,12 +1411,6 @@ class SyncPointData {
|
|
|
1411
1411
|
* Indicates for which repeat occurence this sync point is valid (e.g. 0 on the first time played, 1 on the second time played)
|
|
1412
1412
|
*/
|
|
1413
1413
|
this.barOccurence = 0;
|
|
1414
|
-
/**
|
|
1415
|
-
* The modified tempo at which the cursor should move (aka. the tempo played within the external audio track).
|
|
1416
|
-
* This information is used together with normal tempo changes to calculate how much faster/slower the
|
|
1417
|
-
* cursor playback is performed to align with the audio track.
|
|
1418
|
-
*/
|
|
1419
|
-
this.modifiedTempo = 0;
|
|
1420
1414
|
/**
|
|
1421
1415
|
* The audio offset marking the position within the audio track in milliseconds.
|
|
1422
1416
|
* This information is used to regularly sync (or on seeking) to match a given external audio time axis with the internal time axis.
|
|
@@ -5843,7 +5837,6 @@ class SyncPointDataCloner {
|
|
|
5843
5837
|
static clone(original) {
|
|
5844
5838
|
const clone = new SyncPointData();
|
|
5845
5839
|
clone.barOccurence = original.barOccurence;
|
|
5846
|
-
clone.modifiedTempo = original.modifiedTempo;
|
|
5847
5840
|
clone.millisecondOffset = original.millisecondOffset;
|
|
5848
5841
|
return clone;
|
|
5849
5842
|
}
|
|
@@ -7313,7 +7306,6 @@ class Score {
|
|
|
7313
7306
|
automation.ratioPosition = Math.min(1, Math.max(0, syncPoint.barPosition));
|
|
7314
7307
|
automation.type = AutomationType.SyncPoint;
|
|
7315
7308
|
automation.syncPointValue = new SyncPointData();
|
|
7316
|
-
automation.syncPointValue.modifiedTempo = syncPoint.modifiedTempo;
|
|
7317
7309
|
automation.syncPointValue.millisecondOffset = syncPoint.millisecondOffset;
|
|
7318
7310
|
automation.syncPointValue.barOccurence = syncPoint.barOccurence;
|
|
7319
7311
|
if (syncPoint.barIndex < this.masterBars.length) {
|
|
@@ -7346,8 +7338,7 @@ class Score {
|
|
|
7346
7338
|
barIndex: masterBar.index,
|
|
7347
7339
|
barOccurence: syncPoint.syncPointValue.barOccurence,
|
|
7348
7340
|
barPosition: syncPoint.ratioPosition,
|
|
7349
|
-
millisecondOffset: syncPoint.syncPointValue.millisecondOffset
|
|
7350
|
-
modifiedTempo: syncPoint.syncPointValue.modifiedTempo
|
|
7341
|
+
millisecondOffset: syncPoint.syncPointValue.millisecondOffset
|
|
7351
7342
|
});
|
|
7352
7343
|
}
|
|
7353
7344
|
}
|
|
@@ -14714,9 +14705,6 @@ class GpifParser {
|
|
|
14714
14705
|
case 'BarOccurrence':
|
|
14715
14706
|
syncPointValue.barOccurence = GpifParser.parseIntSafe(vc.innerText, 0);
|
|
14716
14707
|
break;
|
|
14717
|
-
case 'ModifiedTempo':
|
|
14718
|
-
syncPointValue.modifiedTempo = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14719
|
-
break;
|
|
14720
14708
|
case 'FrameOffset':
|
|
14721
14709
|
const frameOffset = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14722
14710
|
syncPointValue.millisecondOffset = (frameOffset / GpifParser.SampleRate) * 1000;
|
|
@@ -22339,6 +22327,61 @@ class SynthEvent {
|
|
|
22339
22327
|
}
|
|
22340
22328
|
}
|
|
22341
22329
|
|
|
22330
|
+
/**
|
|
22331
|
+
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
22332
|
+
*/
|
|
22333
|
+
class BackingTrackSyncPoint {
|
|
22334
|
+
constructor() {
|
|
22335
|
+
/**
|
|
22336
|
+
* The index of the masterbar to which this sync point belongs to.
|
|
22337
|
+
* @remarks
|
|
22338
|
+
* This property is purely informative for external use like in editors.
|
|
22339
|
+
* It has no impact to the synchronization itself.
|
|
22340
|
+
*/
|
|
22341
|
+
this.masterBarIndex = 0;
|
|
22342
|
+
/**
|
|
22343
|
+
* The occurence of the masterbar to which this sync point belongs to. The occurence
|
|
22344
|
+
* is 0-based and increases with every repeated play of a masterbar (e.g. on repeats or jumps).
|
|
22345
|
+
* @remarks
|
|
22346
|
+
* This property is purely informative for external use like in editors.
|
|
22347
|
+
* It has no impact to the synchronization itself.
|
|
22348
|
+
*/
|
|
22349
|
+
this.masterBarOccurence = 0;
|
|
22350
|
+
/**
|
|
22351
|
+
* The BPM the synthesizer has at the exact tick position of this sync point.
|
|
22352
|
+
*/
|
|
22353
|
+
this.synthBpm = 0;
|
|
22354
|
+
/**
|
|
22355
|
+
* The millisecond time position of the synthesizer when this sync point is reached.
|
|
22356
|
+
*/
|
|
22357
|
+
this.synthTime = 0;
|
|
22358
|
+
/**
|
|
22359
|
+
* The midi tick position of the synthesizer when this sync point is reached.
|
|
22360
|
+
*/
|
|
22361
|
+
this.synthTick = 0;
|
|
22362
|
+
/**
|
|
22363
|
+
* The millisecond time in the external media marking the synchronization point.
|
|
22364
|
+
*/
|
|
22365
|
+
this.syncTime = 0;
|
|
22366
|
+
/**
|
|
22367
|
+
* The BPM the song will have virtually after this sync point to align the external media time axis
|
|
22368
|
+
* with the one from the synthesizer.
|
|
22369
|
+
*/
|
|
22370
|
+
this.syncBpm = 0;
|
|
22371
|
+
}
|
|
22372
|
+
/**
|
|
22373
|
+
* Updates the synchronization BPM that will apply after this sync point.
|
|
22374
|
+
* @param nextSyncPointSynthTime The synthesizer time of the next sync point after this one.
|
|
22375
|
+
* @param nextSyncPointSyncTime The synchronization time of the next sync point after this one.
|
|
22376
|
+
*/
|
|
22377
|
+
updateSyncBpm(nextSyncPointSynthTime, nextSyncPointSyncTime) {
|
|
22378
|
+
const synthDuration = nextSyncPointSynthTime - this.synthTime;
|
|
22379
|
+
const syncedDuration = nextSyncPointSyncTime - this.syncTime;
|
|
22380
|
+
const modifiedTempo = (synthDuration / syncedDuration) * this.synthBpm;
|
|
22381
|
+
this.syncBpm = modifiedTempo;
|
|
22382
|
+
}
|
|
22383
|
+
}
|
|
22384
|
+
|
|
22342
22385
|
class MidiFileSequencerTempoChange {
|
|
22343
22386
|
constructor(bpm, ticks, time) {
|
|
22344
22387
|
this.bpm = bpm;
|
|
@@ -22346,14 +22389,6 @@ class MidiFileSequencerTempoChange {
|
|
|
22346
22389
|
this.time = time;
|
|
22347
22390
|
}
|
|
22348
22391
|
}
|
|
22349
|
-
class BackingTrackSyncPointWithTime {
|
|
22350
|
-
constructor(tick, time, modifiedTempo, millisecondOffset) {
|
|
22351
|
-
this.alphaTabTick = tick;
|
|
22352
|
-
this.alphaTabTime = time;
|
|
22353
|
-
this.modifiedTempo = modifiedTempo;
|
|
22354
|
-
this.millisecondOffset = millisecondOffset;
|
|
22355
|
-
}
|
|
22356
|
-
}
|
|
22357
22392
|
class MidiSequencerState {
|
|
22358
22393
|
constructor() {
|
|
22359
22394
|
this.tempoChanges = [];
|
|
@@ -22457,7 +22492,7 @@ class MidiFileSequencer {
|
|
|
22457
22492
|
this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
|
|
22458
22493
|
this._mainState.modifiedTempo =
|
|
22459
22494
|
this._mainState.syncPoints.length > 0
|
|
22460
|
-
? this._mainState.syncPoints[0].
|
|
22495
|
+
? this._mainState.syncPoints[0].syncBpm
|
|
22461
22496
|
: this._mainState.currentTempo;
|
|
22462
22497
|
if (this.isPlayingMain) {
|
|
22463
22498
|
const metronomeVolume = this._synthesizer.metronomeVolume;
|
|
@@ -22628,7 +22663,7 @@ class MidiFileSequencer {
|
|
|
22628
22663
|
}
|
|
22629
22664
|
mainUpdateSyncPoints(syncPoints) {
|
|
22630
22665
|
const state = this._mainState;
|
|
22631
|
-
syncPoints.sort((a, b) => a.
|
|
22666
|
+
syncPoints.sort((a, b) => a.synthTick - b.synthTick); // just in case
|
|
22632
22667
|
state.syncPoints = [];
|
|
22633
22668
|
if (syncPoints.length >= 0) {
|
|
22634
22669
|
let bpm = 120;
|
|
@@ -22638,6 +22673,8 @@ class MidiFileSequencer {
|
|
|
22638
22673
|
for (let i = 0; i < syncPoints.length; i++) {
|
|
22639
22674
|
const p = syncPoints[i];
|
|
22640
22675
|
let deltaTick = 0;
|
|
22676
|
+
// TODO: merge interpolation into MidiFileGenerator where we already play through
|
|
22677
|
+
// the time axis.
|
|
22641
22678
|
// remember state from previous sync point (or start). to handle linear interpolation
|
|
22642
22679
|
let previousModifiedTempo;
|
|
22643
22680
|
let previousMillisecondOffset;
|
|
@@ -22649,9 +22686,9 @@ class MidiFileSequencer {
|
|
|
22649
22686
|
}
|
|
22650
22687
|
else {
|
|
22651
22688
|
const previousSyncPoint = syncPoints[i - 1];
|
|
22652
|
-
previousModifiedTempo = previousSyncPoint.
|
|
22653
|
-
previousMillisecondOffset = previousSyncPoint.
|
|
22654
|
-
previousTick = previousSyncPoint.
|
|
22689
|
+
previousModifiedTempo = previousSyncPoint.syncBpm;
|
|
22690
|
+
previousMillisecondOffset = previousSyncPoint.syncTime;
|
|
22691
|
+
previousTick = previousSyncPoint.synthTick;
|
|
22655
22692
|
}
|
|
22656
22693
|
// process time until sync point
|
|
22657
22694
|
// here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
|
|
@@ -22659,27 +22696,31 @@ class MidiFileSequencer {
|
|
|
22659
22696
|
// otherwise the linear interpolation later in the lookup will fail.
|
|
22660
22697
|
// goal is to have always a linear increase between two points, no matter if the time axis is sliced by tempo changes or sync points
|
|
22661
22698
|
while (tempoChangeIndex < state.tempoChanges.length &&
|
|
22662
|
-
state.tempoChanges[tempoChangeIndex].ticks <= p.
|
|
22699
|
+
state.tempoChanges[tempoChangeIndex].ticks <= p.synthTick) {
|
|
22663
22700
|
deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
|
|
22664
22701
|
if (deltaTick > 0) {
|
|
22665
22702
|
absTick += deltaTick;
|
|
22666
22703
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22667
|
-
const millisPerTick = (p.
|
|
22704
|
+
const millisPerTick = (p.syncTime - previousMillisecondOffset) / (p.synthTick - previousTick);
|
|
22668
22705
|
const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
|
|
22669
|
-
|
|
22706
|
+
const syncPoint = new BackingTrackSyncPoint();
|
|
22707
|
+
syncPoint.synthTick = absTick;
|
|
22708
|
+
syncPoint.synthBpm = bpm;
|
|
22709
|
+
syncPoint.synthTime = absTime;
|
|
22710
|
+
syncPoint.syncTime = interpolatedMillisecondOffset;
|
|
22711
|
+
syncPoint.syncBpm = previousModifiedTempo;
|
|
22670
22712
|
}
|
|
22671
22713
|
bpm = state.tempoChanges[tempoChangeIndex].bpm;
|
|
22672
22714
|
tempoChangeIndex++;
|
|
22673
22715
|
}
|
|
22674
|
-
deltaTick = p.
|
|
22716
|
+
deltaTick = p.synthTick - absTick;
|
|
22675
22717
|
absTick += deltaTick;
|
|
22676
22718
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22677
|
-
state.syncPoints.push(
|
|
22719
|
+
state.syncPoints.push(p);
|
|
22678
22720
|
}
|
|
22679
22721
|
}
|
|
22680
22722
|
state.syncPointIndex = 0;
|
|
22681
|
-
state.modifiedTempo =
|
|
22682
|
-
state.syncPoints.length > 0 ? state.syncPoints[0].modifiedTempo : state.currentTempo;
|
|
22723
|
+
state.modifiedTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
|
|
22683
22724
|
}
|
|
22684
22725
|
currentTimePositionToTickPosition(timePosition) {
|
|
22685
22726
|
const state = this._currentState;
|
|
@@ -22712,16 +22753,15 @@ class MidiFileSequencer {
|
|
|
22712
22753
|
const syncPoints = state.syncPoints;
|
|
22713
22754
|
if (syncPoints.length > 0) {
|
|
22714
22755
|
let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
|
|
22715
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22756
|
+
if (timePosition < syncPoints[syncPointIndex].syncTime) {
|
|
22716
22757
|
syncPointIndex = 0;
|
|
22717
22758
|
}
|
|
22718
|
-
while (syncPointIndex + 1 < syncPoints.length &&
|
|
22719
|
-
syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
|
|
22759
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].syncTime <= timePosition) {
|
|
22720
22760
|
syncPointIndex++;
|
|
22721
22761
|
}
|
|
22722
22762
|
if (syncPointIndex !== state.syncPointIndex) {
|
|
22723
22763
|
state.syncPointIndex = syncPointIndex;
|
|
22724
|
-
state.modifiedTempo = syncPoints[syncPointIndex].
|
|
22764
|
+
state.modifiedTempo = syncPoints[syncPointIndex].syncBpm;
|
|
22725
22765
|
}
|
|
22726
22766
|
}
|
|
22727
22767
|
else {
|
|
@@ -22737,18 +22777,18 @@ class MidiFileSequencer {
|
|
|
22737
22777
|
this.updateSyncPoints(this._mainState, timePosition);
|
|
22738
22778
|
const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22739
22779
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22740
|
-
const timeDiff = timePosition - currentSyncPoint.
|
|
22780
|
+
const timeDiff = timePosition - currentSyncPoint.syncTime;
|
|
22741
22781
|
let alphaTabTimeDiff;
|
|
22742
22782
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22743
22783
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22744
|
-
const relativeTimeDiff = timeDiff / (nextSyncPoint.
|
|
22745
|
-
alphaTabTimeDiff = (nextSyncPoint.
|
|
22784
|
+
const relativeTimeDiff = timeDiff / (nextSyncPoint.syncTime - currentSyncPoint.syncTime);
|
|
22785
|
+
alphaTabTimeDiff = (nextSyncPoint.synthTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22746
22786
|
}
|
|
22747
22787
|
else {
|
|
22748
|
-
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.
|
|
22749
|
-
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.
|
|
22788
|
+
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.syncTime);
|
|
22789
|
+
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22750
22790
|
}
|
|
22751
|
-
return (currentSyncPoint.
|
|
22791
|
+
return (currentSyncPoint.synthTime + alphaTabTimeDiff) / this.playbackSpeed;
|
|
22752
22792
|
}
|
|
22753
22793
|
mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
|
|
22754
22794
|
const mainState = this._mainState;
|
|
@@ -22758,27 +22798,27 @@ class MidiFileSequencer {
|
|
|
22758
22798
|
}
|
|
22759
22799
|
timePosition *= this.playbackSpeed;
|
|
22760
22800
|
let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22761
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22801
|
+
if (timePosition < syncPoints[syncPointIndex].synthTime) {
|
|
22762
22802
|
syncPointIndex = 0;
|
|
22763
22803
|
}
|
|
22764
|
-
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].
|
|
22804
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].synthTime <= timePosition) {
|
|
22765
22805
|
syncPointIndex++;
|
|
22766
22806
|
}
|
|
22767
22807
|
// NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
|
|
22768
22808
|
// we ensure that we have a linear increase between two points
|
|
22769
22809
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22770
|
-
const alphaTabTimeDiff = timePosition - currentSyncPoint.
|
|
22810
|
+
const alphaTabTimeDiff = timePosition - currentSyncPoint.synthTime;
|
|
22771
22811
|
let backingTrackPos;
|
|
22772
22812
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22773
22813
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22774
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.
|
|
22775
|
-
const backingTrackDiff = nextSyncPoint.
|
|
22776
|
-
backingTrackPos = currentSyncPoint.
|
|
22814
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.synthTime - currentSyncPoint.synthTime);
|
|
22815
|
+
const backingTrackDiff = nextSyncPoint.syncTime - currentSyncPoint.syncTime;
|
|
22816
|
+
backingTrackPos = currentSyncPoint.syncTime + backingTrackDiff * relativeAlphaTabTimeDiff;
|
|
22777
22817
|
}
|
|
22778
22818
|
else {
|
|
22779
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.
|
|
22780
|
-
const frameDiff = backingTrackLength - currentSyncPoint.
|
|
22781
|
-
backingTrackPos = currentSyncPoint.
|
|
22819
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.synthTime);
|
|
22820
|
+
const frameDiff = backingTrackLength - currentSyncPoint.syncTime;
|
|
22821
|
+
backingTrackPos = currentSyncPoint.syncTime + frameDiff * relativeAlphaTabTimeDiff;
|
|
22782
22822
|
}
|
|
22783
22823
|
return backingTrackPos;
|
|
22784
22824
|
}
|
|
@@ -31050,7 +31090,6 @@ class SyncPointDataSerializer {
|
|
|
31050
31090
|
}
|
|
31051
31091
|
const o = new Map();
|
|
31052
31092
|
o.set("baroccurence", obj.barOccurence);
|
|
31053
|
-
o.set("modifiedtempo", obj.modifiedTempo);
|
|
31054
31093
|
o.set("millisecondoffset", obj.millisecondOffset);
|
|
31055
31094
|
return o;
|
|
31056
31095
|
}
|
|
@@ -31059,9 +31098,6 @@ class SyncPointDataSerializer {
|
|
|
31059
31098
|
case "baroccurence":
|
|
31060
31099
|
obj.barOccurence = v;
|
|
31061
31100
|
return true;
|
|
31062
|
-
case "modifiedtempo":
|
|
31063
|
-
obj.modifiedTempo = v;
|
|
31064
|
-
return true;
|
|
31065
31101
|
case "millisecondoffset":
|
|
31066
31102
|
obj.millisecondOffset = v;
|
|
31067
31103
|
return true;
|
|
@@ -35824,17 +35860,6 @@ class MidiTickLookup {
|
|
|
35824
35860
|
}
|
|
35825
35861
|
}
|
|
35826
35862
|
|
|
35827
|
-
/**
|
|
35828
|
-
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
35829
|
-
*/
|
|
35830
|
-
class BackingTrackSyncPoint {
|
|
35831
|
-
constructor(tick, data) {
|
|
35832
|
-
this.tick = 0;
|
|
35833
|
-
this.tick = tick;
|
|
35834
|
-
this.data = data;
|
|
35835
|
-
}
|
|
35836
|
-
}
|
|
35837
|
-
|
|
35838
35863
|
class MidiNoteDuration {
|
|
35839
35864
|
constructor() {
|
|
35840
35865
|
this.noteOnly = 0;
|
|
@@ -35855,6 +35880,14 @@ class RasgueadoInfo {
|
|
|
35855
35880
|
this.brushInfos = [];
|
|
35856
35881
|
}
|
|
35857
35882
|
}
|
|
35883
|
+
class PlayThroughContext {
|
|
35884
|
+
constructor() {
|
|
35885
|
+
this.synthTick = 0;
|
|
35886
|
+
this.synthTime = 0;
|
|
35887
|
+
this.currentTempo = 0;
|
|
35888
|
+
this.automationToSyncPoint = new Map();
|
|
35889
|
+
}
|
|
35890
|
+
}
|
|
35858
35891
|
/**
|
|
35859
35892
|
* This generator creates a midi file using a score.
|
|
35860
35893
|
*/
|
|
@@ -35981,9 +36014,22 @@ class MidiFileGenerator {
|
|
|
35981
36014
|
});
|
|
35982
36015
|
return syncPoints;
|
|
35983
36016
|
}
|
|
36017
|
+
/**
|
|
36018
|
+
* @internal
|
|
36019
|
+
*/
|
|
36020
|
+
static buildModifiedTempoLookup(score) {
|
|
36021
|
+
const syncPoints = [];
|
|
36022
|
+
const context = MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
|
|
36023
|
+
}, (_barIndex, _currentTick, _currentTempo) => {
|
|
36024
|
+
}, _endTick => {
|
|
36025
|
+
});
|
|
36026
|
+
return context.automationToSyncPoint;
|
|
36027
|
+
}
|
|
35984
36028
|
static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
|
|
35985
36029
|
const controller = new MidiPlaybackController(score);
|
|
35986
|
-
|
|
36030
|
+
const playContext = new PlayThroughContext();
|
|
36031
|
+
playContext.currentTempo = score.tempo;
|
|
36032
|
+
playContext.syncPoints = syncPoints;
|
|
35987
36033
|
let previousMasterBar = null;
|
|
35988
36034
|
// store the previous played bar for repeats
|
|
35989
36035
|
const barOccurence = new Map();
|
|
@@ -35996,23 +36042,11 @@ class MidiFileGenerator {
|
|
|
35996
36042
|
let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
|
|
35997
36043
|
occurence++;
|
|
35998
36044
|
barOccurence.set(index, occurence);
|
|
35999
|
-
generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
|
|
36000
|
-
const
|
|
36001
|
-
|
|
36002
|
-
|
|
36003
|
-
|
|
36004
|
-
const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
|
|
36005
|
-
syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
|
|
36006
|
-
}
|
|
36007
|
-
}
|
|
36008
|
-
}
|
|
36009
|
-
if (bar.tempoAutomations.length > 0) {
|
|
36010
|
-
currentTempo = bar.tempoAutomations[0].value;
|
|
36011
|
-
}
|
|
36012
|
-
generateTracks(index, currentTick, currentTempo);
|
|
36013
|
-
if (bar.tempoAutomations.length > 0) {
|
|
36014
|
-
currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
|
|
36015
|
-
}
|
|
36045
|
+
generateMasterBar(bar, previousMasterBar, currentTick, playContext.currentTempo, occurence);
|
|
36046
|
+
const trackTempo = bar.tempoAutomations.length > 0 ? bar.tempoAutomations[0].value : playContext.currentTempo;
|
|
36047
|
+
generateTracks(index, currentTick, trackTempo);
|
|
36048
|
+
playContext.synthTick = currentTick;
|
|
36049
|
+
MidiFileGenerator.processBarTime(bar, occurence, playContext);
|
|
36016
36050
|
}
|
|
36017
36051
|
controller.moveNext();
|
|
36018
36052
|
previousMasterBar = bar;
|
|
@@ -36023,21 +36057,119 @@ class MidiFileGenerator {
|
|
|
36023
36057
|
// but where it ends according to the BPM and the remaining ticks.
|
|
36024
36058
|
if (syncPoints.length > 0) {
|
|
36025
36059
|
const lastSyncPoint = syncPoints[syncPoints.length - 1];
|
|
36026
|
-
const remainingTicks = controller.currentTick - lastSyncPoint.
|
|
36060
|
+
const remainingTicks = controller.currentTick - lastSyncPoint.synthTick;
|
|
36027
36061
|
if (remainingTicks > 0) {
|
|
36028
|
-
const
|
|
36029
|
-
|
|
36030
|
-
|
|
36031
|
-
|
|
36032
|
-
|
|
36033
|
-
//
|
|
36034
|
-
|
|
36035
|
-
|
|
36036
|
-
|
|
36037
|
-
|
|
36062
|
+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
|
|
36063
|
+
backingTrackSyncPoint.masterBarIndex = previousMasterBar.index;
|
|
36064
|
+
backingTrackSyncPoint.masterBarOccurence = barOccurence.get(previousMasterBar.index) - 1;
|
|
36065
|
+
backingTrackSyncPoint.synthTick = controller.currentTick;
|
|
36066
|
+
backingTrackSyncPoint.synthBpm = playContext.currentTempo;
|
|
36067
|
+
// we need to assume some BPM for the last interpolated point.
|
|
36068
|
+
// if we have more than just a start point, we keep the BPM before the last manual sync point
|
|
36069
|
+
// otherwise we have no customized sync BPM known and keep the synthesizer one.
|
|
36070
|
+
backingTrackSyncPoint.syncBpm =
|
|
36071
|
+
syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
|
|
36072
|
+
backingTrackSyncPoint.synthTime =
|
|
36073
|
+
lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
|
|
36074
|
+
backingTrackSyncPoint.syncTime =
|
|
36075
|
+
lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
|
|
36076
|
+
// update the previous sync point according to the new time
|
|
36077
|
+
lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
|
|
36078
|
+
syncPoints.push(backingTrackSyncPoint);
|
|
36038
36079
|
}
|
|
36039
36080
|
}
|
|
36040
36081
|
finish(controller.currentTick);
|
|
36082
|
+
return playContext;
|
|
36083
|
+
}
|
|
36084
|
+
static processBarTime(bar, occurence, context) {
|
|
36085
|
+
const duration = bar.calculateDuration();
|
|
36086
|
+
const barSyncPoints = bar.syncPoints;
|
|
36087
|
+
const barStartTick = context.synthTick;
|
|
36088
|
+
if (barSyncPoints) {
|
|
36089
|
+
MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
|
|
36090
|
+
}
|
|
36091
|
+
else {
|
|
36092
|
+
MidiFileGenerator.processBarTimeNoSyncPoints(bar, context);
|
|
36093
|
+
}
|
|
36094
|
+
// don't forget the part after the last tempo change
|
|
36095
|
+
const endTick = barStartTick + duration;
|
|
36096
|
+
const tickOffset = endTick - context.synthTick;
|
|
36097
|
+
if (tickOffset > 0) {
|
|
36098
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36099
|
+
context.synthTick = endTick;
|
|
36100
|
+
}
|
|
36101
|
+
}
|
|
36102
|
+
static processBarTimeWithSyncPoints(bar, occurence, context) {
|
|
36103
|
+
const barStartTick = context.synthTick;
|
|
36104
|
+
const duration = bar.calculateDuration();
|
|
36105
|
+
let tempoChangeIndex = 0;
|
|
36106
|
+
let tickOffset;
|
|
36107
|
+
for (const syncPoint of bar.syncPoints) {
|
|
36108
|
+
if (syncPoint.syncPointValue.barOccurence !== occurence) {
|
|
36109
|
+
continue;
|
|
36110
|
+
}
|
|
36111
|
+
const syncPointTick = barStartTick + syncPoint.ratioPosition * duration;
|
|
36112
|
+
// first process all tempo changes until this sync point
|
|
36113
|
+
while (tempoChangeIndex < bar.tempoAutomations.length &&
|
|
36114
|
+
bar.tempoAutomations[tempoChangeIndex].ratioPosition <= syncPoint.ratioPosition) {
|
|
36115
|
+
const tempoChange = bar.tempoAutomations[tempoChangeIndex];
|
|
36116
|
+
const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
|
|
36117
|
+
tickOffset = absoluteTick - context.synthTick;
|
|
36118
|
+
if (tickOffset > 0) {
|
|
36119
|
+
context.synthTick = absoluteTick;
|
|
36120
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36121
|
+
}
|
|
36122
|
+
context.currentTempo = tempoChange.value;
|
|
36123
|
+
tempoChangeIndex++;
|
|
36124
|
+
}
|
|
36125
|
+
// process time until sync point
|
|
36126
|
+
tickOffset = syncPointTick - context.synthTick;
|
|
36127
|
+
if (tickOffset > 0) {
|
|
36128
|
+
context.synthTick = syncPointTick;
|
|
36129
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36130
|
+
}
|
|
36131
|
+
// update the previous sync point according to the new time
|
|
36132
|
+
if (context.syncPoints.length > 0) {
|
|
36133
|
+
context.syncPoints[context.syncPoints.length - 1].updateSyncBpm(context.synthTime, syncPoint.syncPointValue.millisecondOffset);
|
|
36134
|
+
}
|
|
36135
|
+
// create the new sync point
|
|
36136
|
+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
|
|
36137
|
+
backingTrackSyncPoint.masterBarIndex = bar.index;
|
|
36138
|
+
backingTrackSyncPoint.masterBarOccurence = occurence;
|
|
36139
|
+
backingTrackSyncPoint.synthTick = syncPointTick;
|
|
36140
|
+
backingTrackSyncPoint.synthBpm = context.currentTempo;
|
|
36141
|
+
backingTrackSyncPoint.synthTime = context.synthTime;
|
|
36142
|
+
backingTrackSyncPoint.syncTime = syncPoint.syncPointValue.millisecondOffset;
|
|
36143
|
+
backingTrackSyncPoint.syncBpm = 0 /* calculated by next sync point */;
|
|
36144
|
+
context.syncPoints.push(backingTrackSyncPoint);
|
|
36145
|
+
context.automationToSyncPoint.set(syncPoint, backingTrackSyncPoint);
|
|
36146
|
+
}
|
|
36147
|
+
// process remaining tempo changes after all sync points
|
|
36148
|
+
while (tempoChangeIndex < bar.tempoAutomations.length) {
|
|
36149
|
+
const tempoChange = bar.tempoAutomations[tempoChangeIndex];
|
|
36150
|
+
const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
|
|
36151
|
+
tickOffset = absoluteTick - context.synthTick;
|
|
36152
|
+
if (tickOffset > 0) {
|
|
36153
|
+
context.synthTick = absoluteTick;
|
|
36154
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36155
|
+
}
|
|
36156
|
+
context.currentTempo = tempoChange.value;
|
|
36157
|
+
tempoChangeIndex++;
|
|
36158
|
+
}
|
|
36159
|
+
}
|
|
36160
|
+
static processBarTimeNoSyncPoints(bar, context) {
|
|
36161
|
+
// walk through the tempo changes
|
|
36162
|
+
const barStartTick = context.synthTick;
|
|
36163
|
+
const duration = bar.calculateDuration();
|
|
36164
|
+
for (const changes of bar.tempoAutomations) {
|
|
36165
|
+
const absoluteTick = barStartTick + changes.ratioPosition * duration;
|
|
36166
|
+
const tickOffset = absoluteTick - context.synthTick;
|
|
36167
|
+
if (tickOffset > 0) {
|
|
36168
|
+
context.synthTick = absoluteTick;
|
|
36169
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36170
|
+
}
|
|
36171
|
+
context.currentTempo = changes.value;
|
|
36172
|
+
}
|
|
36041
36173
|
}
|
|
36042
36174
|
static toChannelShort(data) {
|
|
36043
36175
|
const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
|
|
@@ -61000,9 +61132,9 @@ class VersionInfo {
|
|
|
61000
61132
|
print(`build date: ${VersionInfo.date}`);
|
|
61001
61133
|
}
|
|
61002
61134
|
}
|
|
61003
|
-
VersionInfo.version = '1.6.0-alpha.
|
|
61004
|
-
VersionInfo.date = '2025-05-
|
|
61005
|
-
VersionInfo.commit = '
|
|
61135
|
+
VersionInfo.version = '1.6.0-alpha.1430';
|
|
61136
|
+
VersionInfo.date = '2025-05-29T22:21:21.689Z';
|
|
61137
|
+
VersionInfo.commit = '98a4c2bec8d71f2645008118d3b77fb40973e7fe';
|
|
61006
61138
|
|
|
61007
61139
|
/**
|
|
61008
61140
|
* A factory for custom layout engines.
|
|
@@ -62652,6 +62784,7 @@ class GpifWriter {
|
|
|
62652
62784
|
: undefined;
|
|
62653
62785
|
const millisecondPadding = initialSyncPoint ? initialSyncPoint.syncPointValue.millisecondOffset : 0;
|
|
62654
62786
|
this.backingTrackFramePadding = (-1 * ((millisecondPadding / 1000) * GpifWriter.SampleRate)) | 0;
|
|
62787
|
+
const modifiedTempoLookup = new Lazy(() => MidiFileGenerator.buildModifiedTempoLookup(score));
|
|
62655
62788
|
for (const mb of score.masterBars) {
|
|
62656
62789
|
for (const automation of mb.tempoAutomations) {
|
|
62657
62790
|
const tempoAutomation = automations.addElement('Automation');
|
|
@@ -62676,7 +62809,7 @@ class GpifWriter {
|
|
|
62676
62809
|
const value = syncPointAutomation.addElement('Value');
|
|
62677
62810
|
value.addElement('BarIndex').innerText = mb.index.toString();
|
|
62678
62811
|
value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
|
|
62679
|
-
value.addElement('ModifiedTempo').innerText = syncPoint.
|
|
62812
|
+
value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
|
|
62680
62813
|
value.addElement('OriginalTempo').innerText = score.tempo.toString();
|
|
62681
62814
|
const frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
|
|
62682
62815
|
GpifWriter.SampleRate) |
|
package/dist/alphaTab.d.ts
CHANGED
|
@@ -3092,17 +3092,48 @@ declare class BackingTrack {
|
|
|
3092
3092
|
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
3093
3093
|
*/
|
|
3094
3094
|
declare class BackingTrackSyncPoint {
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3095
|
+
/**
|
|
3096
|
+
* The index of the masterbar to which this sync point belongs to.
|
|
3097
|
+
* @remarks
|
|
3098
|
+
* This property is purely informative for external use like in editors.
|
|
3099
|
+
* It has no impact to the synchronization itself.
|
|
3100
|
+
*/
|
|
3101
|
+
masterBarIndex: number;
|
|
3102
|
+
/**
|
|
3103
|
+
* The occurence of the masterbar to which this sync point belongs to. The occurence
|
|
3104
|
+
* is 0-based and increases with every repeated play of a masterbar (e.g. on repeats or jumps).
|
|
3105
|
+
* @remarks
|
|
3106
|
+
* This property is purely informative for external use like in editors.
|
|
3107
|
+
* It has no impact to the synchronization itself.
|
|
3108
|
+
*/
|
|
3109
|
+
masterBarOccurence: number;
|
|
3110
|
+
/**
|
|
3111
|
+
* The BPM the synthesizer has at the exact tick position of this sync point.
|
|
3112
|
+
*/
|
|
3113
|
+
synthBpm: number;
|
|
3114
|
+
/**
|
|
3115
|
+
* The millisecond time position of the synthesizer when this sync point is reached.
|
|
3116
|
+
*/
|
|
3117
|
+
synthTime: number;
|
|
3118
|
+
/**
|
|
3119
|
+
* The midi tick position of the synthesizer when this sync point is reached.
|
|
3120
|
+
*/
|
|
3121
|
+
synthTick: number;
|
|
3122
|
+
/**
|
|
3123
|
+
* The millisecond time in the external media marking the synchronization point.
|
|
3124
|
+
*/
|
|
3125
|
+
syncTime: number;
|
|
3126
|
+
/**
|
|
3127
|
+
* The BPM the song will have virtually after this sync point to align the external media time axis
|
|
3128
|
+
* with the one from the synthesizer.
|
|
3129
|
+
*/
|
|
3130
|
+
syncBpm: number;
|
|
3131
|
+
/**
|
|
3132
|
+
* Updates the synchronization BPM that will apply after this sync point.
|
|
3133
|
+
* @param nextSyncPointSynthTime The synthesizer time of the next sync point after this one.
|
|
3134
|
+
* @param nextSyncPointSyncTime The synchronization time of the next sync point after this one.
|
|
3135
|
+
*/
|
|
3136
|
+
updateSyncBpm(nextSyncPointSynthTime: number, nextSyncPointSyncTime: number): void;
|
|
3106
3137
|
}
|
|
3107
3138
|
|
|
3108
3139
|
/**
|
|
@@ -6492,13 +6523,7 @@ declare interface FlatSyncPoint {
|
|
|
6492
6523
|
*/
|
|
6493
6524
|
barOccurence: number;
|
|
6494
6525
|
/**
|
|
6495
|
-
* The
|
|
6496
|
-
* This information is used together with normal tempo changes to calculate how much faster/slower the
|
|
6497
|
-
* cursor playback is performed to align with the audio track.
|
|
6498
|
-
*/
|
|
6499
|
-
modifiedTempo: number;
|
|
6500
|
-
/**
|
|
6501
|
-
* The uadio offset marking the position within the audio track in milliseconds.
|
|
6526
|
+
* The audio offset marking the position within the audio track in milliseconds.
|
|
6502
6527
|
* This information is used to regularly sync (or on seeking) to match a given external audio time axis with the internal time axis.
|
|
6503
6528
|
*/
|
|
6504
6529
|
millisecondOffset: number;
|
|
@@ -9271,7 +9296,11 @@ declare class MidiFileGenerator {
|
|
|
9271
9296
|
* @returns The generated sync points for usage in the backing track playback.
|
|
9272
9297
|
*/
|
|
9273
9298
|
static generateSyncPoints(score: Score): BackingTrackSyncPoint[];
|
|
9299
|
+
/* Excluded from this release type: buildModifiedTempoLookup */
|
|
9274
9300
|
private static playThroughSong;
|
|
9301
|
+
private static processBarTime;
|
|
9302
|
+
private static processBarTimeWithSyncPoints;
|
|
9303
|
+
private static processBarTimeNoSyncPoints;
|
|
9275
9304
|
private static toChannelShort;
|
|
9276
9305
|
private generateMasterBar;
|
|
9277
9306
|
private generateBar;
|
|
@@ -9415,7 +9444,7 @@ declare class MidiFileSequencerTempoChange {
|
|
|
9415
9444
|
declare class MidiSequencerState {
|
|
9416
9445
|
tempoChanges: MidiFileSequencerTempoChange[];
|
|
9417
9446
|
tempoChangeIndex: number;
|
|
9418
|
-
syncPoints:
|
|
9447
|
+
syncPoints: BackingTrackSyncPoint[];
|
|
9419
9448
|
firstProgramEventPerChannel: Map<number, SynthEvent>;
|
|
9420
9449
|
firstTimeSignatureNumerator: number;
|
|
9421
9450
|
firstTimeSignatureDenominator: number;
|
|
@@ -13656,12 +13685,6 @@ declare class SyncPointData {
|
|
|
13656
13685
|
* Indicates for which repeat occurence this sync point is valid (e.g. 0 on the first time played, 1 on the second time played)
|
|
13657
13686
|
*/
|
|
13658
13687
|
barOccurence: number;
|
|
13659
|
-
/**
|
|
13660
|
-
* The modified tempo at which the cursor should move (aka. the tempo played within the external audio track).
|
|
13661
|
-
* This information is used together with normal tempo changes to calculate how much faster/slower the
|
|
13662
|
-
* cursor playback is performed to align with the audio track.
|
|
13663
|
-
*/
|
|
13664
|
-
modifiedTempo: number;
|
|
13665
13688
|
/**
|
|
13666
13689
|
* The audio offset marking the position within the audio track in milliseconds.
|
|
13667
13690
|
* This information is used to regularly sync (or on seeking) to match a given external audio time axis with the internal time axis.
|