import { type Player } from "../buzz/index.ts" import { join } from "path" import type { Playback, StreamingPlayback } from "../buzz/utils.ts" import { random } from "./index.ts" export class WaitingSounds { typingPlayback?: Playback speakingPlayback?: Playback constructor(private player: Player) {} async start(operatorStream: StreamingPlayback) { if (this.typingPlayback) return // Already playing this.#startTypingSounds() this.#startSpeakingSounds(operatorStream) } async #startTypingSounds() { return new Promise(async (resolve) => { do { const value = Math.random() * 100 let dir: SoundDir if (value > 33) { dir = "typing" } else { dir = "clicking" } const typingSound = getSound(dir) this.typingPlayback = await this.player.play(typingSound) await this.typingPlayback.finished() } while (this.typingPlayback) resolve() }) } async #startSpeakingSounds(operatorStream: StreamingPlayback) { const playedSounds = new Set() let dir: SoundDir | undefined return new Promise(async (resolve) => { while (operatorStream.bufferEmptyFor < 1500) { await Bun.sleep(100) } do { const lastSoundDir = dir const value = Math.random() * 100 if (lastSoundDir === "body-noises") { dir = "apology" } else if (value > 99 && !lastSoundDir) { dir = "body-noises" } else if (value > 75 && !lastSoundDir) { dir = "stalling" } else { dir = undefined await Bun.sleep(1000) } if (dir) { const speakingSound = getSound(dir, Array.from(playedSounds)) this.speakingPlayback = await this.player.play(speakingSound) playedSounds.add(speakingSound) await this.speakingPlayback.finished() } } while (this.typingPlayback) resolve() }) } async stop() { if (!this.typingPlayback) return await Promise.all([this.typingPlayback.stop(), this.speakingPlayback?.finished()]) this.typingPlayback = undefined } } type SoundDir = (typeof soundDirs)[number] const soundDirs = [ "apology", "background", "body-noises", "clicking", "greeting", "stalling", "typing", ] as const export const soundDir = join(import.meta.dir, "../../", "sounds") export const getSound = (dir: SoundDir, exclude: string[] = []): string => { const glob = new Bun.Glob("*.wav") const soundPaths = Array.from(glob.scanSync({ cwd: join(soundDir, dir), absolute: true })) const filteredSoundPaths = soundPaths.filter((path) => !exclude.includes(path)) if (filteredSoundPaths.length === 0) { return random(soundPaths) } return random(filteredSoundPaths) }