import { describe, expect, it } from 'vitest'; import { AFTERGLOW_LOOP_END_SAMPLE, AFTERGLOW_LOOP_START_SAMPLE, AFTERGLOW_TRANSITION_SECONDS, DRIVE_LIFT_LOOP_SAMPLES, DRIVE_LIFT_LOOP_SECONDS, MAIN_DURATION_SAMPLES, MAIN_DURATION_SECONDS, MAIN_PHRASE_BARS, MUSIC_BAR_SAMPLES, MUSIC_BAR_SECONDS, MUSIC_BEAT_SAMPLES, MUSIC_BEAT_SECONDS, INTRO_IN_END_SECONDS, SOUNDTRACK_SAMPLE_RATE, mainPhraseOffsetSeconds, nextBeatTime, nextSafeBarTime, phaseAlignedLoopOffset, resolveMainSchedule, resolveInitialPlayback, } from '@/audio/soundtrack-transport'; describe('soundtrack transport decisions', () => { it('starts at the intro only on Start and otherwise chooses the matching main phrase', () => { expect(resolveInitialPlayback({ sectionId: 'start', mood: 'open', atStart: true, entryPhrase: 0, generation: 0, })).toEqual({ kind: 'intro' }); expect(resolveInitialPlayback({ sectionId: 'infrastructure', mood: 'shadow', atStart: false, entryPhrase: 3, generation: 1, })).toEqual({ kind: 'main', offsetSeconds: mainPhraseOffsetSeconds(3) }); expect(mainPhraseOffsetSeconds(Number.NaN)).toBe(0); const finalPhraseSample = Math.round( 7 * MAIN_PHRASE_BARS * MUSIC_BAR_SECONDS * SOUNDTRACK_SAMPLE_RATE, ); expect(mainPhraseOffsetSeconds(99)).toBe(finalPhraseSample / SOUNDTRACK_SAMPLE_RATE); }); it('coalesces an exit onto the next bar with enough time for a clean crossfade', () => { const anchorTime = 1; const now = anchorTime + MUSIC_BAR_SECONDS * 2 + 0.1; const transitionTime = nextSafeBarTime(now, anchorTime, 0.25); expect(transitionTime).toBeCloseTo(anchorTime + MUSIC_BAR_SECONDS * 3, 8); expect(transitionTime).toBeGreaterThanOrEqual(now + 0.25); expect(nextSafeBarTime(anchorTime, anchorTime, 0)).toBe(anchorTime); const earliestIntroExit = nextSafeBarTime( anchorTime + INTRO_IN_END_SECONDS - 0.35, anchorTime, 0.35, ); expect(earliestIntroExit).toBeCloseTo(anchorTime + MUSIC_BAR_SECONDS * 2, 8); }); it('pins the short adaptive regions to whole samples', () => { expect(MUSIC_BEAT_SAMPLES).toBe(33_488); expect(MUSIC_BAR_SAMPLES).toBe(133_953); expect(DRIVE_LIFT_LOOP_SAMPLES).toBe(535_814); expect(AFTERGLOW_LOOP_START_SAMPLE).toBe(133_953); expect(AFTERGLOW_LOOP_END_SAMPLE).toBe(1_205_581); expect(MAIN_DURATION_SAMPLES).toBe(8_573_023); }); it('derives one immutable main anchor and the final-bar afterglow handoff', () => { expect(resolveMainSchedule({ startTime: 10, offsetSeconds: mainPhraseOffsetSeconds(6), bufferDuration: MAIN_DURATION_SECONDS, })).toEqual({ sourceStartTime: 10, sourceOffsetSeconds: mainPhraseOffsetSeconds(6), musicalAnchorTime: 10 - mainPhraseOffsetSeconds(6), sourceEndTime: 10 + MAIN_DURATION_SECONDS - mainPhraseOffsetSeconds(6), afterglowStartTime: 10 + MAIN_DURATION_SECONDS - mainPhraseOffsetSeconds(6) - AFTERGLOW_TRANSITION_SECONDS, }); }); it('aligns a late loop source and sustained entry to the master beat', () => { expect(phaseAlignedLoopOffset(13, 10, DRIVE_LIFT_LOOP_SECONDS)) .toBeCloseTo(3, 8); expect(nextBeatTime(11.45, 10)).toBeCloseTo( 10 + 3 * MUSIC_BEAT_SECONDS, 8, ); }); it('keeps one-sample-rounded beat and bar boundaries on the current boundary', () => { const oneSample = 1 / SOUNDTRACK_SAMPLE_RATE; expect(nextBeatTime(MUSIC_BEAT_SECONDS + oneSample, 0)) .toBeCloseTo(MUSIC_BEAT_SECONDS, 8); expect(nextSafeBarTime(MUSIC_BAR_SECONDS + oneSample, 0, 0)) .toBeCloseTo(MUSIC_BAR_SECONDS, 8); }); });