Skip to content

Commit 8be4aea

Browse files
authored
fix: Wrong modified tempo if file has no sync points (#2159)
1 parent b08102e commit 8be4aea

File tree

3 files changed

+122
-14
lines changed

3 files changed

+122
-14
lines changed

src/synth/MidiFileSequencer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ export class MidiFileSequencer {
480480
if (tempoChangeIndex !== state.tempoChangeIndex) {
481481
state.tempoChangeIndex = tempoChangeIndex;
482482
state.currentTempo = state.tempoChanges[state.tempoChangeIndex].bpm;
483+
484+
if (state.syncPoints.length === 0) {
485+
state.syncPointTempo = state.currentTempo;
486+
}
483487
}
484488
}
485489

test/audio/SyncPoint.test.ts

Lines changed: 103 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,76 @@ import {
1111
type IExternalMediaHandler,
1212
type IExternalMediaSynthOutput
1313
} from '@src/synth/ExternalMediaPlayer';
14+
import type { IAudioSampleSynthesizer } from '@src/synth/IAudioSampleSynthesizer';
1415
import type { ISynthOutputDevice } from '@src/synth/ISynthOutput';
16+
import { MidiFileSequencer } from '@src/synth/MidiFileSequencer';
1517
import type { PositionChangedEventArgs } from '@src/synth/PositionChangedEventArgs';
18+
import type { Hydra } from '@src/synth/soundfont/Hydra';
19+
import type { SynthEvent } from '@src/synth/synthesis/SynthEvent';
1620
import { FlatMidiEventGenerator } from '@test/audio/FlatMidiEventGenerator';
1721
import { TestPlatform } from '@test/TestPlatform';
1822
import { expect } from 'chai';
1923

2024
describe('SyncPointTests', () => {
21-
it('sync-point-update', () => {
22-
// MidiFileSequencer
23-
// sync points and tempo changes -> expect interpolation
25+
it('sync-point-update', async () => {
26+
const score = await syncPointTestScore();
27+
28+
const midi = new MidiFile();
29+
const handler = new AlphaSynthMidiFileHandler(midi);
30+
const generator = new MidiFileGenerator(score, new Settings(), handler);
31+
generator.generate();
32+
33+
const sequencer = new MidiFileSequencer(new EmptyAudioSynthesizer());
34+
sequencer.loadMidi(midi);
35+
sequencer.mainUpdateSyncPoints(generator.syncPoints);
36+
37+
expect(
38+
sequencer.currentSyncPoints.map(
39+
p =>
40+
`${p.masterBarIndex},${p.masterBarOccurence},${p.synthBpm},${p.syncBpm},${p.synthTime},${p.syncTime}`
41+
)
42+
).toMatchSnapshot();
2443
});
2544

26-
it('backing-track-time-mapping', () => {
27-
// MidiFileSequencer
28-
// do a variety of lookups along the time axis.
29-
// - sequentially (playback)
30-
// - jumps (seeks back and forth)
31-
// check
32-
// - updated syncPointIndex
33-
// - interpolated time
34-
// - reverse lookup with mainTimePositionToBackingTrack
45+
/**
46+
* See #2158
47+
*/
48+
it('no-syncpoints-modified-tempo-', async () => {
49+
const score = ScoreLoader.loadAlphaTex(`
50+
.
51+
\\tempo 90
52+
C4 * 4 |
53+
\\tempo 120
54+
C4 * 4
55+
`);
56+
57+
const midi = new MidiFile();
58+
const handler = new AlphaSynthMidiFileHandler(midi);
59+
const generator = new MidiFileGenerator(score, new Settings(), handler);
60+
generator.generate();
61+
62+
const sequencer = new MidiFileSequencer(new EmptyAudioSynthesizer());
63+
sequencer.loadMidi(midi);
64+
65+
sequencer.currentUpdateCurrentTempo(0);
66+
expect(sequencer.currentTempo).to.equal(90);
67+
expect(sequencer.modifiedTempo).to.equal(90);
68+
69+
sequencer.currentUpdateCurrentTempo(1000);
70+
expect(sequencer.currentTempo).to.equal(90);
71+
expect(sequencer.modifiedTempo).to.equal(90);
72+
73+
sequencer.currentUpdateCurrentTempo(2000);
74+
expect(sequencer.currentTempo).to.equal(90);
75+
expect(sequencer.modifiedTempo).to.equal(90);
76+
77+
sequencer.currentUpdateCurrentTempo(3000);
78+
expect(sequencer.currentTempo).to.equal(120);
79+
expect(sequencer.modifiedTempo).to.equal(120);
80+
81+
sequencer.currentUpdateCurrentTempo(4000);
82+
expect(sequencer.currentTempo).to.equal(120);
83+
expect(sequencer.modifiedTempo).to.equal(120);
3584
});
3685

3786
async function syncPointTestScore() {
@@ -55,7 +104,10 @@ describe('SyncPointTests', () => {
55104
generator.generate();
56105

57106
expect(
58-
generator.syncPoints.map(p => `${p.masterBarIndex},${p.masterBarOccurence},${p.synthBpm},${p.syncBpm},${p.synthTime},${p.syncTime}`)
107+
generator.syncPoints.map(
108+
p =>
109+
`${p.masterBarIndex},${p.masterBarOccurence},${p.synthBpm},${p.syncBpm},${p.synthTime},${p.syncTime}`
110+
)
59111
).toMatchSnapshot();
60112

61113
const update = MidiFileGenerator.generateSyncPoints(score);
@@ -327,7 +379,7 @@ class TestBackingTrackOutput implements IBackingTrackSynthOutput {
327379
public sampleRequest: IEventEmitter = new EventEmitter();
328380

329381
public async enumerateOutputDevices(): Promise<ISynthOutputDevice[]> {
330-
return ([] as ISynthOutputDevice[]);
382+
return [] as ISynthOutputDevice[];
331383
}
332384
public async setOutputDevice(device: ISynthOutputDevice | null): Promise<void> {}
333385
public async getOutputDevice(): Promise<ISynthOutputDevice | null> {
@@ -365,3 +417,40 @@ class TestExternalMediaHandler implements IExternalMediaHandler {
365417
play(): void {}
366418
pause(): void {}
367419
}
420+
421+
class EmptyAudioSynthesizer implements IAudioSampleSynthesizer {
422+
public masterVolume: number = 0;
423+
public metronomeVolume: number = 0;
424+
public outSampleRate: number = 44100;
425+
public currentTempo: number = 120;
426+
public timeSignatureNumerator: number = 4;
427+
public timeSignatureDenominator: number = 4;
428+
public activeVoiceCount: number = 0;
429+
public noteOffAll(immediate: boolean): void {}
430+
public resetSoft(): void {}
431+
public resetPresets(): void {}
432+
public loadPresets(
433+
hydra: Hydra,
434+
instrumentPrograms: Set<number>,
435+
percussionKeys: Set<number>,
436+
append: boolean
437+
): void {}
438+
public setupMetronomeChannel(metronomeVolume: number): void {}
439+
public synthesizeSilent(sampleCount: number): void {}
440+
public dispatchEvent(synthEvent: SynthEvent): void {}
441+
public synthesize(buffer: Float32Array, bufferPos: number, sampleCount: number): SynthEvent[] {
442+
return [];
443+
}
444+
public applyTranspositionPitches(transpositionPitches: Map<number, number>): void {}
445+
public setChannelTranspositionPitch(channel: number, semitones: number): void {}
446+
public channelSetMute(channel: number, mute: boolean): void {}
447+
public channelSetSolo(channel: number, solo: boolean): void {}
448+
public resetChannelStates(): void {}
449+
public channelSetMixVolume(channel: number, volume: number): void {}
450+
public hasSamplesForProgram(program: number): boolean {
451+
return true;
452+
}
453+
public hasSamplesForPercussion(key: number): boolean {
454+
return true;
455+
}
456+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,18 @@ Array [
402402
"11,0,80,80.01088583480758,48000,42004.666666666664",
403403
]
404404
`;
405+
406+
exports[`SyncPointTests sync-point-update 1`] = `
407+
Array [
408+
"0,0,120,120,0,0",
409+
"2,0,120,240,4000,4000",
410+
"3,0,120,240,6000,5000",
411+
"4,0,60,59.96124953261497,8000,6000",
412+
"5,0,60,59.961249532614985,12000,10002.585034013606",
413+
"8,0,80,120.04899959166994,24000,22010.34013605442",
414+
"9,0,80,120.04899959167003,27000,24009.52380952381",
415+
"8,1,80,80.01088583480758,36000,30007.074829931975",
416+
"9,1,80,80.01778172927317,39000,33006.666666666664",
417+
"11,0,80,80.01088583480758,48000,42004.666666666664",
418+
]
419+
`;

0 commit comments

Comments
 (0)