"use client" import { useCallback, useEffect, useRef, useState } from "react" /** * ICU Critical Alarm System * * Uses Web Audio API to generate realistic ICU monitor alarm sounds. * - Critical: Rapid triple-beep pattern (like real ICU cardiac monitors) * - Warning: Double-beep pattern (medium urgency) * * No external audio files needed — pure synthesis. */ type AlarmLevel = 'critical' | 'warning' | null interface UseCriticalAlarmReturn { /** Play the alarm for the given level */ playAlarm: (level: AlarmLevel) => void /** Stop any currently playing alarm */ stopAlarm: () => void /** Whether sound is muted */ isMuted: boolean /** Toggle mute state */ toggleMute: () => void /** Whether an alarm is currently active */ isPlaying: boolean } // Persist mute state across component mounts let globalMuted = false export function useCriticalAlarm(): UseCriticalAlarmReturn { const [isMuted, setIsMuted] = useState(globalMuted) const [isPlaying, setIsPlaying] = useState(false) const audioCtxRef = useRef(null) const intervalRef = useRef | null>(null) const timeoutRefs = useRef[]>([]) const activeNodesRef = useRef([]) // Initialize AudioContext lazily (must be after user interaction) const getAudioContext = useCallback(() => { if (!audioCtxRef.current) { audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)() } if (audioCtxRef.current.state === 'suspended') { audioCtxRef.current.resume() } return audioCtxRef.current }, []) // Play a single beep tone const playBeep = useCallback(( frequency: number, duration: number, volume: number = 0.3, type: OscillatorType = 'sine' ) => { try { const ctx = getAudioContext() const oscillator = ctx.createOscillator() const gainNode = ctx.createGain() oscillator.type = type oscillator.frequency.setValueAtTime(frequency, ctx.currentTime) // Attack-decay envelope for realistic beep gainNode.gain.setValueAtTime(0, ctx.currentTime) gainNode.gain.linearRampToValueAtTime(volume, ctx.currentTime + 0.01) gainNode.gain.setValueAtTime(volume, ctx.currentTime + duration - 0.05) gainNode.gain.linearRampToValueAtTime(0, ctx.currentTime + duration) oscillator.connect(gainNode) gainNode.connect(ctx.destination) oscillator.start(ctx.currentTime) oscillator.stop(ctx.currentTime + duration) activeNodesRef.current.push(oscillator) oscillator.onended = () => { activeNodesRef.current = activeNodesRef.current.filter(n => n !== oscillator) gainNode.disconnect() } } catch (e) { console.warn('Audio playback failed:', e) } }, [getAudioContext]) // ICU Critical alarm pattern: rapid triple beep (like cardiac monitor) // Frequency ~1000Hz, pattern: beep-beep-beep ... pause ... repeat const playCriticalPattern = useCallback(() => { // Triple beep: 3 quick beeps playBeep(1000, 0.15, 0.35, 'sine') const t1 = setTimeout(() => playBeep(1000, 0.15, 0.35, 'sine'), 200) const t2 = setTimeout(() => playBeep(1000, 0.15, 0.35, 'sine'), 400) // Second harmonic overlay for urgency const t3 = setTimeout(() => playBeep(1500, 0.08, 0.15, 'square'), 450) timeoutRefs.current.push(t1, t2, t3) }, [playBeep]) // Warning alarm pattern: double beep (medium urgency) const playWarningPattern = useCallback(() => { playBeep(800, 0.12, 0.2, 'sine') const t1 = setTimeout(() => playBeep(800, 0.12, 0.2, 'sine'), 250) timeoutRefs.current.push(t1) }, [playBeep]) const stopAlarm = useCallback(() => { // Clear all scheduled beeps if (intervalRef.current) { clearInterval(intervalRef.current) intervalRef.current = null } timeoutRefs.current.forEach(t => clearTimeout(t)) timeoutRefs.current = [] // Stop any active oscillators activeNodesRef.current.forEach(osc => { try { osc.stop() } catch { } }) activeNodesRef.current = [] setIsPlaying(false) }, []) const playAlarm = useCallback((level: AlarmLevel) => { if (!level || isMuted) { stopAlarm() return } // Stop existing alarm before starting new one stopAlarm() setIsPlaying(true) if (level === 'critical') { // Play immediately playCriticalPattern() // Repeat every 1.5 seconds (ICU-style persistent alarm) intervalRef.current = setInterval(() => { if (!globalMuted) { playCriticalPattern() } }, 1500) // Auto-stop after 10 seconds to avoid infinite alarm const autoStop = setTimeout(() => { stopAlarm() }, 10000) timeoutRefs.current.push(autoStop) } else if (level === 'warning') { // Play immediately playWarningPattern() // Repeat every 3 seconds (less urgent) intervalRef.current = setInterval(() => { if (!globalMuted) { playWarningPattern() } }, 3000) // Auto-stop after 6 seconds const autoStop = setTimeout(() => { stopAlarm() }, 6000) timeoutRefs.current.push(autoStop) } }, [isMuted, stopAlarm, playCriticalPattern, playWarningPattern]) const toggleMute = useCallback(() => { const newMuted = !isMuted globalMuted = newMuted setIsMuted(newMuted) if (newMuted) { stopAlarm() } }, [isMuted, stopAlarm]) // Cleanup on unmount useEffect(() => { return () => { stopAlarm() } }, [stopAlarm]) return { playAlarm, stopAlarm, isMuted, toggleMute, isPlaying, } }