@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.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
|
*
|
|
@@ -1412,13 +1412,7 @@ class SyncPointData {
|
|
|
1412
1412
|
*/
|
|
1413
1413
|
this.barOccurence = 0;
|
|
1414
1414
|
/**
|
|
1415
|
-
* The
|
|
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
|
-
/**
|
|
1421
|
-
* The uadio offset marking the position within the audio track in milliseconds.
|
|
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.
|
|
1423
1417
|
*/
|
|
1424
1418
|
this.millisecondOffset = 0;
|
|
@@ -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
|
}
|
|
@@ -7299,6 +7292,59 @@ class Score {
|
|
|
7299
7292
|
this.tracks[i].finish(settings, sharedDataBag);
|
|
7300
7293
|
}
|
|
7301
7294
|
}
|
|
7295
|
+
/**
|
|
7296
|
+
* Applies the given list of {@link FlatSyncPoint} to this song.
|
|
7297
|
+
* @param syncPoints The list of sync points to apply.
|
|
7298
|
+
* @since 1.6.0
|
|
7299
|
+
*/
|
|
7300
|
+
applyFlatSyncPoints(syncPoints) {
|
|
7301
|
+
for (const b of this.masterBars) {
|
|
7302
|
+
b.syncPoints = undefined;
|
|
7303
|
+
}
|
|
7304
|
+
for (const syncPoint of syncPoints) {
|
|
7305
|
+
const automation = new Automation();
|
|
7306
|
+
automation.ratioPosition = Math.min(1, Math.max(0, syncPoint.barPosition));
|
|
7307
|
+
automation.type = AutomationType.SyncPoint;
|
|
7308
|
+
automation.syncPointValue = new SyncPointData();
|
|
7309
|
+
automation.syncPointValue.millisecondOffset = syncPoint.millisecondOffset;
|
|
7310
|
+
automation.syncPointValue.barOccurence = syncPoint.barOccurence;
|
|
7311
|
+
if (syncPoint.barIndex < this.masterBars.length) {
|
|
7312
|
+
this.masterBars[syncPoint.barIndex].addSyncPoint(automation);
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
for (const b of this.masterBars) {
|
|
7316
|
+
if (b.syncPoints) {
|
|
7317
|
+
b.syncPoints.sort((a, b) => {
|
|
7318
|
+
const occurence = a.syncPointValue.barOccurence - b.syncPointValue.barOccurence;
|
|
7319
|
+
if (occurence !== 0) {
|
|
7320
|
+
return occurence;
|
|
7321
|
+
}
|
|
7322
|
+
return a.ratioPosition - b.ratioPosition;
|
|
7323
|
+
});
|
|
7324
|
+
}
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
/**
|
|
7328
|
+
* Exports all sync points in this song to a {@link FlatSyncPoint} list.
|
|
7329
|
+
* @since 1.6.0
|
|
7330
|
+
*/
|
|
7331
|
+
exportFlatSyncPoints() {
|
|
7332
|
+
const syncPoints = [];
|
|
7333
|
+
for (const masterBar of this.masterBars) {
|
|
7334
|
+
const masterBarSyncPoints = masterBar.syncPoints;
|
|
7335
|
+
if (masterBarSyncPoints) {
|
|
7336
|
+
for (const syncPoint of masterBarSyncPoints) {
|
|
7337
|
+
syncPoints.push({
|
|
7338
|
+
barIndex: masterBar.index,
|
|
7339
|
+
barOccurence: syncPoint.syncPointValue.barOccurence,
|
|
7340
|
+
barPosition: syncPoint.ratioPosition,
|
|
7341
|
+
millisecondOffset: syncPoint.syncPointValue.millisecondOffset
|
|
7342
|
+
});
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
return syncPoints;
|
|
7347
|
+
}
|
|
7302
7348
|
}
|
|
7303
7349
|
|
|
7304
7350
|
/**
|
|
@@ -13607,6 +13653,7 @@ var XmlNodeType;
|
|
|
13607
13653
|
XmlNodeType[XmlNodeType["CDATA"] = 3] = "CDATA";
|
|
13608
13654
|
XmlNodeType[XmlNodeType["Document"] = 4] = "Document";
|
|
13609
13655
|
XmlNodeType[XmlNodeType["DocumentType"] = 5] = "DocumentType";
|
|
13656
|
+
XmlNodeType[XmlNodeType["Comment"] = 6] = "Comment";
|
|
13610
13657
|
})(XmlNodeType || (XmlNodeType = {}));
|
|
13611
13658
|
class XmlNode {
|
|
13612
13659
|
constructor() {
|
|
@@ -14183,7 +14230,7 @@ class XmlWriter {
|
|
|
14183
14230
|
this.indent();
|
|
14184
14231
|
for (const child of xml.childNodes) {
|
|
14185
14232
|
// skip text nodes in case of multiple children
|
|
14186
|
-
if (child.nodeType === XmlNodeType.Element) {
|
|
14233
|
+
if (child.nodeType === XmlNodeType.Element || child.nodeType === XmlNodeType.Comment) {
|
|
14187
14234
|
this.writeNode(child);
|
|
14188
14235
|
}
|
|
14189
14236
|
}
|
|
@@ -14214,6 +14261,9 @@ class XmlWriter {
|
|
|
14214
14261
|
case XmlNodeType.DocumentType:
|
|
14215
14262
|
this.write(`<!DOCTYPE ${xml.value}>`);
|
|
14216
14263
|
break;
|
|
14264
|
+
case XmlNodeType.Comment:
|
|
14265
|
+
this.write(`<!-- ${xml.value} -->`);
|
|
14266
|
+
break;
|
|
14217
14267
|
}
|
|
14218
14268
|
}
|
|
14219
14269
|
unindend() {
|
|
@@ -14655,9 +14705,6 @@ class GpifParser {
|
|
|
14655
14705
|
case 'BarOccurrence':
|
|
14656
14706
|
syncPointValue.barOccurence = GpifParser.parseIntSafe(vc.innerText, 0);
|
|
14657
14707
|
break;
|
|
14658
|
-
case 'ModifiedTempo':
|
|
14659
|
-
syncPointValue.modifiedTempo = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14660
|
-
break;
|
|
14661
14708
|
case 'FrameOffset':
|
|
14662
14709
|
const frameOffset = GpifParser.parseFloatSafe(vc.innerText, 0);
|
|
14663
14710
|
syncPointValue.millisecondOffset = (frameOffset / GpifParser.SampleRate) * 1000;
|
|
@@ -16810,7 +16857,7 @@ GpifParser.BendPointPositionFactor = BendPoint.MaxPosition / 100.0;
|
|
|
16810
16857
|
* Internal Range: 1 per quarter note
|
|
16811
16858
|
*/
|
|
16812
16859
|
GpifParser.BendPointValueFactor = 1 / 25.0;
|
|
16813
|
-
//
|
|
16860
|
+
// tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
|
|
16814
16861
|
// they are NOT using the sample rate of the input file.
|
|
16815
16862
|
// Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
|
|
16816
16863
|
GpifParser.SampleRate = 44100;
|
|
@@ -22280,6 +22327,61 @@ class SynthEvent {
|
|
|
22280
22327
|
}
|
|
22281
22328
|
}
|
|
22282
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
|
+
|
|
22283
22385
|
class MidiFileSequencerTempoChange {
|
|
22284
22386
|
constructor(bpm, ticks, time) {
|
|
22285
22387
|
this.bpm = bpm;
|
|
@@ -22287,14 +22389,6 @@ class MidiFileSequencerTempoChange {
|
|
|
22287
22389
|
this.time = time;
|
|
22288
22390
|
}
|
|
22289
22391
|
}
|
|
22290
|
-
class BackingTrackSyncPointWithTime {
|
|
22291
|
-
constructor(tick, time, modifiedTempo, millisecondOffset) {
|
|
22292
|
-
this.alphaTabTick = tick;
|
|
22293
|
-
this.alphaTabTime = time;
|
|
22294
|
-
this.modifiedTempo = modifiedTempo;
|
|
22295
|
-
this.millisecondOffset = millisecondOffset;
|
|
22296
|
-
}
|
|
22297
|
-
}
|
|
22298
22392
|
class MidiSequencerState {
|
|
22299
22393
|
constructor() {
|
|
22300
22394
|
this.tempoChanges = [];
|
|
@@ -22398,7 +22492,7 @@ class MidiFileSequencer {
|
|
|
22398
22492
|
this._mainState.currentTempo = this._mainState.tempoChanges[0].bpm;
|
|
22399
22493
|
this._mainState.modifiedTempo =
|
|
22400
22494
|
this._mainState.syncPoints.length > 0
|
|
22401
|
-
? this._mainState.syncPoints[0].
|
|
22495
|
+
? this._mainState.syncPoints[0].syncBpm
|
|
22402
22496
|
: this._mainState.currentTempo;
|
|
22403
22497
|
if (this.isPlayingMain) {
|
|
22404
22498
|
const metronomeVolume = this._synthesizer.metronomeVolume;
|
|
@@ -22569,7 +22663,7 @@ class MidiFileSequencer {
|
|
|
22569
22663
|
}
|
|
22570
22664
|
mainUpdateSyncPoints(syncPoints) {
|
|
22571
22665
|
const state = this._mainState;
|
|
22572
|
-
syncPoints.sort((a, b) => a.
|
|
22666
|
+
syncPoints.sort((a, b) => a.synthTick - b.synthTick); // just in case
|
|
22573
22667
|
state.syncPoints = [];
|
|
22574
22668
|
if (syncPoints.length >= 0) {
|
|
22575
22669
|
let bpm = 120;
|
|
@@ -22579,6 +22673,8 @@ class MidiFileSequencer {
|
|
|
22579
22673
|
for (let i = 0; i < syncPoints.length; i++) {
|
|
22580
22674
|
const p = syncPoints[i];
|
|
22581
22675
|
let deltaTick = 0;
|
|
22676
|
+
// TODO: merge interpolation into MidiFileGenerator where we already play through
|
|
22677
|
+
// the time axis.
|
|
22582
22678
|
// remember state from previous sync point (or start). to handle linear interpolation
|
|
22583
22679
|
let previousModifiedTempo;
|
|
22584
22680
|
let previousMillisecondOffset;
|
|
@@ -22590,9 +22686,9 @@ class MidiFileSequencer {
|
|
|
22590
22686
|
}
|
|
22591
22687
|
else {
|
|
22592
22688
|
const previousSyncPoint = syncPoints[i - 1];
|
|
22593
|
-
previousModifiedTempo = previousSyncPoint.
|
|
22594
|
-
previousMillisecondOffset = previousSyncPoint.
|
|
22595
|
-
previousTick = previousSyncPoint.
|
|
22689
|
+
previousModifiedTempo = previousSyncPoint.syncBpm;
|
|
22690
|
+
previousMillisecondOffset = previousSyncPoint.syncTime;
|
|
22691
|
+
previousTick = previousSyncPoint.synthTick;
|
|
22596
22692
|
}
|
|
22597
22693
|
// process time until sync point
|
|
22598
22694
|
// here it gets a bit tricky. if we have tempo changes on the synthesizer time axis (inbetween two sync points)
|
|
@@ -22600,25 +22696,31 @@ class MidiFileSequencer {
|
|
|
22600
22696
|
// otherwise the linear interpolation later in the lookup will fail.
|
|
22601
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
|
|
22602
22698
|
while (tempoChangeIndex < state.tempoChanges.length &&
|
|
22603
|
-
state.tempoChanges[tempoChangeIndex].ticks <= p.
|
|
22699
|
+
state.tempoChanges[tempoChangeIndex].ticks <= p.synthTick) {
|
|
22604
22700
|
deltaTick = state.tempoChanges[tempoChangeIndex].ticks - absTick;
|
|
22605
22701
|
if (deltaTick > 0) {
|
|
22606
22702
|
absTick += deltaTick;
|
|
22607
22703
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22608
|
-
const millisPerTick = (p.
|
|
22704
|
+
const millisPerTick = (p.syncTime - previousMillisecondOffset) / (p.synthTick - previousTick);
|
|
22609
22705
|
const interpolatedMillisecondOffset = (absTick - previousTick) * millisPerTick + previousMillisecondOffset;
|
|
22610
|
-
|
|
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;
|
|
22611
22712
|
}
|
|
22612
22713
|
bpm = state.tempoChanges[tempoChangeIndex].bpm;
|
|
22613
22714
|
tempoChangeIndex++;
|
|
22614
22715
|
}
|
|
22615
|
-
deltaTick = p.
|
|
22716
|
+
deltaTick = p.synthTick - absTick;
|
|
22616
22717
|
absTick += deltaTick;
|
|
22617
22718
|
absTime += deltaTick * (60000.0 / (bpm * state.division));
|
|
22618
|
-
state.syncPoints.push(
|
|
22719
|
+
state.syncPoints.push(p);
|
|
22619
22720
|
}
|
|
22620
22721
|
}
|
|
22621
22722
|
state.syncPointIndex = 0;
|
|
22723
|
+
state.modifiedTempo = state.syncPoints.length > 0 ? state.syncPoints[0].syncBpm : state.currentTempo;
|
|
22622
22724
|
}
|
|
22623
22725
|
currentTimePositionToTickPosition(timePosition) {
|
|
22624
22726
|
const state = this._currentState;
|
|
@@ -22651,16 +22753,15 @@ class MidiFileSequencer {
|
|
|
22651
22753
|
const syncPoints = state.syncPoints;
|
|
22652
22754
|
if (syncPoints.length > 0) {
|
|
22653
22755
|
let syncPointIndex = Math.min(state.syncPointIndex, syncPoints.length - 1);
|
|
22654
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22756
|
+
if (timePosition < syncPoints[syncPointIndex].syncTime) {
|
|
22655
22757
|
syncPointIndex = 0;
|
|
22656
22758
|
}
|
|
22657
|
-
while (syncPointIndex + 1 < syncPoints.length &&
|
|
22658
|
-
syncPoints[syncPointIndex + 1].millisecondOffset <= timePosition) {
|
|
22759
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].syncTime <= timePosition) {
|
|
22659
22760
|
syncPointIndex++;
|
|
22660
22761
|
}
|
|
22661
22762
|
if (syncPointIndex !== state.syncPointIndex) {
|
|
22662
22763
|
state.syncPointIndex = syncPointIndex;
|
|
22663
|
-
state.modifiedTempo = syncPoints[syncPointIndex].
|
|
22764
|
+
state.modifiedTempo = syncPoints[syncPointIndex].syncBpm;
|
|
22664
22765
|
}
|
|
22665
22766
|
}
|
|
22666
22767
|
else {
|
|
@@ -22676,18 +22777,18 @@ class MidiFileSequencer {
|
|
|
22676
22777
|
this.updateSyncPoints(this._mainState, timePosition);
|
|
22677
22778
|
const syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22678
22779
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22679
|
-
const timeDiff = timePosition - currentSyncPoint.
|
|
22780
|
+
const timeDiff = timePosition - currentSyncPoint.syncTime;
|
|
22680
22781
|
let alphaTabTimeDiff;
|
|
22681
22782
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22682
22783
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22683
|
-
const relativeTimeDiff = timeDiff / (nextSyncPoint.
|
|
22684
|
-
alphaTabTimeDiff = (nextSyncPoint.
|
|
22784
|
+
const relativeTimeDiff = timeDiff / (nextSyncPoint.syncTime - currentSyncPoint.syncTime);
|
|
22785
|
+
alphaTabTimeDiff = (nextSyncPoint.synthTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22685
22786
|
}
|
|
22686
22787
|
else {
|
|
22687
|
-
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.
|
|
22688
|
-
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.
|
|
22788
|
+
const relativeTimeDiff = timeDiff / (backingTrackLength - currentSyncPoint.syncTime);
|
|
22789
|
+
alphaTabTimeDiff = (mainState.endTime - currentSyncPoint.synthTime) * relativeTimeDiff;
|
|
22689
22790
|
}
|
|
22690
|
-
return (currentSyncPoint.
|
|
22791
|
+
return (currentSyncPoint.synthTime + alphaTabTimeDiff) / this.playbackSpeed;
|
|
22691
22792
|
}
|
|
22692
22793
|
mainTimePositionToBackingTrack(timePosition, backingTrackLength) {
|
|
22693
22794
|
const mainState = this._mainState;
|
|
@@ -22697,27 +22798,27 @@ class MidiFileSequencer {
|
|
|
22697
22798
|
}
|
|
22698
22799
|
timePosition *= this.playbackSpeed;
|
|
22699
22800
|
let syncPointIndex = Math.min(mainState.syncPointIndex, syncPoints.length - 1);
|
|
22700
|
-
if (timePosition < syncPoints[syncPointIndex].
|
|
22801
|
+
if (timePosition < syncPoints[syncPointIndex].synthTime) {
|
|
22701
22802
|
syncPointIndex = 0;
|
|
22702
22803
|
}
|
|
22703
|
-
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].
|
|
22804
|
+
while (syncPointIndex + 1 < syncPoints.length && syncPoints[syncPointIndex + 1].synthTime <= timePosition) {
|
|
22704
22805
|
syncPointIndex++;
|
|
22705
22806
|
}
|
|
22706
22807
|
// NOTE: this logic heavily relies on the interpolation done in mainUpdateSyncPoints
|
|
22707
22808
|
// we ensure that we have a linear increase between two points
|
|
22708
22809
|
const currentSyncPoint = syncPoints[syncPointIndex];
|
|
22709
|
-
const alphaTabTimeDiff = timePosition - currentSyncPoint.
|
|
22810
|
+
const alphaTabTimeDiff = timePosition - currentSyncPoint.synthTime;
|
|
22710
22811
|
let backingTrackPos;
|
|
22711
22812
|
if (syncPointIndex + 1 < syncPoints.length) {
|
|
22712
22813
|
const nextSyncPoint = syncPoints[syncPointIndex + 1];
|
|
22713
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.
|
|
22714
|
-
const backingTrackDiff = nextSyncPoint.
|
|
22715
|
-
backingTrackPos = currentSyncPoint.
|
|
22814
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (nextSyncPoint.synthTime - currentSyncPoint.synthTime);
|
|
22815
|
+
const backingTrackDiff = nextSyncPoint.syncTime - currentSyncPoint.syncTime;
|
|
22816
|
+
backingTrackPos = currentSyncPoint.syncTime + backingTrackDiff * relativeAlphaTabTimeDiff;
|
|
22716
22817
|
}
|
|
22717
22818
|
else {
|
|
22718
|
-
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.
|
|
22719
|
-
const frameDiff = backingTrackLength - currentSyncPoint.
|
|
22720
|
-
backingTrackPos = currentSyncPoint.
|
|
22819
|
+
const relativeAlphaTabTimeDiff = alphaTabTimeDiff / (mainState.endTime - currentSyncPoint.synthTime);
|
|
22820
|
+
const frameDiff = backingTrackLength - currentSyncPoint.syncTime;
|
|
22821
|
+
backingTrackPos = currentSyncPoint.syncTime + frameDiff * relativeAlphaTabTimeDiff;
|
|
22721
22822
|
}
|
|
22722
22823
|
return backingTrackPos;
|
|
22723
22824
|
}
|
|
@@ -30989,7 +31090,6 @@ class SyncPointDataSerializer {
|
|
|
30989
31090
|
}
|
|
30990
31091
|
const o = new Map();
|
|
30991
31092
|
o.set("baroccurence", obj.barOccurence);
|
|
30992
|
-
o.set("modifiedtempo", obj.modifiedTempo);
|
|
30993
31093
|
o.set("millisecondoffset", obj.millisecondOffset);
|
|
30994
31094
|
return o;
|
|
30995
31095
|
}
|
|
@@ -30998,9 +31098,6 @@ class SyncPointDataSerializer {
|
|
|
30998
31098
|
case "baroccurence":
|
|
30999
31099
|
obj.barOccurence = v;
|
|
31000
31100
|
return true;
|
|
31001
|
-
case "modifiedtempo":
|
|
31002
|
-
obj.modifiedTempo = v;
|
|
31003
|
-
return true;
|
|
31004
31101
|
case "millisecondoffset":
|
|
31005
31102
|
obj.millisecondOffset = v;
|
|
31006
31103
|
return true;
|
|
@@ -35763,17 +35860,6 @@ class MidiTickLookup {
|
|
|
35763
35860
|
}
|
|
35764
35861
|
}
|
|
35765
35862
|
|
|
35766
|
-
/**
|
|
35767
|
-
* Rerpresents a point to sync the alphaTab time axis with an external backing track.
|
|
35768
|
-
*/
|
|
35769
|
-
class BackingTrackSyncPoint {
|
|
35770
|
-
constructor(tick, data) {
|
|
35771
|
-
this.tick = 0;
|
|
35772
|
-
this.tick = tick;
|
|
35773
|
-
this.data = data;
|
|
35774
|
-
}
|
|
35775
|
-
}
|
|
35776
|
-
|
|
35777
35863
|
class MidiNoteDuration {
|
|
35778
35864
|
constructor() {
|
|
35779
35865
|
this.noteOnly = 0;
|
|
@@ -35794,6 +35880,14 @@ class RasgueadoInfo {
|
|
|
35794
35880
|
this.brushInfos = [];
|
|
35795
35881
|
}
|
|
35796
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
|
+
}
|
|
35797
35891
|
/**
|
|
35798
35892
|
* This generator creates a midi file using a score.
|
|
35799
35893
|
*/
|
|
@@ -35920,9 +36014,22 @@ class MidiFileGenerator {
|
|
|
35920
36014
|
});
|
|
35921
36015
|
return syncPoints;
|
|
35922
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
|
+
}
|
|
35923
36028
|
static playThroughSong(score, syncPoints, generateMasterBar, generateTracks, finish) {
|
|
35924
36029
|
const controller = new MidiPlaybackController(score);
|
|
35925
|
-
|
|
36030
|
+
const playContext = new PlayThroughContext();
|
|
36031
|
+
playContext.currentTempo = score.tempo;
|
|
36032
|
+
playContext.syncPoints = syncPoints;
|
|
35926
36033
|
let previousMasterBar = null;
|
|
35927
36034
|
// store the previous played bar for repeats
|
|
35928
36035
|
const barOccurence = new Map();
|
|
@@ -35935,23 +36042,11 @@ class MidiFileGenerator {
|
|
|
35935
36042
|
let occurence = barOccurence.has(index) ? barOccurence.get(index) : -1;
|
|
35936
36043
|
occurence++;
|
|
35937
36044
|
barOccurence.set(index, occurence);
|
|
35938
|
-
generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
|
|
35939
|
-
const
|
|
35940
|
-
|
|
35941
|
-
|
|
35942
|
-
|
|
35943
|
-
const tick = currentTick + bar.calculateDuration() * syncPoint.ratioPosition;
|
|
35944
|
-
syncPoints.push(new BackingTrackSyncPoint(tick, syncPoint.syncPointValue));
|
|
35945
|
-
}
|
|
35946
|
-
}
|
|
35947
|
-
}
|
|
35948
|
-
if (bar.tempoAutomations.length > 0) {
|
|
35949
|
-
currentTempo = bar.tempoAutomations[0].value;
|
|
35950
|
-
}
|
|
35951
|
-
generateTracks(index, currentTick, currentTempo);
|
|
35952
|
-
if (bar.tempoAutomations.length > 0) {
|
|
35953
|
-
currentTempo = bar.tempoAutomations[bar.tempoAutomations.length - 1].value;
|
|
35954
|
-
}
|
|
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);
|
|
35955
36050
|
}
|
|
35956
36051
|
controller.moveNext();
|
|
35957
36052
|
previousMasterBar = bar;
|
|
@@ -35962,21 +36057,119 @@ class MidiFileGenerator {
|
|
|
35962
36057
|
// but where it ends according to the BPM and the remaining ticks.
|
|
35963
36058
|
if (syncPoints.length > 0) {
|
|
35964
36059
|
const lastSyncPoint = syncPoints[syncPoints.length - 1];
|
|
35965
|
-
const remainingTicks = controller.currentTick - lastSyncPoint.
|
|
36060
|
+
const remainingTicks = controller.currentTick - lastSyncPoint.synthTick;
|
|
35966
36061
|
if (remainingTicks > 0) {
|
|
35967
|
-
const
|
|
35968
|
-
|
|
35969
|
-
|
|
35970
|
-
|
|
35971
|
-
|
|
35972
|
-
//
|
|
35973
|
-
|
|
35974
|
-
|
|
35975
|
-
|
|
35976
|
-
|
|
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);
|
|
35977
36079
|
}
|
|
35978
36080
|
}
|
|
35979
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
|
+
}
|
|
35980
36173
|
}
|
|
35981
36174
|
static toChannelShort(data) {
|
|
35982
36175
|
const value = Math.max(-32768, Math.min(32767, data * 8 - 1));
|
|
@@ -38480,6 +38673,7 @@ class AlphaSynthWrapper {
|
|
|
38480
38673
|
value.playbackSpeed = this._playbackSpeed;
|
|
38481
38674
|
value.isLooping = this._isLooping;
|
|
38482
38675
|
value.midiEventsPlayedFilter = this._midiEventsPlayedFilter;
|
|
38676
|
+
this.ready.trigger();
|
|
38483
38677
|
}
|
|
38484
38678
|
else {
|
|
38485
38679
|
newUnregister.push(value.ready.on(() => {
|
|
@@ -43840,6 +44034,9 @@ class AudioElementBackingTrackSynthOutput {
|
|
|
43840
44034
|
const audioElement = document.createElement('audio');
|
|
43841
44035
|
audioElement.style.display = 'none';
|
|
43842
44036
|
document.body.appendChild(audioElement);
|
|
44037
|
+
audioElement.addEventListener('seeked', () => {
|
|
44038
|
+
this.updatePosition();
|
|
44039
|
+
});
|
|
43843
44040
|
audioElement.addEventListener('timeupdate', () => {
|
|
43844
44041
|
this.updatePosition();
|
|
43845
44042
|
});
|
|
@@ -60935,9 +61132,9 @@ class VersionInfo {
|
|
|
60935
61132
|
print(`build date: ${VersionInfo.date}`);
|
|
60936
61133
|
}
|
|
60937
61134
|
}
|
|
60938
|
-
VersionInfo.version = '1.6.0-alpha.
|
|
60939
|
-
VersionInfo.date = '2025-05-
|
|
60940
|
-
VersionInfo.commit = '
|
|
61135
|
+
VersionInfo.version = '1.6.0-alpha.1430';
|
|
61136
|
+
VersionInfo.date = '2025-05-29T22:21:21.689Z';
|
|
61137
|
+
VersionInfo.commit = '98a4c2bec8d71f2645008118d3b77fb40973e7fe';
|
|
60941
61138
|
|
|
60942
61139
|
/**
|
|
60943
61140
|
* A factory for custom layout engines.
|
|
@@ -61850,18 +62047,24 @@ class GpifWriter {
|
|
|
61850
62047
|
writeDom(parent, score) {
|
|
61851
62048
|
const gpif = parent.addElement('GPIF');
|
|
61852
62049
|
// just some values at the time this was implemented,
|
|
61853
|
-
gpif.addElement('GPVersion').innerText = '
|
|
62050
|
+
gpif.addElement('GPVersion').innerText = '8.1.3';
|
|
61854
62051
|
const gpRevision = gpif.addElement('GPRevision');
|
|
61855
|
-
gpRevision.
|
|
61856
|
-
gpRevision.attributes.set('
|
|
61857
|
-
gpRevision.
|
|
61858
|
-
|
|
61859
|
-
|
|
62052
|
+
gpRevision.attributes.set('required', '12024');
|
|
62053
|
+
gpRevision.attributes.set('recommended', '13000');
|
|
62054
|
+
gpRevision.innerText = '13007';
|
|
62055
|
+
const encoding = gpif.addElement('Encoding');
|
|
62056
|
+
encoding.addElement('EncodingDescription').innerText = 'GP8';
|
|
62057
|
+
const alphaTabComment = new XmlNode();
|
|
62058
|
+
alphaTabComment.nodeType = XmlNodeType.Comment;
|
|
62059
|
+
alphaTabComment.value = `Written by alphaTab ${VersionInfo.version} (${VersionInfo.commit})`;
|
|
62060
|
+
encoding.addChild(alphaTabComment);
|
|
61860
62061
|
this.writeScoreNode(gpif, score);
|
|
61861
62062
|
this.writeMasterTrackNode(gpif, score);
|
|
62063
|
+
this.writeBackingTrackNode(gpif, score);
|
|
61862
62064
|
this.writeAudioTracksNode(gpif, score);
|
|
61863
62065
|
this.writeTracksNode(gpif, score);
|
|
61864
62066
|
this.writeMasterBarsNode(gpif, score);
|
|
62067
|
+
this.writeAssets(gpif, score);
|
|
61865
62068
|
const bars = gpif.addElement('Bars');
|
|
61866
62069
|
const voices = gpif.addElement('Voices');
|
|
61867
62070
|
const beats = gpif.addElement('Beats');
|
|
@@ -61884,6 +62087,42 @@ class GpifWriter {
|
|
|
61884
62087
|
}
|
|
61885
62088
|
}
|
|
61886
62089
|
}
|
|
62090
|
+
writeAssets(parent, score) {
|
|
62091
|
+
if (!score.backingTrack?.rawAudioFile) {
|
|
62092
|
+
return;
|
|
62093
|
+
}
|
|
62094
|
+
const assets = parent.addElement('Assets');
|
|
62095
|
+
const asset = assets.addElement('Asset');
|
|
62096
|
+
asset.attributes.set('id', this.backingTrackAssetId);
|
|
62097
|
+
this.backingTrackAssetFileName = 'Content/Assets/backing-track';
|
|
62098
|
+
asset.addElement('EmbeddedFilePath').setCData(this.backingTrackAssetFileName);
|
|
62099
|
+
}
|
|
62100
|
+
writeBackingTrackNode(parent, score) {
|
|
62101
|
+
if (!score.backingTrack?.rawAudioFile) {
|
|
62102
|
+
return;
|
|
62103
|
+
}
|
|
62104
|
+
const backingTrackNode = parent.addElement('BackingTrack');
|
|
62105
|
+
const backingTrackAssetId = '0';
|
|
62106
|
+
this.backingTrackAssetId = backingTrackAssetId;
|
|
62107
|
+
backingTrackNode.addElement('IconId').innerText = '21';
|
|
62108
|
+
backingTrackNode.addElement('Color').innerText = '0 0 0';
|
|
62109
|
+
backingTrackNode.addElement('Name').setCData('Audio Track');
|
|
62110
|
+
backingTrackNode.addElement('ShortName').setCData('a.track');
|
|
62111
|
+
backingTrackNode.addElement('PlaybackState').innerText = 'Default';
|
|
62112
|
+
backingTrackNode.addElement('Enabled').innerText = 'true';
|
|
62113
|
+
backingTrackNode.addElement('Source').innerText = 'Local';
|
|
62114
|
+
backingTrackNode.addElement('AssetId').innerText = backingTrackAssetId;
|
|
62115
|
+
const channelStrip = backingTrackNode.addElement('ChannelStrip');
|
|
62116
|
+
channelStrip.addElement('Parameters').innerText =
|
|
62117
|
+
'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';
|
|
62118
|
+
channelStrip.addElement('YouTubeVideoUrl').innerText = '';
|
|
62119
|
+
channelStrip.addElement('Filter').innerText = '6';
|
|
62120
|
+
channelStrip.addElement('FramesPerPixel').innerText = '400';
|
|
62121
|
+
const framePadding = this.backingTrackFramePadding !== undefined ? this.backingTrackFramePadding : 0;
|
|
62122
|
+
backingTrackNode.addElement('FramePadding').innerText = `${framePadding}`;
|
|
62123
|
+
backingTrackNode.addElement('Semitones').innerText = '0';
|
|
62124
|
+
backingTrackNode.addElement('Cents').innerText = '0';
|
|
62125
|
+
}
|
|
61887
62126
|
writeNoteNode(parent, note) {
|
|
61888
62127
|
const noteNode = parent.addElement('Note');
|
|
61889
62128
|
noteNode.attributes.set('id', note.id.toString());
|
|
@@ -62540,6 +62779,12 @@ class GpifWriter {
|
|
|
62540
62779
|
initialTempoAutomation.addElement('Text').innerText = score.tempoLabel;
|
|
62541
62780
|
}
|
|
62542
62781
|
}
|
|
62782
|
+
const initialSyncPoint = score.masterBars[0].syncPoints
|
|
62783
|
+
? score.masterBars[0].syncPoints.find(p => p.ratioPosition === 0 && p.syncPointValue.barOccurence === 0)
|
|
62784
|
+
: undefined;
|
|
62785
|
+
const millisecondPadding = initialSyncPoint ? initialSyncPoint.syncPointValue.millisecondOffset : 0;
|
|
62786
|
+
this.backingTrackFramePadding = (-1 * ((millisecondPadding / 1000) * GpifWriter.SampleRate)) | 0;
|
|
62787
|
+
const modifiedTempoLookup = new Lazy(() => MidiFileGenerator.buildModifiedTempoLookup(score));
|
|
62543
62788
|
for (const mb of score.masterBars) {
|
|
62544
62789
|
for (const automation of mb.tempoAutomations) {
|
|
62545
62790
|
const tempoAutomation = automations.addElement('Automation');
|
|
@@ -62553,6 +62798,25 @@ class GpifWriter {
|
|
|
62553
62798
|
tempoAutomation.addElement('Text').innerText = automation.text;
|
|
62554
62799
|
}
|
|
62555
62800
|
}
|
|
62801
|
+
if (mb.syncPoints) {
|
|
62802
|
+
for (const syncPoint of mb.syncPoints) {
|
|
62803
|
+
const syncPointAutomation = automations.addElement('Automation');
|
|
62804
|
+
syncPointAutomation.addElement('Type').innerText = 'SyncPoint';
|
|
62805
|
+
syncPointAutomation.addElement('Linear').innerText = 'false';
|
|
62806
|
+
syncPointAutomation.addElement('Bar').innerText = mb.index.toString();
|
|
62807
|
+
syncPointAutomation.addElement('Position').innerText = syncPoint.ratioPosition.toString();
|
|
62808
|
+
syncPointAutomation.addElement('Visible').innerText = 'true';
|
|
62809
|
+
const value = syncPointAutomation.addElement('Value');
|
|
62810
|
+
value.addElement('BarIndex').innerText = mb.index.toString();
|
|
62811
|
+
value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue.barOccurence.toString();
|
|
62812
|
+
value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint).syncBpm.toString();
|
|
62813
|
+
value.addElement('OriginalTempo').innerText = score.tempo.toString();
|
|
62814
|
+
const frameOffset = (((syncPoint.syncPointValue.millisecondOffset - millisecondPadding) / 1000) *
|
|
62815
|
+
GpifWriter.SampleRate) |
|
|
62816
|
+
0;
|
|
62817
|
+
value.addElement('FrameOffset').innerText = frameOffset.toString();
|
|
62818
|
+
}
|
|
62819
|
+
}
|
|
62556
62820
|
}
|
|
62557
62821
|
}
|
|
62558
62822
|
writeAudioTracksNode(parent, score) {
|
|
@@ -63203,6 +63467,10 @@ class GpifWriter {
|
|
|
63203
63467
|
voiceNode.addElement('Beats').innerText = voice.beats.map(v => v.id).join(' ');
|
|
63204
63468
|
}
|
|
63205
63469
|
}
|
|
63470
|
+
// tests have shown that Guitar Pro seem to always work with 44100hz for the frame offsets,
|
|
63471
|
+
// they are NOT using the sample rate of the input file.
|
|
63472
|
+
// Downsampling a 44100hz ogg to 8000hz and using it in as audio track resulted in the same frame offset when placing sync points.
|
|
63473
|
+
GpifWriter.SampleRate = 44100;
|
|
63206
63474
|
GpifWriter.MidiProgramInfoLookup = new Map([
|
|
63207
63475
|
[0, new GpifMidiProgramInfo(GpifIconIds.Piano, 'Acoustic Piano')],
|
|
63208
63476
|
[1, new GpifMidiProgramInfo(GpifIconIds.Piano, 'Acoustic Piano')],
|
|
@@ -64940,11 +65208,11 @@ class ZipWriter {
|
|
|
64940
65208
|
}
|
|
64941
65209
|
|
|
64942
65210
|
/**
|
|
64943
|
-
* This ScoreExporter can write Guitar Pro 7 (gp) files.
|
|
65211
|
+
* This ScoreExporter can write Guitar Pro 7+ (gp) files.
|
|
64944
65212
|
*/
|
|
64945
65213
|
class Gp7Exporter extends ScoreExporter {
|
|
64946
65214
|
get name() {
|
|
64947
|
-
return 'Guitar Pro 7';
|
|
65215
|
+
return 'Guitar Pro 7-8';
|
|
64948
65216
|
}
|
|
64949
65217
|
writeScore(score) {
|
|
64950
65218
|
Logger.debug(this.name, 'Writing data entries');
|
|
@@ -64961,6 +65229,9 @@ class Gp7Exporter extends ScoreExporter {
|
|
|
64961
65229
|
fileSystem.writeEntry(new ZipEntry('Content/PartConfiguration', partConfiguration));
|
|
64962
65230
|
fileSystem.writeEntry(new ZipEntry('Content/LayoutConfiguration', layoutConfiguration));
|
|
64963
65231
|
fileSystem.writeEntry(new ZipEntry('Content/score.gpif', IOHelper.stringToBytes(gpifXml)));
|
|
65232
|
+
if (gpifWriter.backingTrackAssetFileName) {
|
|
65233
|
+
fileSystem.writeEntry(new ZipEntry(gpifWriter.backingTrackAssetFileName, score.backingTrack.rawAudioFile));
|
|
65234
|
+
}
|
|
64964
65235
|
fileSystem.end();
|
|
64965
65236
|
}
|
|
64966
65237
|
}
|