import { describe, expect, it } from 'vitest'; import { AudioSwitch, DIVERTED_IDS } from '../../src/runtime/audioSwitch'; import { type AudioSink, SILENT_AUDIO } from '../../src/runtime/systems/Audio'; /** A sink that numbers its plays from `first` and writes down every call, with its clock at `clock`. */ function sinkFrom(first: number, clock = 0) { const calls: string[] = []; let next = first; const sink: AudioSink & { calls: string[]; clockAt: number } = { ...SILENT_AUDIO, calls, clockAt: clock, play: (file) => (calls.push(`play ${file}`), next++), playSequence: (name) => (calls.push(`sequence ${name}`), next++), stopVoice: (voice) => void calls.push(`stopVoice ${voice}`), stopPlayback: (id) => void calls.push(`stopPlayback ${id}`), tune: (voice) => void calls.push(`tune ${voice}`), stop: (file) => void calls.push(`stop ${file}`), stopOwner: (owner) => void calls.push(`stopOwner ${owner}`), setMix: (buses) => void calls.push(`setMix ${buses.map((bus) => bus.name).join(',')}`), setMixVolume: (bus, volume) => void calls.push(`setMixVolume ${bus} ${volume}`), resetMix: () => void calls.push('resetMix'), length: () => 2, clock: () => sink.clockAt, }; return sink; } describe('AudioSwitch (#423)', () => { it('plays live until it is diverted, and reaches each voice in the sink that numbered it', () => { const live = sinkFrom(1); const aside = sinkFrom(DIVERTED_IDS); const audio = new AudioSwitch(live); const before = audio.play('music', 1, true, ''); audio.divert(aside); const after = audio.play('hit', 1, false, ''); expect(before).toBe(1); expect(after).toBe(DIVERTED_IDS); audio.tune(before, {}); audio.stopVoice(after); expect(live.calls).toEqual(['play music', 'tune 1']); expect(aside.calls).toEqual(['play hit', `stopVoice ${DIVERTED_IDS}`]); audio.restore(); expect(audio.play('again', 1, false, '')).toBe(2); // One of the diverted sink, gone since, reaches nobody. audio.stopVoice(after); expect(aside.calls).toHaveLength(2); }); it('stops by name, owner and everything in both, and hands the mix as it stands to the one it diverts to', () => { const live = sinkFrom(1); const aside = sinkFrom(DIVERTED_IDS); const audio = new AudioSwitch(live); audio.setMix([{ name: 'mix', volume: 1, into: null, fx: null }]); audio.setMixVolume('mix:music', 0.5); audio.divert(aside); expect(aside.calls).toEqual(['setMix mix', 'setMixVolume mix:music 0.5']); audio.stop(''); audio.stopOwner('o1'); expect(live.calls.slice(-2)).toEqual(['stop ', 'stopOwner o1']); expect(aside.calls.slice(-2)).toEqual(['stop ', 'stopOwner o1']); // The live sink knows every file, a streamed one too. expect(audio.length('long')).toBe(2); }); it('keeps time by the sink it plays to', () => { const live = sinkFrom(1, 5); const aside = sinkFrom(DIVERTED_IDS, 0.25); const audio = new AudioSwitch(live); const music = audio.playSequence('composition:c', {} as never, ''); expect(audio.clock()).toBe(5); audio.divert(aside); expect(audio.clock()).toBe(0.25); // A composition that meets its picture again stops its live playing and starts on the other. audio.stopPlayback(music, 0.02); audio.playSequence('composition:c', {} as never, ''); expect(live.calls).toEqual(['sequence composition:c', 'stopPlayback 1']); expect(aside.calls).toEqual(['sequence composition:c']); }); });