File size: 4,216 Bytes
78d0e31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"use client"

import { useEffect, useRef, useState } from "react"
import { Volume2, VolumeX } from "lucide-react"

export function AudioHeartbeat() {
  const audioContextRef = useRef<AudioContext | null>(null)
  const oscillatorRef = useRef<OscillatorNode | null>(null)
  const gainNodeRef = useRef<GainNode | null>(null)
  const [isPlaying, setIsPlaying] = useState(false)
  const [audioSupported, setAudioSupported] = useState(true)

  // Initialize audio context
  useEffect(() => {
    try {
      // Check if AudioContext is supported
      if (typeof window !== "undefined" && window.AudioContext) {
        audioContextRef.current = new AudioContext()
        setAudioSupported(true)
      } else {
        console.warn("Web Audio API is not supported in this browser")
        setAudioSupported(false)
      }
    } catch (error) {
      console.error("Error initializing audio context:", error)
      setAudioSupported(false)
    }

    // Cleanup
    return () => {
      stopHeartbeat()
      if (audioContextRef.current && audioContextRef.current.state !== "closed") {
        audioContextRef.current.close().catch(console.error)
      }
    }
  }, [])

  const startHeartbeat = () => {
    if (!audioContextRef.current || !audioSupported) return

    try {
      // Create oscillator for the heartbeat sound
      oscillatorRef.current = audioContextRef.current.createOscillator()
      oscillatorRef.current.type = "sine"
      oscillatorRef.current.frequency.value = 2 // Very low frequency for heartbeat rhythm

      // Create gain node for volume control
      gainNodeRef.current = audioContextRef.current.createGain()
      gainNodeRef.current.gain.value = 0.3

      // Connect nodes
      oscillatorRef.current.connect(gainNodeRef.current)
      gainNodeRef.current.connect(audioContextRef.current.destination)

      // Start oscillator
      oscillatorRef.current.start()

      // Create heartbeat effect with gain automation
      const now = audioContextRef.current.currentTime
      const heartbeatInterval = 1.2 // seconds between heartbeats

      // Schedule heartbeats
      for (let i = 0; i < 100; i++) {
        // Schedule many heartbeats ahead
        const beatTime = now + i * heartbeatInterval

        // First beat
        gainNodeRef.current.gain.setValueAtTime(0.01, beatTime)
        gainNodeRef.current.gain.exponentialRampToValueAtTime(0.3, beatTime + 0.1)
        gainNodeRef.current.gain.exponentialRampToValueAtTime(0.01, beatTime + 0.3)

        // Second beat (slightly softer)
        gainNodeRef.current.gain.setValueAtTime(0.01, beatTime + 0.4)
        gainNodeRef.current.gain.exponentialRampToValueAtTime(0.2, beatTime + 0.5)
        gainNodeRef.current.gain.exponentialRampToValueAtTime(0.01, beatTime + 0.7)
      }

      setIsPlaying(true)
    } catch (error) {
      console.error("Error starting heartbeat:", error)
      setAudioSupported(false)
    }
  }

  const stopHeartbeat = () => {
    try {
      if (oscillatorRef.current) {
        oscillatorRef.current.stop()
        oscillatorRef.current.disconnect()
        oscillatorRef.current = null
      }

      if (gainNodeRef.current) {
        gainNodeRef.current.disconnect()
        gainNodeRef.current = null
      }

      setIsPlaying(false)
    } catch (error) {
      console.error("Error stopping heartbeat:", error)
    }
  }

  const toggleAudio = () => {
    if (!audioSupported) return

    if (isPlaying) {
      stopHeartbeat()
    } else {
      // Resume audio context if it's suspended (needed for browsers that require user interaction)
      if (audioContextRef.current && audioContextRef.current.state === "suspended") {
        audioContextRef.current.resume().catch(console.error)
      }
      startHeartbeat()
    }
  }

  // Don't render the button if audio is not supported
  if (!audioSupported) return null

  return (
    <button
      onClick={toggleAudio}
      className="fixed bottom-4 left-4 z-50 bg-dusk/80 hover:bg-dusk p-2 rounded-full backdrop-blur-sm"
      aria-label={isPlaying ? "Mute heartbeat" : "Play heartbeat"}
    >
      {isPlaying ? <Volume2 className="h-5 w-5 text-flame" /> : <VolumeX className="h-5 w-5 text-gray-400" />}
    </button>
  )
}