104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
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, private streamPlayback: StreamingPlayback) {}
|
|
|
|
async start() {
|
|
if (this.typingPlayback) return // Already playing
|
|
|
|
this.#startTypingSounds()
|
|
this.#startSpeakingSounds()
|
|
}
|
|
|
|
async #startTypingSounds() {
|
|
return new Promise<void>(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() {
|
|
const playedSounds = new Set<string>()
|
|
let dir: SoundDir | undefined
|
|
return new Promise<void>(async (resolve) => {
|
|
// Don't start speaking until the stream playback buffer is empty!
|
|
while (this.streamPlayback.bufferEmptyFor < 1000) {
|
|
await Bun.sleep(100)
|
|
}
|
|
|
|
do {
|
|
this.streamPlayback.bufferEmptyFor
|
|
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)
|
|
}
|