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']); }); it('lands a stinger only on the clock of the sink it plays in (#338)', () => { const live = { ...sinkFrom(1, 5), landOn: () => 6 }; const aside = { ...sinkFrom(DIVERTED_IDS, 0.25), landOn: () => 0.5 }; const audio = new AudioSwitch(live); const song = audio.playSequence('song:s', {} as never, ''); expect(audio.landOn(song, () => 0)).toBe(6); audio.divert(aside); // The song plays on live, the stinger would play diverted: the two clocks do not meet. expect(audio.landOn(song, () => 0)).toBeNull(); const there = audio.playSequence('song:s', {} as never, ''); expect(audio.landOn(there, () => 0)).toBe(0.5); }); it('tells what ended in both sinks, and asks each voice of the one that numbered it (#373)', () => { const live = { ...sinkFrom(1), endings: () => [{ id: 1, name: 'music', stopped: false }], playing: (id: number) => id === 2, }; const aside = { ...sinkFrom(DIVERTED_IDS), endings: () => [{ id: DIVERTED_IDS, name: 'hit', stopped: true }], playing: (id: number) => id === DIVERTED_IDS + 1, }; const audio = new AudioSwitch(live); expect(audio.endings().map((end) => end.id)).toEqual([1]); audio.divert(aside); expect(audio.endings().map((end) => end.id)).toEqual([1, DIVERTED_IDS]); expect([2, 3, DIVERTED_IDS, DIVERTED_IDS + 1].map((id) => audio.playing(id))).toEqual([ true, false, false, true, ]); audio.restore(); expect(audio.playing(DIVERTED_IDS + 1)).toBe(false); }); });