Skip to content

Commit a7639d9

Browse files
authored
feat: Add option to create fresh sync points (#2161)
1 parent 8be4aea commit a7639d9

File tree

3 files changed

+89
-5
lines changed

3 files changed

+89
-5
lines changed

src/midi/MidiFileGenerator.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class PlayThroughContext {
6464
public currentTempo: number = 0;
6565
public automationToSyncPoint: Map<Automation, BackingTrackSyncPoint> = new Map<Automation, BackingTrackSyncPoint>();
6666
public syncPoints!: BackingTrackSyncPoint[];
67+
public createNewSyncPoints: boolean = false;
6768
}
6869

6970
/**
@@ -133,6 +134,7 @@ export class MidiFileGenerator {
133134
MidiFileGenerator.playThroughSong(
134135
this._score,
135136
this.syncPoints,
137+
false,
136138
(bar, previousMasterBar, currentTick, currentTempo, occurence) => {
137139
this.generateMasterBar(bar, previousMasterBar, currentTick, currentTempo, occurence);
138140
},
@@ -219,14 +221,16 @@ export class MidiFileGenerator {
219221
* It correctly handles repeats and places sync points accoridng to their absolute midi tick when they
220222
* need to be considered for synchronization.
221223
* @param score The song for which to regenerate the sync points.
224+
* @param createNew Whether a new set of sync points should be generated for the sync (start, stop and tempo changes).
222225
* @returns The generated sync points for usage in the backing track playback.
223226
*/
224-
public static generateSyncPoints(score: Score): BackingTrackSyncPoint[] {
227+
public static generateSyncPoints(score: Score, createNew: boolean = false): BackingTrackSyncPoint[] {
225228
const syncPoints: BackingTrackSyncPoint[] = [];
226229

227230
MidiFileGenerator.playThroughSong(
228231
score,
229232
syncPoints,
233+
createNew,
230234
(_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
231235
// no generation
232236
},
@@ -250,6 +254,7 @@ export class MidiFileGenerator {
250254
const context = MidiFileGenerator.playThroughSong(
251255
score,
252256
syncPoints,
257+
false,
253258
(_masterBar, _previousMasterBar, _currentTick, _currentTempo, _barOccurence) => {
254259
// no generation
255260
},
@@ -267,6 +272,7 @@ export class MidiFileGenerator {
267272
private static playThroughSong(
268273
score: Score,
269274
syncPoints: BackingTrackSyncPoint[],
275+
createNewSyncPoints: boolean,
270276
generateMasterBar: (
271277
masterBar: MasterBar,
272278
previousMasterBar: MasterBar | null,
@@ -282,6 +288,7 @@ export class MidiFileGenerator {
282288
const playContext = new PlayThroughContext();
283289
playContext.currentTempo = score.tempo;
284290
playContext.syncPoints = syncPoints;
291+
playContext.createNewSyncPoints = createNewSyncPoints;
285292
let previousMasterBar: MasterBar | null = null;
286293

287294
// store the previous played bar for repeats
@@ -328,16 +335,24 @@ export class MidiFileGenerator {
328335
// we need to assume some BPM for the last interpolated point.
329336
// if we have more than just a start point, we keep the BPM before the last manual sync point
330337
// otherwise we have no customized sync BPM known and keep the synthesizer one.
331-
backingTrackSyncPoint.syncBpm =
332-
syncPoints.length > 1 ? syncPoints[syncPoints.length - 2].syncBpm : lastSyncPoint.synthBpm;
338+
if (playContext.createNewSyncPoints) {
339+
backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
340+
backingTrackSyncPoint.synthBpm = lastSyncPoint.synthBpm;
341+
} else if (syncPoints.length === 1) {
342+
backingTrackSyncPoint.syncBpm = lastSyncPoint.synthBpm;
343+
} else {
344+
backingTrackSyncPoint.syncBpm = syncPoints[syncPoints.length - 2].syncBpm;
345+
}
333346

334347
backingTrackSyncPoint.synthTime =
335348
lastSyncPoint.synthTime + MidiUtils.ticksToMillis(remainingTicks, lastSyncPoint.synthBpm);
336349
backingTrackSyncPoint.syncTime =
337350
lastSyncPoint.syncTime + MidiUtils.ticksToMillis(remainingTicks, backingTrackSyncPoint.syncBpm);
338351

339352
// update the previous sync point according to the new time
340-
lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
353+
if (!playContext.createNewSyncPoints) {
354+
lastSyncPoint.updateSyncBpm(backingTrackSyncPoint.synthTime, backingTrackSyncPoint.syncTime);
355+
}
341356

342357
syncPoints.push(backingTrackSyncPoint);
343358
}
@@ -352,7 +367,9 @@ export class MidiFileGenerator {
352367
const duration = bar.calculateDuration();
353368
const barSyncPoints = bar.syncPoints;
354369
const barStartTick = context.synthTick;
355-
if (barSyncPoints) {
370+
if (context.createNewSyncPoints) {
371+
MidiFileGenerator.processBarTimeWithNewSyncPoints(bar, occurence, context);
372+
} else if (barSyncPoints) {
356373
MidiFileGenerator.processBarTimeWithSyncPoints(bar, occurence, context);
357374
} else {
358375
MidiFileGenerator.processBarTimeNoSyncPoints(bar, context);
@@ -367,6 +384,51 @@ export class MidiFileGenerator {
367384
}
368385
}
369386

387+
private static processBarTimeWithNewSyncPoints(bar: MasterBar, occurence: number, context: PlayThroughContext) {
388+
// start marker
389+
const barStartTick = context.synthTick;
390+
if (bar.index === 0 && occurence === 0) {
391+
context.currentTempo = bar.score.tempo;
392+
393+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
394+
backingTrackSyncPoint.masterBarIndex = bar.index;
395+
backingTrackSyncPoint.masterBarOccurence = occurence;
396+
backingTrackSyncPoint.synthTick = barStartTick;
397+
backingTrackSyncPoint.synthBpm = context.currentTempo;
398+
backingTrackSyncPoint.synthTime = context.synthTime;
399+
backingTrackSyncPoint.syncBpm = context.currentTempo;
400+
backingTrackSyncPoint.syncTime = context.synthTime;
401+
402+
context.syncPoints.push(backingTrackSyncPoint);
403+
}
404+
405+
// walk tempo changes and create points
406+
const duration = bar.calculateDuration();
407+
for (const change of bar.tempoAutomations) {
408+
const absoluteTick = barStartTick + change.ratioPosition * duration;
409+
const tickOffset = absoluteTick - context.synthTick;
410+
if (tickOffset > 0) {
411+
context.synthTick = absoluteTick;
412+
context.synthTime += MidiUtils.ticksToMillis(tickOffset, context.currentTempo);
413+
}
414+
415+
if (change.value !== context.currentTempo) {
416+
context.currentTempo = change.value;
417+
418+
const backingTrackSyncPoint = new BackingTrackSyncPoint();
419+
backingTrackSyncPoint.masterBarIndex = bar.index;
420+
backingTrackSyncPoint.masterBarOccurence = occurence;
421+
backingTrackSyncPoint.synthTick = absoluteTick;
422+
backingTrackSyncPoint.synthBpm = context.currentTempo;
423+
backingTrackSyncPoint.synthTime = context.synthTime;
424+
backingTrackSyncPoint.syncBpm = context.currentTempo;
425+
backingTrackSyncPoint.syncTime = context.synthTime;
426+
427+
context.syncPoints.push(backingTrackSyncPoint);
428+
}
429+
}
430+
}
431+
370432
private static processBarTimeWithSyncPoints(bar: MasterBar, occurence: number, context: PlayThroughContext) {
371433
const barStartTick = context.synthTick;
372434
const duration = bar.calculateDuration();

test/audio/SyncPoint.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ describe('SyncPointTests', () => {
124124
}
125125
});
126126

127+
it('sync-point-generation-new', async () => {
128+
const score = await syncPointTestScore();
129+
130+
const update = MidiFileGenerator.generateSyncPoints(score, true);
131+
expect(
132+
update.map(
133+
p =>
134+
`${p.masterBarIndex},${p.masterBarOccurence},${p.synthBpm},${p.syncBpm},${p.synthTime},${p.syncTime}`
135+
)
136+
).toMatchSnapshot();
137+
138+
});
139+
127140
it('modified-tempo-lookup', async () => {
128141
const score = await syncPointTestScore();
129142

test/audio/__snapshots__/SyncPoint.test.ts.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,15 @@ Array [
403403
]
404404
`;
405405

406+
exports[`SyncPointTests sync-point-generation-new 1`] = `
407+
Array [
408+
"0,0,120,120,0,0",
409+
"4,0,60,60,8000,8000",
410+
"8,0,80,80,24000,24000",
411+
"11,0,80,80,48000,48000",
412+
]
413+
`;
414+
406415
exports[`SyncPointTests sync-point-update 1`] = `
407416
Array [
408417
"0,0,120,120,0,0",

0 commit comments

Comments
 (0)