Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/alphatab/src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ export class GpifWriter {
}

if (beat.showTimer) {
beatNode.addElement('Timer').innerText = (beat.timer ?? 0).toString();
beatNode.addElement('Timer').innerText = Math.round(beat.timer ?? 0).toString();
}

this._writeBeatProperties(beatNode, beat);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ export class AlphaTex1LanguageDefinitions {
[
[[106], 0],
[[106], 0],
[[106], 0],
[[106], 2],
[[106], 3]
]
]
Expand Down
2 changes: 1 addition & 1 deletion packages/alphatab/src/midi/MidiUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class MidiUtils {
* @returns The converted duration in milliseconds.
*/
public static ticksToMillis(ticks: number, tempo: number): number {
return (ticks * (60000.0 / (tempo * MidiUtils.QuarterTime))) | 0;
return (ticks * 60000.0) / (tempo * MidiUtils.QuarterTime);
}

/**
Expand Down
Binary file modified packages/alphatab/test-data/audio/export-sync-points.pcm
Binary file not shown.
26 changes: 26 additions & 0 deletions packages/alphatab/test/audio/MidiFileGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,32 @@ describe('MidiFileGeneratorTest', () => {
expect(actualTimers.join(',')).toBe(expectedTimers.join(','));
});

/**
* See #2952
*/
it('beat-timer-non-integer-bar-duration', () => {
// a 4/4 bar at 148 BPM lasts 1621.6216...ms
const barCount = 200;
const barTex: string[] = [];
for (let i = 0; i < barCount; i++) {
barTex.push('C4.4 { timer } C4.4 * 3');
}
const score: Score = parseTex(`
\\tempo 148
.
${barTex.join(' | ')}
`);

const handler: FlatMidiEventGenerator = new FlatMidiEventGenerator();
const generator: MidiFileGenerator = new MidiFileGenerator(score, null, handler);
generator.generate();

const bars = score.tracks[0].staves[0].bars;
for (let i = 0; i < barCount; i++) {
expect(bars[i].voices[0].beats[0].timer, `bar ${i}`).toBeCloseTo((i * 4 * 60000) / 148, 6);
}
});

describe('metronome', () => {
function testMetronome(tex: string, expectedClicks: string) {
const score: Score = parseTex(tex);
Expand Down
85 changes: 85 additions & 0 deletions packages/alphatab/test/audio/SyncPoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import type { AlphaTabMetronomeEvent } from '@coderline/alphatab/midi/MidiEvent'
import { MidiFile } from '@coderline/alphatab/midi/MidiFile';
import { MidiFileGenerator } from '@coderline/alphatab/midi/MidiFileGenerator';
import type { BackingTrack } from '@coderline/alphatab/model/BackingTrack';
import type { Score } from '@coderline/alphatab/model/Score';
import { Settings } from '@coderline/alphatab/Settings';
import { BackingTrackPlayer, type IBackingTrackSynthOutput } from '@coderline/alphatab/synth/BackingTrackPlayer';
import {
ExternalMediaPlayer,
type IExternalMediaHandler,
type IExternalMediaSynthOutput
} from '@coderline/alphatab/synth/ExternalMediaPlayer';
import type { IAlphaSynth } from '@coderline/alphatab/synth/IAlphaSynth';
import type { IAudioSampleSynthesizer } from '@coderline/alphatab/synth/IAudioSampleSynthesizer';
import type { ISynthOutputDevice } from '@coderline/alphatab/synth/ISynthOutput';
import { MidiFileSequencer } from '@coderline/alphatab/synth/MidiFileSequencer';
Expand Down Expand Up @@ -357,6 +359,89 @@ describe('SyncPointTests', () => {
expect(events.map(e => `${e.currentTime},${e.originalTempo},${e.modifiedTempo}`)).toMatchSnapshot();
expect(testOutput.seekTimes).toMatchSnapshot();
});

/**
* See #2952
*/
describe('non-integer-bar-duration', () => {
// a 4/4 bar at 148 BPM lasts 1621.6216...ms
const tempo = 148;
const barCount = 201;
const ticksPerBar = 3840;
const barMillis = (4 * 60000) / tempo;
const lastSyncTime = (barCount - 1) * barMillis;

function createScore(): Score {
const barTex: string[] = [];
for (let i = 0; i < barCount; i++) {
barTex.push('C4.4 * 4');
}
return ScoreLoader.loadAlphaTex(`
\\tempo ${tempo}
.
${barTex.join(' | ')}
.
\\sync 0 0 0
\\sync ${barCount - 1} 0 ${lastSyncTime}
`);
}

function createGenerator(score: Score, midi: MidiFile): MidiFileGenerator {
const generator = new MidiFileGenerator(score, new Settings(), new AlphaSynthMidiFileHandler(midi));
generator.generate();
return generator;
}

function expectPlayerInSync(player: IAlphaSynth, seek: (time: number) => void) {
const score = createScore();
const midi = new MidiFile();
const generator = createGenerator(score, midi);

player.loadMidiFile(midi);
player.loadBackingTrack(score);
player.updateSyncPoints(generator.syncPoints);

let tick = -1;
player.positionChanged.on(e => {
tick = e.currentTick;
});
player.play();

for (let bar = 0; bar < barCount; bar++) {
seek(bar * barMillis);
const expectedTick = bar * ticksPerBar;
expect(Math.abs(tick - expectedTick), `bar ${bar}: tick ${tick}`).toBeLessThanOrEqual(1);
}
}

it('sync-point-times', () => {
const generator = createGenerator(createScore(), new MidiFile());

const syncPoints = generator.syncPoints;
expect(syncPoints[1].synthTick).toBe((barCount - 1) * ticksPerBar);
expect(syncPoints[1].syncTime).toBe(lastSyncTime);
expect(syncPoints[1].synthTime).toBeCloseTo(lastSyncTime, 6);
expect(syncPoints[0].syncBpm).toBeCloseTo(tempo, 6);
});

it('external-media-position', () => {
const player = new ExternalMediaPlayer(500);
const output = player.output as IExternalMediaSynthOutput;
const mediaHandler = new TestExternalMediaHandler(output);
mediaHandler.backingTrackDuration = lastSyncTime + barMillis;
output.handler = mediaHandler;

expectPlayerInSync(player, time => mediaHandler.simulateSeek(time));
});

it('backing-track-position', () => {
const output = new TestBackingTrackOutput();
output.backingTrackDuration = lastSyncTime + barMillis;
const player = new BackingTrackPlayer(output, 500);

expectPlayerInSync(player, time => output.simulateSeek(time));
});
});
});

describe('MidiFileSequencerCountInTests', () => {
Expand Down
122 changes: 61 additions & 61 deletions packages/alphatab/test/audio/__snapshots__/SyncPoint.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exports[`SyncPointTests > modified-tempo-lookup 1`] = `
"22010.34013605442 => 120.04899959166994",
"24009.52380952381 => 120.04899959167003",
"30007.074829931975 => 80.01088583480758",
"33006.666666666664 => 80.01778172927317",
"33006.666666666664 => 80.01088583480758",
]
`;

Expand Down Expand Up @@ -50,15 +50,15 @@ exports[`SyncPointTests > playback-fast-backing-track 1`] = `
"18496.530140155122,80,160.02177166961516",
"18996.59817662267,80,160.02177166961516",
"19496.66621309022,80,160.02177166961516",
"19996.77706156924,80,160.03556345854633",
"20496.888197377197,80,160.03556345854633",
"20996.999333185155,80,160.03556345854633",
"21497.11046899311,80,160.03556345854633",
"21997.221604801067,80,160.03556345854633",
"22497.332740609025,80,160.03556345854633",
"22997.443876416983,80,160.03556345854633",
"23497.55501222494,80,160.03556345854633",
"23997.6661480329,80,160.03556345854633",
"19996.734249557765,80,160.02177166961516",
"20496.80228602531,80,160.02177166961516",
"20996.87032249286,80,160.02177166961516",
"21496.938358960408,80,160.02177166961516",
"21997.006395427954,80,160.02177166961516",
"22497.0744318955,80,160.02177166961516",
"22997.14246836305,80,160.02177166961516",
"23497.210504830597,80,160.02177166961516",
"23997.278541298143,80,160.02177166961516",
]
`;

Expand Down Expand Up @@ -98,15 +98,15 @@ exports[`SyncPointTests > playback-fast-external-media 1`] = `
"18496.530140155122,80,160.02177166961516",
"18996.59817662267,80,160.02177166961516",
"19496.66621309022,80,160.02177166961516",
"19996.77706156924,80,160.03556345854633",
"20496.888197377197,80,160.03556345854633",
"20996.999333185155,80,160.03556345854633",
"21497.11046899311,80,160.03556345854633",
"21997.221604801067,80,160.03556345854633",
"22497.332740609025,80,160.03556345854633",
"22997.443876416983,80,160.03556345854633",
"23497.55501222494,80,160.03556345854633",
"23997.6661480329,80,160.03556345854633",
"19996.734249557765,80,160.02177166961516",
"20496.80228602531,80,160.02177166961516",
"20996.87032249286,80,160.02177166961516",
"21496.938358960408,80,160.02177166961516",
"21997.006395427954,80,160.02177166961516",
"22497.0744318955,80,160.02177166961516",
"22997.14246836305,80,160.02177166961516",
"23497.210504830597,80,160.02177166961516",
"23997.278541298143,80,160.02177166961516",
]
`;

Expand Down Expand Up @@ -179,24 +179,24 @@ exports[`SyncPointTests > playback-normal-backing-track 1`] = `
"37993.19635324534,80,80.01088583480758",
"38493.26438971289,80,80.01088583480758",
"38993.33242618044,80,80.01088583480758",
"39493.44298733052,80,80.01778172927317",
"39993.55412313848,80,80.01778172927317",
"40493.665258946436,80,80.01778172927317",
"40993.77639475439,80,80.01778172927317",
"41493.88753056235,80,80.01778172927317",
"41993.99866637031,80,80.01778172927317",
"42494.10980217827,80,80.01778172927317",
"42994.22093798622,80,80.01778172927317",
"43494.332073794176,80,80.01778172927317",
"43994.443209602134,80,80.01778172927317",
"44494.55434541009,80,80.01778172927317",
"44994.66548121805,80,80.01778172927317",
"45494.77661702601,80,80.01778172927317",
"45994.887752833965,80,80.01778172927317",
"46494.99888864192,80,80.01778172927317",
"46995.11002444988,80,80.01778172927317",
"47495.22116025784,80,80.01778172927317",
"47995.3322960658,80,80.01778172927317",
"39493.40046264798,80,80.01088583480758",
"39993.46849911553,80,80.01088583480758",
"40493.53653558308,80,80.01088583480758",
"40993.60457205062,80,80.01088583480758",
"41493.67260851817,80,80.01088583480758",
"41993.74064498572,80,80.01088583480758",
"42493.808681453265,80,80.01088583480758",
"42993.876717920815,80,80.01088583480758",
"43493.94475438836,80,80.01088583480758",
"43994.01279085591,80,80.01088583480758",
"44494.08082732346,80,80.01088583480758",
"44994.148863791,80,80.01088583480758",
"45494.21690025855,80,80.01088583480758",
"45994.2849367261,80,80.01088583480758",
"46494.35297319364,80,80.01088583480758",
"46994.42100966119,80,80.01088583480758",
"47494.48904612874,80,80.01088583480758",
"47994.557082596286,80,80.01088583480758",
]
`;

Expand Down Expand Up @@ -269,24 +269,24 @@ exports[`SyncPointTests > playback-normal-external-media 1`] = `
"37993.19635324534,80,80.01088583480758",
"38493.26438971289,80,80.01088583480758",
"38993.33242618044,80,80.01088583480758",
"39493.44298733052,80,80.01778172927317",
"39993.55412313848,80,80.01778172927317",
"40493.665258946436,80,80.01778172927317",
"40993.77639475439,80,80.01778172927317",
"41493.88753056235,80,80.01778172927317",
"41993.99866637031,80,80.01778172927317",
"42494.10980217827,80,80.01778172927317",
"42994.22093798622,80,80.01778172927317",
"43494.332073794176,80,80.01778172927317",
"43994.443209602134,80,80.01778172927317",
"44494.55434541009,80,80.01778172927317",
"44994.66548121805,80,80.01778172927317",
"45494.77661702601,80,80.01778172927317",
"45994.887752833965,80,80.01778172927317",
"46494.99888864192,80,80.01778172927317",
"46995.11002444988,80,80.01778172927317",
"47495.22116025784,80,80.01778172927317",
"47995.3322960658,80,80.01778172927317",
"39493.40046264798,80,80.01088583480758",
"39993.46849911553,80,80.01088583480758",
"40493.53653558308,80,80.01088583480758",
"40993.60457205062,80,80.01088583480758",
"41493.67260851817,80,80.01088583480758",
"41993.74064498572,80,80.01088583480758",
"42493.808681453265,80,80.01088583480758",
"42993.876717920815,80,80.01088583480758",
"43493.94475438836,80,80.01088583480758",
"43994.01279085591,80,80.01088583480758",
"44494.08082732346,80,80.01088583480758",
"44994.148863791,80,80.01088583480758",
"45494.21690025855,80,80.01088583480758",
"45994.2849367261,80,80.01088583480758",
"46494.35297319364,80,80.01088583480758",
"46994.42100966119,80,80.01088583480758",
"47494.48904612874,80,80.01088583480758",
"47994.557082596286,80,80.01088583480758",
]
`;

Expand All @@ -311,7 +311,7 @@ exports[`SyncPointTests > seek-fast-backing-track 2`] = `
4000,
8001.292517006803,
23343.12925170068,
36006,
36006.258503401354,
]
`;

Expand All @@ -336,7 +336,7 @@ exports[`SyncPointTests > seek-fast-external-media 2`] = `
4000,
8001.292517006803,
23343.12925170068,
36006,
36006.258503401354,
]
`;

Expand Down Expand Up @@ -398,8 +398,8 @@ exports[`SyncPointTests > sync-point-generation 1`] = `
"8,0,80,120.04899959166994,24000,22010.34013605442",
"9,0,80,120.04899959167003,27000,24009.52380952381",
"8,1,80,80.01088583480758,36000,30007.074829931975",
"9,1,80,80.01778172927317,39000,33006.666666666664",
"11,0,80,80.01088583480758,48000,42004.666666666664",
"9,1,80,80.01088583480758,39000,33006.666666666664",
"11,0,80,80.01088583480758,48000,42005.44217687073",
]
`;

Expand All @@ -422,7 +422,7 @@ exports[`SyncPointTests > sync-point-update 1`] = `
"8,0,80,120.04899959166994,24000,22010.34013605442",
"9,0,80,120.04899959167003,27000,24009.52380952381",
"8,1,80,80.01088583480758,36000,30007.074829931975",
"9,1,80,80.01778172927317,39000,33006.666666666664",
"11,0,80,80.01088583480758,48000,42004.666666666664",
"9,1,80,80.01088583480758,39000,33006.666666666664",
"11,0,80,80.01088583480758,48000,42005.44217687073",
]
`;
14 changes: 14 additions & 0 deletions packages/alphatab/test/exporter/Gp7Exporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ describe('Gp7ExporterTest', () => {
expect(gpif).toContain('<MultiVoice>1></MultiVoice>');
});

it('beat-timer-as-integer', () => {
const score = ScoreLoader.loadAlphaTex(`
C4.4 { timer } C4.4 { timer }
`);
const beats = score.tracks[0].staves[0].bars[0].voices[0].beats;
beats[0].timer = 1621.4;
beats[1].timer = 3243.6;

const gpif = readExportedGpif(exportGp7(score));

expect(gpif).toContain('<Timer>1621</Timer>');
expect(gpif).toContain('<Timer>3244</Timer>');
});

it('alphatex-drums-to-gp7', () => {
const tex = `\\track "Drums" { instrument percussion }
\\clef neutral
Expand Down
Loading
Loading