diff --git a/src/midi/MidiTickLookup.ts b/src/midi/MidiTickLookup.ts index 1f55ca08e..58770f112 100644 --- a/src/midi/MidiTickLookup.ts +++ b/src/midi/MidiTickLookup.ts @@ -29,6 +29,12 @@ export class MidiTickLookupFindBeatResult { */ public duration: number = 0; + /** + * Gets or sets the duration in midi ticks for how long this tick lookup is valid + * starting at the `currentBeatLookup.start` + */ + public tickDuration: number = 0; + /** * Gets or sets the beats ot highlight along the current beat. */ @@ -134,7 +140,8 @@ export class MidiTickLookup { currentBeatHint: MidiTickLookupFindBeatResult, tick: number ): MidiTickLookupFindBeatResult | null { - if (tick >= currentBeatHint.currentBeatLookup.start && tick < currentBeatHint.currentBeatLookup.end) { + const end = currentBeatHint.currentBeatLookup.start + currentBeatHint.tickDuration; + if (tick >= currentBeatHint.currentBeatLookup.start && tick < end) { // still same beat? return currentBeatHint; } else if ( @@ -190,9 +197,8 @@ export class MidiTickLookup { const result = new MidiTickLookupFindBeatResult(); result.currentBeatLookup = beat; result.nextBeatLookup = nextBeat; - result.duration = !nextBeat - ? MidiUtils.ticksToMillis(beat.end - beat.start, beat.masterBar.tempo) - : MidiUtils.ticksToMillis(nextBeat.start - beat.start, beat.masterBar.tempo); + result.tickDuration = !nextBeat ? beat.end - beat.start : nextBeat.start - beat.start; + result.duration = MidiUtils.ticksToMillis(result.tickDuration, beat.masterBar.tempo) result.beatsToHighlight = beat.beatsToHighlight; return result; } diff --git a/test-data/audio/cursor-snapping.gp b/test-data/audio/cursor-snapping.gp new file mode 100644 index 000000000..1e0148413 Binary files /dev/null and b/test-data/audio/cursor-snapping.gp differ diff --git a/test/audio/MidiTickLookup.test.ts b/test/audio/MidiTickLookup.test.ts new file mode 100644 index 000000000..2613e8ecb --- /dev/null +++ b/test/audio/MidiTickLookup.test.ts @@ -0,0 +1,52 @@ +import { ScoreLoader } from '@src/importer'; +import { AlphaSynthMidiFileHandler, MidiFile, MidiFileGenerator, MidiTickLookup } from '@src/midi'; +import { Duration, Score } from '@src/model'; +import { Settings } from '@src/Settings'; +import { TestPlatform } from '@test/TestPlatform'; + +describe('MidiTickLookupTest', () => { + async function buildLookup(score:Score, settings:Settings): Promise { + const midiFile = new MidiFile(); + const handler = new AlphaSynthMidiFileHandler(midiFile); + const midiFileGenerator = new MidiFileGenerator(score, settings, handler); + midiFileGenerator.generate(); + return midiFileGenerator.tickLookup; + } + + it('cursor-snapping', async () => { + const buffer = await TestPlatform.loadFile('test-data/audio/cursor-snapping.gp'); + const settings = new Settings(); + const score = ScoreLoader.loadScoreFromBytes(buffer, settings); + const lookup = await buildLookup(score, settings); + + // initial lookup should detect correctly first rest on first voice + // with the quarter rest on the second voice as next beat + const firstBeat = lookup.findBeat([score.tracks[0]], 0, null); + expect(firstBeat!.currentBeat.id).toEqual(score.tracks[0].staves[0].bars[0].voices[0].beats[0].id); + expect(firstBeat!.nextBeat!.id).toEqual(score.tracks[0].staves[0].bars[0].voices[1].beats[1].id); + expect(firstBeat!.currentBeat.duration).toEqual(Duration.Whole); + expect(firstBeat!.nextBeat!.duration).toEqual(Duration.Quarter); + + // Duration must only go to the next rest on the second voice despite the whole note + expect(firstBeat!.duration).toEqual(750); + expect(firstBeat!.tickDuration).toEqual(960); + + // Still playing first beat + const stillFirst = lookup.findBeat([score.tracks[0]], 400, firstBeat); + expect(stillFirst!.currentBeat.id).toEqual(score.tracks[0].staves[0].bars[0].voices[0].beats[0].id); + expect(stillFirst!.nextBeat!.id).toEqual(score.tracks[0].staves[0].bars[0].voices[1].beats[1].id); + expect(stillFirst!.currentBeat.duration).toEqual(Duration.Whole); + expect(stillFirst!.nextBeat!.duration).toEqual(Duration.Quarter); + expect(stillFirst!.duration).toEqual(750); + expect(stillFirst!.tickDuration).toEqual(960); + + // Now we're past the second rest heading to the third + const secondBeat = lookup.findBeat([score.tracks[0]], 970 /* after first quarter */, stillFirst); + expect(secondBeat!.currentBeat.id).toEqual(score.tracks[0].staves[0].bars[0].voices[1].beats[1].id); + expect(secondBeat!.nextBeat!.id).toEqual(score.tracks[0].staves[0].bars[0].voices[1].beats[2].id); + expect(secondBeat!.currentBeat.duration).toEqual(Duration.Quarter); + expect(secondBeat!.nextBeat!.duration).toEqual(Duration.Quarter); + expect(secondBeat!.duration).toEqual(750); + expect(secondBeat!.tickDuration).toEqual(960); + }); +});