@coderline/alphatab 1.6.0-alpha.1426 → 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 +377 -106
- package/dist/alphaTab.d.ts +134 -21
- package/dist/alphaTab.js +377 -106
- 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.js
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
|
*
|
|
@@ -1418,13 +1418,7 @@
|
|
|
1418
1418
|
*/
|
|
1419
1419
|
this.barOccurence = 0;
|
|
1420
1420
|
/**
|
|
1421
|
-
* The
|
|
1422
|
-
* This information is used together with normal tempo changes to calculate how much faster/slower the
|
|
1423
|
-
* cursor playback is performed to align with the audio track.
|
|
1424
|
-
*/
|
|
1425
|
-
this.modifiedTempo = 0;
|
|
1426
|
-
/**
|
|
1427
|
-
* The uadio offset marking the position within the audio track in milliseconds.
|
|
1421
|
+
* The audio offset marking the position within the audio track in milliseconds.
|
|
1428
1422
|
* This information is used to regularly sync (or on seeking) to match a given external audio time axis with the internal time axis.
|
|
1429
1423
|
*/
|
|
1430
1424
|
this.millisecondOffset = 0;
|
|
@@ -5849,7 +5843,6 @@
|
|
|
5849
5843
|
static clone(original) {
|
|
5850
5844
|
const clone = new SyncPointData();
|
|
5851
5845
|
clone.barOccurence = original.barOccurence;
|
|
5852
|
-
clone.modifiedTempo = original.modifiedTempo;
|
|
5853
5846
|
clone.millisecondOffset = original.millisecondOffset;
|
|
5854
5847
|
return clone;
|
|
5855
5848
|
}
|
|
@@ -7305,6 +7298,59 @@
|
|
|
7305
7298
|
this.tracks[i].finish(settings, sharedDataBag);
|
|
7306
7299
|
}
|
|
7307
7300
|
}
|
|
7301
|
+
/**
|
|
7302
|
+
* Applies the given list of {@link FlatSyncPoint} to this song.
|
|
7303
|
+
* @param syncPoints The list of sync points to apply.
|
|
7304
|
+
* @since 1.6.0
|
|
7305
|
+
*/
|
|
7306
|
+
applyFlatSyncPoints(syncPoints) {
|
|
7307
|
+
for (const b of this.masterBars) {
|
|
7308
|
+
b.syncPoints = undefined;
|
|
7309
|
+
}
|
|
7310
|
+
for (const syncPoint of syncPoints) {
|
|
7311
|
+
const automation = new Automation();
|
|
7312
|
+
automation.ratioPosition = Math.min(1, Math.max(0, syncPoint.barPosition));
|
|
7313
|
+
automation.type = AutomationType.SyncPoint;
|
|
7314
|
+
automation.syncPointValue = new SyncPointData();
|
|
7315
|
+
automation.syncPointValue.millisecondOffset = syncPoint.millisecondOffset;
|
|
7316
|
+
automation.syncPointValue.barOccurence = syncPoint.barOccurence;
|
|
7317
|
+
if (syncPoint.barIndex < this.masterBars.length) {
|
|
7318
|
+
this.masterBars[syncPoint.barIndex].addSyncPoint(automation);
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
for (const b of this.masterBars) {
|
|
7322
|
+
if (b.syncPoints) {
|
|
7323
|
+
b.syncPoints.sort((a, b) => {
|
|
7324
|
+
const occurence = a.syncPointValue.barOccurence - b.syncPointValue.barOccurence;
|
|
7325
|
+
if (occurence !== 0) {
|
|
7326
|
+
return occurence;
|
|
7327
|
+
}
|
|
7328
|
+
return a.ratioPosition - b.ratioPosition;
|
|
7329
|
+
});
|
|
7330
|
+
}
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
/**
|
|
7334
|
+
* Exports all sync points in this song to a {@link FlatSyncPoint} list.
|
|
7335
|
+
* @since 1.6.0
|
|
7336
|
+
*/
|
|
7337
|
+
exportFlatSyncPoints() {
|
|
7338
|
+
const syncPoints = [];
|
|
7339
|
+
for (const masterBar of this.masterBars) {
|
|
7340
|
+
const masterBarSyncPoints = masterBar.syncPoints;
|
|
7341
|
+
if (masterBarSyncPoints) {
|
|
7342
|
+
for (const syncPoint of masterBarSyncPoints) {
|
|
7343
|
+
syncPoints.push({
|
|
7344
|
+
barIndex: masterBar.index,
|
|
7345
|
+
barOccurence: syncPoint.syncPointValue.barOccurence,
|
|
7346
|
+
barPosition: syncPoint.ratioPosition,
|
|
7347
|
+
millisecondOffset: syncPoint.syncPointValue.millisecondOffset
|
|
7348
|
+
});
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
return syncPoints;
|
|
7353
|
+
}
|
|
7308
7354
|
}
|
|
7309
7355
|
|
|
7310
7356
|
/**
|
|
@@ -13613,6 +13659,7 @@
|
|
|
13613
13659
|
XmlNodeType[XmlNodeType["CDATA"] = 3] = "CDATA";
|
|
13614
13660
|
XmlNodeType[XmlNodeType["Document"] = 4] = "Document";
|
|
13615
13661
|
XmlNodeType[XmlNodeType["DocumentType"] = 5] = "DocumentType";
|
|
13662
|
+
XmlNodeType[XmlNodeType["Comment"] = 6] = "Comment";
|
|
13616
13663
|
})(XmlNodeType || (XmlNodeType = {}));
|
|
13617
13664
|
class XmlNode {
|
|
13618
13665
|
constructor() {
|
|
@@ -14189,7 +14236,7 @@
|
|
|
14189
14236
|
this.indent();
|
|
14190
14237
|
for (const child of xml.childNodes) {
|
|
14191
14238
|
// skip text nodes in case of multiple children
|
|
14192
|
-
if (child.nodeType === XmlNodeType.Element) {
|
|
14239
|
+
if (child.nodeType === XmlNodeType.Element || child.nodeType === XmlNodeType.Comment) {
|
|
14193
14240
|
this.writeNode(child);
|
|
14194
14241
|
}
|
|
14195
14242
|
}
|
|
@@ -14220,6 +14267,9 @@
|
|
|
14220
14267
|
case XmlNodeType.DocumentType:
|
|
14221
14268
|
this.write(`<!DOCTYPE ${xml.value}>`);
|
|
14222
14269
|
break;
|
|
14270
|
+
case XmlNodeType.Comment:
|
|
14271
|
+
this.write(`<!-- ${xml.value} -->`);
|
|
14272
|
+
break;
|
|
14223
14273
|
}
|
|
14224
14274
|
}
|
|
14225
14275
|
unindend() {
|
|
@@ -14661,9 +14711,6 @@
|
|
|
14661
14711
|
case 'BarOccurrence':
|
|
14662
14712
|
syncPointValue.barOccurence = GpifParser.parseIntSafe(vc.innerText, 0);
|
|
14663
14713
|
break;
|
|
14664
|
-
case 'ModifiedTempo':
|
|
14665
|
-
syncPointValue.modifiedTempo = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14666
|
-
break;
|
|
14667
14714
|
case 'FrameOffset':
|
|
14668
14715
|
const frameOffset = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14669
14716
|
syncPointValue.millisecondOffset = (frameOffset / GpifParser.SampleRate) * 1000;
|
|
@@ -16816,7 +16863,7 @@
|
|
|
16816
16863
|
* Internal Range: 1 per quarter note
|
|
16817
16864
|
*/
|
|
16818
16865
|
GpifParser.BendPointValueFactor = 1 / 25.0;
|
|
16819
|
-
//
|
|
16866
|
+
// tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
|
|
16820
16867
|
// they are NOT using the sample rate of the input file.
|
|
16821
16868
|
// Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
|
|
16822
16869
|
GpifParser.SampleRate = 44100;
|
|
@@ -22286,6 +22333,61 @@
|
|
|
22286
22333
|
}
|
|
22287
22334
|
}
|
|
22288
22335
|
|
|
22336
|
+
/**
|
|
22337
|
+
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
22338
|
+
*/
|
|
22339
|
+
class BackingTrackSyncPoint {
|
|
22340
|
+
constructor() {
|
|
22341
|
+
/**
|
|
22342
|
+
* The index of the masterbar to which this sync point belongs to.
|
|
22343
|
+
* @remarks
|
|
22344
|
+
* This property is purely informative for external use like in editors.
|
|
22345
|
+
* It has no impact to the synchronization itself.
|
|
22346
|
+
*/
|
|
22347
|
+
this.masterBarIndex = 0;
|
|
22348
|
+
/**
|
|
22349
|
+
* The occurence of the masterbar to which this sync point belongs to. The occurence
|
|
22350
|
+
* is 0-based and increases with every repeated play of a masterbar (e.g. on repeats or jumps).
|
|
22351
|
+
* @remarks
|
|
22352
|
+
* This property is purely informative for external use like in editors.
|
|
22353
|
+
* It has no impact to the synchronization itself.
|
|
22354
|
+
*/
|
|
22355
|
+
this.masterBarOccurence = 0;
|
|
22356
|
+
/**
|
|
22357
|
+
* The BPM the synthesizer has at the exact tick position of this sync point.
|
|
22358
|
+
*/
|
|
22359
|
+
this.synthBpm = 0;
|
|
22360
|
+
/**
|
|
22361
|
+
* The millisecond time position of the synthesizer when this sync point is reached.
|
|
22362
|
+
*/
|
|
22363
|
+
this.synthTime = 0;
|
|
22364
|
+
/**
|
|
22365
|
+
* The midi tick position of the synthesizer when this sync point is reached.
|
|
22366
|
+
*/
|
|
22367
|
+
this.synthTick = 0;
|
|
22368
|
+
/**
|
|
22369
|
+
* The millisecond time in the external media marking the synchronization point.
|
|
22370
|
+
*/
|
|
22371
|
+
this.syncTime = 0;
|
|
22372
|
+
/**
|
|
22373
|
+
* The BPM the song will have virtually after this sync point to align the external media time axis
|
|
22374
|
+
* with the one from the synthesizer.
|
|
22375
|
+
*/
|
|
22376
|
+
this.syncBpm = 0;
|
|
22377
|
+
}
|
|
22378
|
+
/**
|
|
22379
|
+
* Updates the synchronization BPM that will apply after this sync point.
|
|
22380
|
+
* @param nextSyncPointSynthTime The synthesizer time of the next sync point after this one.
|
|
22381
|
+
* @param nextSyncPointSyncTime The synchronization time of the next sync point after this one.
|
|
22382
|
+
*/
|
|
22383
|
+
updateSyncBpm(nextSyncPointSynthTime, nextSyncPointSyncTime) {
|
|
22384
|
+
const synthDuration = nextSyncPointSynthTime - this.synthTime;
|
|
22385
|
+
const syncedDuration = nextSyncPointSyncTime - this.syncTime;
|
|
22386
|
+
const modifiedTempo = (synthDuration / syncedDuration) * this.synthBpm;
|
|
22387
|
+
this.syncBpm = modifiedTempo;
|
|
22388
|
+
}
|
|
22389
|
+
}
|
|
22390
|
+
|
|
22289
22391
|
class MidiFileSequencerTempoChange {
|
|
22290
22392
|
constructor(bpm, ticks, time) {
|
|
22291
22393
|
this.bpm = bpm;
|
|
@@ -22293,14 +22395,6 @@
|
|
|
22293
22395
|
this.time = time;
|
|
22294
22396
|
}
|
|
22295
22397
|
}
|
|
22296
|
-
class BackingTrackSyncPointWithTime {
|
|
22297
|
-
constructor(tick, time, modifiedTempo, millisecondOffset) {
|
|
22298
|
-
this.alphaTabTick = tick;
|
|
22299
|
-
this.alphaTabTime = time;
|
|
22300
|
-
this.modifiedTempo = modifiedTempo;
|
|
22301
|
-
this.millisecondOffset = millisecondOffset;
|
|
22302
|
-
}
|
|
22303
|
-
}
|
|
22304
22398
|
class MidiSequencerState {
|
|
22305
22399
|
constructor() {
|
|
22306
22400
|
this.tempoChanges = [];
|
|
@@ -22404,7 +22498,7 @@
|
|
|
22404
22498
|
this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
|
|
22405
22499
|
this._mainState.modifiedTempo =
|
|
22406
22500
|
this._mainState.syncPoints.length > 0
|
|
22407
|
-
? this._mainState.syncPoints[0].
|
|
22501
|
+
? this._mainState.syncPoints[0].syncBpm
|
|
22408
22502
|
: this._mainState.currentTempo;
|
|
22409
22503
|
if (this.isPlayingMain) {
|
|
22410
22504
|
const metronomeVolume = this._synthesizer.metronomeVolume;
|
|
@@ -22575,7 +22669,7 @@
|
|
|
22575
22669
|
}
|
|
22576
22670
|
mainUpdateSyncPoints(syncPoints) {
|
|
22577
22671
|
const state = this._mainState;
|
|
22578
|
-
syncPoints.sort((a, b) => a.
|
|
22672
|
+
syncPoints.sort((a, b) => a.synthTick - b.synthTick); // just in case
|
|
22579
22673
|
state.syncPoints = [];
|
|
22580
22674
|
if (syncPoints.length >= 0) {
|
|
22581
22675
|
let bpm = 120;
|
|
@@ -22585,6 +22679,8 @@
|
|
|
22585
22679
|
for (let i = 0; i < syncPoints.length; i++) {
|
|
22586
22680
|
const p = syncPoints[i];
|
|
22587
22681
|
let deltaTick = 0;
|
|
22682
|
+
// TODO: merge interpolation into MidiFileGenerator where we already play through
|
|
22683
|
+
// the time axis.
|
|
22588
22684
|
// remember state from previous sync point (or start). to handle linear interpolation
|
|
22589
22685
|
let previousModifiedTempo;
|
|
22590
22686
|
let previousMillisecondOffset;
|
|
@@ -22596,9 +22692,9 @@
|
|
|
22596
22692
|
}
|
|
22597
22693
|
else {
|
|
22598
22694
|
const previousSyncPoint = syncPoints[i - 1];
|
|
22599
|
-
previousModifiedTempo = previousSyncPoint.
|
|
22600
|
-
previousMillisecondOffset = previousSyncPoint.
|
|
22601
|
-
previousTick = previousSyncPoint.
|
|
22695
|
+
previousModifiedTempo = previousSyncPoint.syncBpm;
|
|
22696
|
+
previousMillisecondOffset = previousSyncPoint.syncTime;
|
|
22697
|
+
previousTick = previousSyncPoint.synthTick;
|
|
22602
22698
|
}
|
|
22603
22699
|
// process time until sync point
|
|
22604
22700
|
// here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
|
|
@@ -22606,25 +22702,31 @@
|
|
|
22606
22702
|
// otherwise the linear interpolation later in the lookup will fail.
|
|
22607
22703
|
// 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
|
|
22608
22704
|
while (tempoChangeIndex < state.tempoChanges.length &&
|
|
22609
|
-
state.tempoChanges[tempoChangeIndex].ticks <= p.
|
|
22705
|
+
state.tempoChanges[tempoChangeIndex].ticks <= p.synthTick) {
|
|
22610
22706
|
deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
|
|
22611
22707
|
if (deltaTick > 0) {
|
|
22612
22708
|
absTick += deltaTick;
|
|
22613
22709
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22614
|
-
const millisPerTick = (p.
|
|
22710
|
+
const millisPerTick = (p.syncTime - previousMillisecondOffset) / (p.synthTick - previousTick);
|
|
22615
22711
|
const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
|
|
22616
|
-
|
|
22712
|
+
const syncPoint = new BackingTrackSyncPoint();
|
|
22713
|
+
syncPoint.synthTick = absTick;
|
|
22714
|
+
syncPoint.synthBpm = bpm;
|
|
22715
|
+
syncPoint.synthTime = absTime;
|
|
22716
|
+
syncPoint.syncTime = interpolatedMillisecondOffset;
|
|
22717
|
+
syncPoint.syncBpm = previousModifiedTempo;
|
|
22617
22718
|
}
|
|
22618
22719
|
bpm = state.tempoChanges[tempoChangeIndex].bpm;
|
|
22619
22720
|
tempoChangeIndex++;
|
|
22620
22721
|
}
|
|
22621
|
-
deltaTick = p.
|
|
22722
|
+
deltaTick = p.synthTick - absTick;
|
|
22622
22723
|
absTick += deltaTick;
|
|
22623
22724
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22624
|
-
state.syncPoints.push(
|
|
22725
|
+
state.syncPoints.push(p);
|
|
22625
22726
|
}
|
|
22626
22727
|
}
|
|
22627
22728
|
state.syncPointIndex = 0;
|
|
22729
|
+
state.modifiedTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
|
|
22628
22730
|
}
|
|
22629
22731
|
currentTimePositionToTickPosition(timePosition) {
|
|
22630
22732
|
const state = this._currentState;
|
|
@@ -22657,16 +22759,15 @@
|
|
|
22657
22759
|
const syncPoints = state.syncPoints;
|
|
22658
22760
|
if (syncPoints.length > 0) {
|
|
22659
22761
|
let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
|
|
22660
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22762
|
+
if (timePosition < syncPoints[syncPointIndex].syncTime) {
|
|
22661
22763
|
syncPointIndex = 0;
|
|
22662
22764
|
}
|
|
22663
|
-
while (syncPointIndex + 1 < syncPoints.length &&
|
|
22664
|
-
syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
|
|
22765
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].syncTime <= timePosition) {
|
|
22665
22766
|
syncPointIndex++;
|
|
22666
22767
|
}
|
|
22667
22768
|
if (syncPointIndex !== state.syncPointIndex) {
|
|
22668
22769
|
state.syncPointIndex = syncPointIndex;
|
|
22669
|
-
state.modifiedTempo = syncPoints[syncPointIndex].
|
|
22770
|
+
state.modifiedTempo = syncPoints[syncPointIndex].syncBpm;
|
|
22670
22771
|
}
|
|
22671
22772
|
}
|
|
22672
22773
|
else {
|
|
@@ -22682,18 +22783,18 @@
|
|
|
22682
22783
|
this.updateSyncPoints(this._mainState, timePosition);
|
|
22683
22784
|
const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22684
22785
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22685
|
-
const timeDiff = timePosition - currentSyncPoint.
|
|
22786
|
+
const timeDiff = timePosition - currentSyncPoint.syncTime;
|
|
22686
22787
|
let alphaTabTimeDiff;
|
|
22687
22788
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22688
22789
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22689
|
-
const relativeTimeDiff = timeDiff / (nextSyncPoint.
|
|
22690
|
-
alphaTabTimeDiff = (nextSyncPoint.
|
|
22790
|
+
const relativeTimeDiff = timeDiff / (nextSyncPoint.syncTime - currentSyncPoint.syncTime);
|
|
22791
|
+
alphaTabTimeDiff = (nextSyncPoint.synthTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22691
22792
|
}
|
|
22692
22793
|
else {
|
|
22693
|
-
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.
|
|
22694
|
-
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.
|
|
22794
|
+
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.syncTime);
|
|
22795
|
+
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22695
22796
|
}
|
|
22696
|
-
return (currentSyncPoint.
|
|
22797
|
+
return (currentSyncPoint.synthTime + alphaTabTimeDiff) / this.playbackSpeed;
|
|
22697
22798
|
}
|
|
22698
22799
|
mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
|
|
22699
22800
|
const mainState = this._mainState;
|
|
@@ -22703,27 +22804,27 @@
|
|
|
22703
22804
|
}
|
|
22704
22805
|
timePosition *= this.playbackSpeed;
|
|
22705
22806
|
let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22706
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22807
|
+
if (timePosition < syncPoints[syncPointIndex].synthTime) {
|
|
22707
22808
|
syncPointIndex = 0;
|
|
22708
22809
|
}
|
|
22709
|
-
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].
|
|
22810
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].synthTime <= timePosition) {
|
|
22710
22811
|
syncPointIndex++;
|
|
22711
22812
|
}
|
|
22712
22813
|
// NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
|
|
22713
22814
|
// we ensure that we have a linear increase between two points
|
|
22714
22815
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22715
|
-
const alphaTabTimeDiff = timePosition - currentSyncPoint.
|
|
22816
|
+
const alphaTabTimeDiff = timePosition - currentSyncPoint.synthTime;
|
|
22716
22817
|
let backingTrackPos;
|
|
22717
22818
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22718
22819
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22719
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.
|
|
22720
|
-
const backingTrackDiff = nextSyncPoint.
|
|
22721
|
-
backingTrackPos = currentSyncPoint.
|
|
22820
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.synthTime - currentSyncPoint.synthTime);
|
|
22821
|
+
const backingTrackDiff = nextSyncPoint.syncTime - currentSyncPoint.syncTime;
|
|
22822
|
+
backingTrackPos = currentSyncPoint.syncTime + backingTrackDiff * relativeAlphaTabTimeDiff;
|
|
22722
22823
|
}
|
|
22723
22824
|
else {
|
|
22724
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.
|
|
22725
|
-
const frameDiff = backingTrackLength - currentSyncPoint.
|
|
22726
|
-
backingTrackPos = currentSyncPoint.
|
|
22825
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.synthTime);
|
|
22826
|
+
const frameDiff = backingTrackLength - currentSyncPoint.syncTime;
|
|
22827
|
+
backingTrackPos = currentSyncPoint.syncTime + frameDiff * relativeAlphaTabTimeDiff;
|
|
22727
22828
|
}
|
|
22728
22829
|
return backingTrackPos;
|
|
22729
22830
|
}
|
|
@@ -30995,7 +31096,6 @@
|
|
|
30995
31096
|
}
|
|
30996
31097
|
const o = new Map();
|
|
30997
31098
|
o.set("baroccurence", obj.barOccurence);
|
|
30998
|
-
o.set("modifiedtempo", obj.modifiedTempo);
|
|
30999
31099
|
o.set("millisecondoffset", obj.millisecondOffset);
|
|
31000
31100
|
return o;
|
|
31001
31101
|
}
|
|
@@ -31004,9 +31104,6 @@
|
|
|
31004
31104
|
case "baroccurence":
|
|
31005
31105
|
obj.barOccurence = v;
|
|
31006
31106
|
return true;
|
|
31007
|
-
case "modifiedtempo":
|
|
31008
|
-
obj.modifiedTempo = v;
|
|
31009
|
-
return true;
|
|
31010
31107
|
case "millisecondoffset":
|
|
31011
31108
|
obj.millisecondOffset = v;
|
|
31012
31109
|
return true;
|
|
@@ -35769,17 +35866,6 @@
|
|
|
35769
35866
|
}
|
|
35770
35867
|
}
|
|
35771
35868
|
|
|
35772
|
-
/**
|
|
35773
|
-
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
35774
|
-
*/
|
|
35775
|
-
class BackingTrackSyncPoint {
|
|
35776
|
-
constructor(tick, data) {
|
|
35777
|
-
this.tick = 0;
|
|
35778
|
-
this.tick = tick;
|
|
35779
|
-
this.data = data;
|
|
35780
|
-
}
|
|
35781
|
-
}
|
|
35782
|
-
|
|
35783
35869
|
class MidiNoteDuration {
|
|
35784
35870
|
constructor() {
|
|
35785
35871
|
this.noteOnly = 0;
|
|
@@ -35800,6 +35886,14 @@
|
|
|
35800
35886
|
this.brushInfos = [];
|
|
35801
35887
|
}
|
|
35802
35888
|
}
|
|
35889
|
+
class PlayThroughContext {
|
|
35890
|
+
constructor() {
|
|
35891
|
+
this.synthTick = 0;
|
|
35892
|
+
this.synthTime = 0;
|
|
35893
|
+
this.currentTempo = 0;
|
|
35894
|
+
this.automationToSyncPoint = new Map();
|
|
35895
|
+
}
|
|
35896
|
+
}
|
|
35803
35897
|
/**
|
|
35804
35898
|
* This generator creates a midi file using a score.
|
|
35805
35899
|
*/
|
|
@@ -35926,9 +36020,22 @@
|
|
|
35926
36020
|
});
|
|
35927
36021
|
return syncPoints;
|
|
35928
36022
|
}
|
|
36023
|
+
/**
|
|
36024
|
+
* @internal
|
|
36025
|
+
*/
|
|
36026
|
+
static buildModifiedTempoLookup(score) {
|
|
36027
|
+
const syncPoints = [];
|
|
36028
|
+
const context = MidiFileGenerator.playThroughSong(score, syncPoints, (_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
|
|
36029
|
+
}, (_barIndex, _currentTick, _currentTempo) => {
|
|
36030
|
+
}, _endTick => {
|
|
36031
|
+
});
|
|
36032
|
+
return context.automationToSyncPoint;
|
|
36033
|
+
}
|
|
35929
36034
|
static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
|
|
35930
36035
|
const controller = new MidiPlaybackController(score);
|
|
35931
|
-
|
|
36036
|
+
const playContext = new PlayThroughContext();
|
|
36037
|
+
playContext.currentTempo = score.tempo;
|
|
36038
|
+
playContext.syncPoints = syncPoints;
|
|
35932
36039
|
let previousMasterBar = null;
|
|
35933
36040
|
// store the previous played bar for repeats
|
|
35934
36041
|
const barOccurence = new Map();
|
|
@@ -35941,23 +36048,11 @@
|
|
|
35941
36048
|
let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
|
|
35942
36049
|
occurence++;
|
|
35943
36050
|
barOccurence.set(index, occurence);
|
|
35944
|
-
generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
|
|
35945
|
-
const
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
|
|
35950
|
-
syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
|
|
35951
|
-
}
|
|
35952
|
-
}
|
|
35953
|
-
}
|
|
35954
|
-
if (bar.tempoAutomations.length > 0) {
|
|
35955
|
-
currentTempo = bar.tempoAutomations[0].value;
|
|
35956
|
-
}
|
|
35957
|
-
generateTracks(index, currentTick, currentTempo);
|
|
35958
|
-
if (bar.tempoAutomations.length > 0) {
|
|
35959
|
-
currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
|
|
35960
|
-
}
|
|
36051
|
+
generateMasterBar(bar, previousMasterBar, currentTick, playContext.currentTempo, occurence);
|
|
36052
|
+
const trackTempo = bar.tempoAutomations.length > 0 ? bar.tempoAutomations[0].value : playContext.currentTempo;
|
|
36053
|
+
generateTracks(index, currentTick, trackTempo);
|
|
36054
|
+
playContext.synthTick = currentTick;
|
|
36055
|
+
MidiFileGenerator.processBarTime(bar, occurence, playContext);
|
|
35961
36056
|
}
|
|
35962
36057
|
controller.moveNext();
|
|
35963
36058
|
previousMasterBar = bar;
|
|
@@ -35968,21 +36063,119 @@
|
|
|
35968
36063
|
// but where it ends according to the BPM and the remaining ticks.
|
|
35969
36064
|
if (syncPoints.length > 0) {
|
|
35970
36065
|
const lastSyncPoint = syncPoints[syncPoints.length - 1];
|
|
35971
|
-
const remainingTicks = controller.currentTick - lastSyncPoint.
|
|
36066
|
+
const remainingTicks = controller.currentTick - lastSyncPoint.synthTick;
|
|
35972
36067
|
if (remainingTicks > 0) {
|
|
35973
|
-
const
|
|
35974
|
-
|
|
35975
|
-
|
|
35976
|
-
|
|
35977
|
-
|
|
35978
|
-
//
|
|
35979
|
-
|
|
35980
|
-
|
|
35981
|
-
|
|
35982
|
-
|
|
36068
|
+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
|
|
36069
|
+
backingTrackSyncPoint.masterBarIndex = previousMasterBar.index;
|
|
36070
|
+
backingTrackSyncPoint.masterBarOccurence = barOccurence.get(previousMasterBar.index) - 1;
|
|
36071
|
+
backingTrackSyncPoint.synthTick = controller.currentTick;
|
|
36072
|
+
backingTrackSyncPoint.synthBpm = playContext.currentTempo;
|
|
36073
|
+
// we need to assume some BPM for the last interpolated point.
|
|
36074
|
+
// if we have more than just a start point, we keep the BPM before the last manual sync point
|
|
36075
|
+
// otherwise we have no customized sync BPM known and keep the synthesizer one.
|
|
36076
|
+
backingTrackSyncPoint.syncBpm =
|
|
36077
|
+
syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
|
|
36078
|
+
backingTrackSyncPoint.synthTime =
|
|
36079
|
+
lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
|
|
36080
|
+
backingTrackSyncPoint.syncTime =
|
|
36081
|
+
lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
|
|
36082
|
+
// update the previous sync point according to the new time
|
|
36083
|
+
lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
|
|
36084
|
+
syncPoints.push(backingTrackSyncPoint);
|
|
35983
36085
|
}
|
|
35984
36086
|
}
|
|
35985
36087
|
finish(controller.currentTick);
|
|
36088
|
+
return playContext;
|
|
36089
|
+
}
|
|
36090
|
+
static processBarTime(bar, occurence, context) {
|
|
36091
|
+
const duration = bar.calculateDuration();
|
|
36092
|
+
const barSyncPoints = bar.syncPoints;
|
|
36093
|
+
const barStartTick = context.synthTick;
|
|
36094
|
+
if (barSyncPoints) {
|
|
36095
|
+
MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
|
|
36096
|
+
}
|
|
36097
|
+
else {
|
|
36098
|
+
MidiFileGenerator.processBarTimeNoSyncPoints(bar, context);
|
|
36099
|
+
}
|
|
36100
|
+
// don't forget the part after the last tempo change
|
|
36101
|
+
const endTick = barStartTick + duration;
|
|
36102
|
+
const tickOffset = endTick - context.synthTick;
|
|
36103
|
+
if (tickOffset > 0) {
|
|
36104
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36105
|
+
context.synthTick = endTick;
|
|
36106
|
+
}
|
|
36107
|
+
}
|
|
36108
|
+
static processBarTimeWithSyncPoints(bar, occurence, context) {
|
|
36109
|
+
const barStartTick = context.synthTick;
|
|
36110
|
+
const duration = bar.calculateDuration();
|
|
36111
|
+
let tempoChangeIndex = 0;
|
|
36112
|
+
let tickOffset;
|
|
36113
|
+
for (const syncPoint of bar.syncPoints) {
|
|
36114
|
+
if (syncPoint.syncPointValue.barOccurence !== occurence) {
|
|
36115
|
+
continue;
|
|
36116
|
+
}
|
|
36117
|
+
const syncPointTick = barStartTick + syncPoint.ratioPosition * duration;
|
|
36118
|
+
// first process all tempo changes until this sync point
|
|
36119
|
+
while (tempoChangeIndex < bar.tempoAutomations.length &&
|
|
36120
|
+
bar.tempoAutomations[tempoChangeIndex].ratioPosition <= syncPoint.ratioPosition) {
|
|
36121
|
+
const tempoChange = bar.tempoAutomations[tempoChangeIndex];
|
|
36122
|
+
const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
|
|
36123
|
+
tickOffset = absoluteTick - context.synthTick;
|
|
36124
|
+
if (tickOffset > 0) {
|
|
36125
|
+
context.synthTick = absoluteTick;
|
|
36126
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36127
|
+
}
|
|
36128
|
+
context.currentTempo = tempoChange.value;
|
|
36129
|
+
tempoChangeIndex++;
|
|
36130
|
+
}
|
|
36131
|
+
// process time until sync point
|
|
36132
|
+
tickOffset = syncPointTick - context.synthTick;
|
|
36133
|
+
if (tickOffset > 0) {
|
|
36134
|
+
context.synthTick = syncPointTick;
|
|
36135
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36136
|
+
}
|
|
36137
|
+
// update the previous sync point according to the new time
|
|
36138
|
+
if (context.syncPoints.length > 0) {
|
|
36139
|
+
context.syncPoints[context.syncPoints.length - 1].updateSyncBpm(context.synthTime, syncPoint.syncPointValue.millisecondOffset);
|
|
36140
|
+
}
|
|
36141
|
+
// create the new sync point
|
|
36142
|
+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
|
|
36143
|
+
backingTrackSyncPoint.masterBarIndex = bar.index;
|
|
36144
|
+
backingTrackSyncPoint.masterBarOccurence = occurence;
|
|
36145
|
+
backingTrackSyncPoint.synthTick = syncPointTick;
|
|
36146
|
+
backingTrackSyncPoint.synthBpm = context.currentTempo;
|
|
36147
|
+
backingTrackSyncPoint.synthTime = context.synthTime;
|
|
36148
|
+
backingTrackSyncPoint.syncTime = syncPoint.syncPointValue.millisecondOffset;
|
|
36149
|
+
backingTrackSyncPoint.syncBpm = 0 /* calculated by next sync point */;
|
|
36150
|
+
context.syncPoints.push(backingTrackSyncPoint);
|
|
36151
|
+
context.automationToSyncPoint.set(syncPoint, backingTrackSyncPoint);
|
|
36152
|
+
}
|
|
36153
|
+
// process remaining tempo changes after all sync points
|
|
36154
|
+
while (tempoChangeIndex < bar.tempoAutomations.length) {
|
|
36155
|
+
const tempoChange = bar.tempoAutomations[tempoChangeIndex];
|
|
36156
|
+
const absoluteTick = barStartTick + tempoChange.ratioPosition * duration;
|
|
36157
|
+
tickOffset = absoluteTick - context.synthTick;
|
|
36158
|
+
if (tickOffset > 0) {
|
|
36159
|
+
context.synthTick = absoluteTick;
|
|
36160
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36161
|
+
}
|
|
36162
|
+
context.currentTempo = tempoChange.value;
|
|
36163
|
+
tempoChangeIndex++;
|
|
36164
|
+
}
|
|
36165
|
+
}
|
|
36166
|
+
static processBarTimeNoSyncPoints(bar, context) {
|
|
36167
|
+
// walk through the tempo changes
|
|
36168
|
+
const barStartTick = context.synthTick;
|
|
36169
|
+
const duration = bar.calculateDuration();
|
|
36170
|
+
for (const changes of bar.tempoAutomations) {
|
|
36171
|
+
const absoluteTick = barStartTick + changes.ratioPosition * duration;
|
|
36172
|
+
const tickOffset = absoluteTick - context.synthTick;
|
|
36173
|
+
if (tickOffset > 0) {
|
|
36174
|
+
context.synthTick = absoluteTick;
|
|
36175
|
+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
|
|
36176
|
+
}
|
|
36177
|
+
context.currentTempo = changes.value;
|
|
36178
|
+
}
|
|
35986
36179
|
}
|
|
35987
36180
|
static toChannelShort(data) {
|
|
35988
36181
|
const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
|
|
@@ -38486,6 +38679,7 @@
|
|
|
38486
38679
|
value.playbackSpeed = this._playbackSpeed;
|
|
38487
38680
|
value.isLooping = this._isLooping;
|
|
38488
38681
|
value.midiEventsPlayedFilter = this._midiEventsPlayedFilter;
|
|
38682
|
+
this.ready.trigger();
|
|
38489
38683
|
}
|
|
38490
38684
|
else {
|
|
38491
38685
|
newUnregister.push(value.ready.on(() => {
|
|
@@ -43846,6 +44040,9 @@
|
|
|
43846
44040
|
const audioElement = document.createElement('audio');
|
|
43847
44041
|
audioElement.style.display = 'none';
|
|
43848
44042
|
document.body.appendChild(audioElement);
|
|
44043
|
+
audioElement.addEventListener('seeked', () => {
|
|
44044
|
+
this.updatePosition();
|
|
44045
|
+
});
|
|
43849
44046
|
audioElement.addEventListener('timeupdate', () => {
|
|
43850
44047
|
this.updatePosition();
|
|
43851
44048
|
});
|
|
@@ -60941,9 +61138,9 @@
|
|
|
60941
61138
|
print(`build date: ${VersionInfo.date}`);
|
|
60942
61139
|
}
|
|
60943
61140
|
}
|
|
60944
|
-
VersionInfo.version = '1.6.0-alpha.
|
|
60945
|
-
VersionInfo.date = '2025-05-
|
|
60946
|
-
VersionInfo.commit = '
|
|
61141
|
+
VersionInfo.version = '1.6.0-alpha.1430';
|
|
61142
|
+
VersionInfo.date = '2025-05-29T22:21:21.689Z';
|
|
61143
|
+
VersionInfo.commit = '98a4c2bec8d71f2645008118d3b77fb40973e7fe';
|
|
60947
61144
|
|
|
60948
61145
|
/**
|
|
60949
61146
|
* A factory for custom layout engines.
|
|
@@ -61856,18 +62053,24 @@
|
|
|
61856
62053
|
writeDom(parent, score) {
|
|
61857
62054
|
const gpif = parent.addElement('GPIF');
|
|
61858
62055
|
// just some values at the time this was implemented,
|
|
61859
|
-
gpif.addElement('GPVersion').innerText = '
|
|
62056
|
+
gpif.addElement('GPVersion').innerText = '8.1.3';
|
|
61860
62057
|
const gpRevision = gpif.addElement('GPRevision');
|
|
61861
|
-
gpRevision.
|
|
61862
|
-
gpRevision.attributes.set('
|
|
61863
|
-
gpRevision.
|
|
61864
|
-
|
|
61865
|
-
|
|
62058
|
+
gpRevision.attributes.set('required', '12024');
|
|
62059
|
+
gpRevision.attributes.set('recommended', '13000');
|
|
62060
|
+
gpRevision.innerText = '13007';
|
|
62061
|
+
const encoding = gpif.addElement('Encoding');
|
|
62062
|
+
encoding.addElement('EncodingDescription').innerText = 'GP8';
|
|
62063
|
+
const alphaTabComment = new XmlNode();
|
|
62064
|
+
alphaTabComment.nodeType = XmlNodeType.Comment;
|
|
62065
|
+
alphaTabComment.value = `Written by alphaTab ${VersionInfo.version} (${VersionInfo.commit})`;
|
|
62066
|
+
encoding.addChild(alphaTabComment);
|
|
61866
62067
|
this.writeScoreNode(gpif, score);
|
|
61867
62068
|
this.writeMasterTrackNode(gpif, score);
|
|
62069
|
+
this.writeBackingTrackNode(gpif, score);
|
|
61868
62070
|
this.writeAudioTracksNode(gpif, score);
|
|
61869
62071
|
this.writeTracksNode(gpif, score);
|
|
61870
62072
|
this.writeMasterBarsNode(gpif, score);
|
|
62073
|
+
this.writeAssets(gpif, score);
|
|
61871
62074
|
const bars = gpif.addElement('Bars');
|
|
61872
62075
|
const voices = gpif.addElement('Voices');
|
|
61873
62076
|
const beats = gpif.addElement('Beats');
|
|
@@ -61890,6 +62093,42 @@
|
|
|
61890
62093
|
}
|
|
61891
62094
|
}
|
|
61892
62095
|
}
|
|
62096
|
+
writeAssets(parent, score) {
|
|
62097
|
+
if (!score.backingTrack?.rawAudioFile) {
|
|
62098
|
+
return;
|
|
62099
|
+
}
|
|
62100
|
+
const assets = parent.addElement('Assets');
|
|
62101
|
+
const asset = assets.addElement('Asset');
|
|
62102
|
+
asset.attributes.set('id', this.backingTrackAssetId);
|
|
62103
|
+
this.backingTrackAssetFileName = 'Content/Assets/backing-track';
|
|
62104
|
+
asset.addElement('EmbeddedFilePath').setCData(this.backingTrackAssetFileName);
|
|
62105
|
+
}
|
|
62106
|
+
writeBackingTrackNode(parent, score) {
|
|
62107
|
+
if (!score.backingTrack?.rawAudioFile) {
|
|
62108
|
+
return;
|
|
62109
|
+
}
|
|
62110
|
+
const backingTrackNode = parent.addElement('BackingTrack');
|
|
62111
|
+
const backingTrackAssetId = '0';
|
|
62112
|
+
this.backingTrackAssetId = backingTrackAssetId;
|
|
62113
|
+
backingTrackNode.addElement('IconId').innerText = '21';
|
|
62114
|
+
backingTrackNode.addElement('Color').innerText = '0 0 0';
|
|
62115
|
+
backingTrackNode.addElement('Name').setCData('Audio Track');
|
|
62116
|
+
backingTrackNode.addElement('ShortName').setCData('a.track');
|
|
62117
|
+
backingTrackNode.addElement('PlaybackState').innerText = 'Default';
|
|
62118
|
+
backingTrackNode.addElement('Enabled').innerText = 'true';
|
|
62119
|
+
backingTrackNode.addElement('Source').innerText = 'Local';
|
|
62120
|
+
backingTrackNode.addElement('AssetId').innerText = backingTrackAssetId;
|
|
62121
|
+
const channelStrip = backingTrackNode.addElement('ChannelStrip');
|
|
62122
|
+
channelStrip.addElement('Parameters').innerText =
|
|
62123
|
+
'0.500000 0.500000 0.500000 0.500000 0.500000 0.500000 0.500000 0.500000 0.500000 0.000000 0.500000 0.500000 0.800000 0.500000 0.500000 0.500000';
|
|
62124
|
+
channelStrip.addElement('YouTubeVideoUrl').innerText = '';
|
|
62125
|
+
channelStrip.addElement('Filter').innerText = '6';
|
|
62126
|
+
channelStrip.addElement('FramesPerPixel').innerText = '400';
|
|
62127
|
+
const framePadding = this.backingTrackFramePadding !== undefined ? this.backingTrackFramePadding : 0;
|
|
62128
|
+
backingTrackNode.addElement('FramePadding').innerText = `${framePadding}`;
|
|
62129
|
+
backingTrackNode.addElement('Semitones').innerText = '0';
|
|
62130
|
+
backingTrackNode.addElement('Cents').innerText = '0';
|
|
62131
|
+
}
|
|
61893
62132
|
writeNoteNode(parent, note) {
|
|
61894
62133
|
const noteNode = parent.addElement('Note');
|
|
61895
62134
|
noteNode.attributes.set('id', note.id.toString());
|
|
@@ -62546,6 +62785,12 @@
|
|
|
62546
62785
|
initialTempoAutomation.addElement('Text').innerText = score.tempoLabel;
|
|
62547
62786
|
}
|
|
62548
62787
|
}
|
|
62788
|
+
const initialSyncPoint = score.masterBars[0].syncPoints
|
|
62789
|
+
? score.masterBars[0].syncPoints.find(p => p.ratioPosition === 0 && p.syncPointValue.barOccurence === 0)
|
|
62790
|
+
: undefined;
|
|
62791
|
+
const millisecondPadding = initialSyncPoint ? initialSyncPoint.syncPointValue.millisecondOffset : 0;
|
|
62792
|
+
this.backingTrackFramePadding = (-1 * ((millisecondPadding / 1000) * GpifWriter.SampleRate)) | 0;
|
|
62793
|
+
const modifiedTempoLookup = new Lazy(() => MidiFileGenerator.buildModifiedTempoLookup(score));
|
|
62549
62794
|
for (const mb of score.masterBars) {
|
|
62550
62795
|
for (const automation of mb.tempoAutomations) {
|
|
62551
62796
|
const tempoAutomation = automations.addElement('Automation');
|
|
@@ -62559,6 +62804,25 @@
|
|
|
62559
62804
|
tempoAutomation.addElement('Text').innerText = automation.text;
|
|
62560
62805
|
}
|
|
62561
62806
|
}
|
|
62807
|
+
if (mb.syncPoints) {
|
|
62808
|
+
for (const syncPoint of mb.syncPoints) {
|
|
62809
|
+
const syncPointAutomation = automations.addElement('Automation');
|
|
62810
|
+
syncPointAutomation.addElement('Type').innerText = 'SyncPoint';
|
|
62811
|
+
syncPointAutomation.addElement('Linear').innerText = 'false';
|
|
62812
|
+
syncPointAutomation.addElement('Bar').innerText = mb.index.toString();
|
|
62813
|
+
syncPointAutomation.addElement('Position').innerText = syncPoint.ratioPosition.toString();
|
|
62814
|
+
syncPointAutomation.addElement('Visible').innerText = 'true';
|
|
62815
|
+
const value = syncPointAutomation.addElement('Value');
|
|
62816
|
+
value.addElement('BarIndex').innerText = mb.index.toString();
|
|
62817
|
+
value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
|
|
62818
|
+
value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
|
|
62819
|
+
value.addElement('OriginalTempo').innerText = score.tempo.toString();
|
|
62820
|
+
const frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
|
|
62821
|
+
GpifWriter.SampleRate) |
|
|
62822
|
+
0;
|
|
62823
|
+
value.addElement('FrameOffset').innerText = frameOffset.toString();
|
|
62824
|
+
}
|
|
62825
|
+
}
|
|
62562
62826
|
}
|
|
62563
62827
|
}
|
|
62564
62828
|
writeAudioTracksNode(parent, score) {
|
|
@@ -63209,6 +63473,10 @@
|
|
|
63209
63473
|
voiceNode.addElement('Beats').innerText = voice.beats.map(v => v.id).join(' ');
|
|
63210
63474
|
}
|
|
63211
63475
|
}
|
|
63476
|
+
// tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
|
|
63477
|
+
// they are NOT using the sample rate of the input file.
|
|
63478
|
+
// Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
|
|
63479
|
+
GpifWriter.SampleRate = 44100;
|
|
63212
63480
|
GpifWriter.MidiProgramInfoLookup = new Map([
|
|
63213
63481
|
[0, new GpifMidiProgramInfo(GpifIconIds.Piano, 'Acoustic Piano')],
|
|
63214
63482
|
[1, new GpifMidiProgramInfo(GpifIconIds.Piano, 'Acoustic Piano')],
|
|
@@ -64946,11 +65214,11 @@
|
|
|
64946
65214
|
}
|
|
64947
65215
|
|
|
64948
65216
|
/**
|
|
64949
|
-
* This ScoreExporter can write Guitar Pro 7 (gp) files.
|
|
65217
|
+
* This ScoreExporter can write Guitar Pro 7+ (gp) files.
|
|
64950
65218
|
*/
|
|
64951
65219
|
class Gp7Exporter extends ScoreExporter {
|
|
64952
65220
|
get name() {
|
|
64953
|
-
return 'Guitar Pro 7';
|
|
65221
|
+
return 'Guitar Pro 7-8';
|
|
64954
65222
|
}
|
|
64955
65223
|
writeScore(score) {
|
|
64956
65224
|
Logger.debug(this.name, 'Writing data entries');
|
|
@@ -64967,6 +65235,9 @@
|
|
|
64967
65235
|
fileSystem.writeEntry(new ZipEntry('Content/PartConfiguration', partConfiguration));
|
|
64968
65236
|
fileSystem.writeEntry(new ZipEntry('Content/LayoutConfiguration', layoutConfiguration));
|
|
64969
65237
|
fileSystem.writeEntry(new ZipEntry('Content/score.gpif', IOHelper.stringToBytes(gpifXml)));
|
|
65238
|
+
if (gpifWriter.backingTrackAssetFileName) {
|
|
65239
|
+
fileSystem.writeEntry(new ZipEntry(gpifWriter.backingTrackAssetFileName, score.backingTrack.rawAudioFile));
|
|
65240
|
+
}
|
|
64970
65241
|
fileSystem.end();
|
|
64971
65242
|
}
|
|
64972
65243
|
}
|