import { describe, expect, it } from 'vitest'; import { MAIN_PHRASE_BARS, MUSIC_BAR_SECONDS, INTRO_IN_END_SECONDS, SOUNDTRACK_SAMPLE_RATE, mainPhraseOffsetSeconds, nextSafeBarTime, 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({ atStart: true, bias: 'open', entryPhrase: 0, intensity: 0, })).toEqual({ kind: 'intro' }); expect(resolveInitialPlayback({ atStart: false, bias: 'shadow', entryPhrase: 3, intensity: 0.5, })).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); }); });