import { ooAudioBus } from "./oo-audio-bus"; import { readOoSoundEnabled } from "./oo-sound-pref"; const SESSION_KEY = "evoo_voice_mark_played"; function prefersReducedMotion(): boolean { if (typeof window === "undefined" || !window.matchMedia) return false; return window.matchMedia("(prefers-reduced-motion: reduce)").matches; } function hasPlayedThisSession(): boolean { if (typeof window === "undefined" || !window.sessionStorage) return true; return window.sessionStorage.getItem(SESSION_KEY) === "1"; } function markPlayed(): void { if (typeof window === "undefined" || !window.sessionStorage) return; window.sessionStorage.setItem(SESSION_KEY, "1"); } function playVoiceMarkOnContext(ctx: AudioContext) { const master = ooAudioBus.sfxDestination(ctx); const t0 = ctx.currentTime + 0.01; const eOsc = ctx.createOscillator(); const eGain = ctx.createGain(); const eFilter = ctx.createBiquadFilter(); eFilter.type = "bandpass"; eFilter.frequency.value = 480; eFilter.Q.value = 5; eOsc.type = "triangle"; eOsc.frequency.setValueAtTime(320, t0); eGain.gain.setValueAtTime(0.0001, t0); eGain.gain.exponentialRampToValueAtTime(0.05, t0 + 0.02); eGain.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.12); eOsc.connect(eFilter); eFilter.connect(eGain); eGain.connect(master); eOsc.start(t0); eOsc.stop(t0 + 0.14); const ooOsc = ctx.createOscillator(); const ooGain = ctx.createGain(); const ooFilter = ctx.createBiquadFilter(); ooFilter.type = "bandpass"; ooFilter.frequency.value = 360; ooFilter.Q.value = 4; ooOsc.type = "sine"; ooOsc.frequency.setValueAtTime(280, t0 + 0.1); ooOsc.frequency.exponentialRampToValueAtTime(220, t0 + 0.38); ooGain.gain.setValueAtTime(0.0001, t0 + 0.1); ooGain.gain.exponentialRampToValueAtTime(0.055, t0 + 0.14); ooGain.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.42); ooOsc.connect(ooFilter); ooFilter.connect(ooGain); ooGain.connect(master); ooOsc.start(t0 + 0.1); ooOsc.stop(t0 + 0.45); } class OoVoiceMarkEngine { maybePlayColdOpen() { if (!readOoSoundEnabled()) return; if (prefersReducedMotion()) return; if (hasPlayedThisSession()) return; const ctx = ooAudioBus.ensureContext(); if (!ctx) return; const run = () => { try { playVoiceMarkOnContext(ctx); markPlayed(); } catch { // Ignore playback failures. } }; if (ctx.state === "suspended") { void ctx .resume() .then(run) .catch(() => undefined); return; } run(); } } export const ooVoiceMark = new OoVoiceMarkEngine(); export function resetOoVoiceMarkForTests(): void { if (typeof window !== "undefined" && window.sessionStorage) { window.sessionStorage.removeItem(SESSION_KEY); } }