@@ -12,6 +12,7 @@ export default function Doorbell() {
1212 const [ isRinging , setIsRinging ] = useState ( false )
1313
1414 const handleDoorbellClick = ( ) => {
15+ playDoorbellSound ( )
1516 setIsRinging ( true )
1617 }
1718
@@ -148,3 +149,82 @@ const AnimatedButtonContent = styled(motion.span)`
148149 font-size: 2rem;
149150 line-height: 1.5;
150151`
152+
153+ // Constants //
154+
155+ const playDoorbellSound = ( ) => {
156+ try {
157+ const audioContext = new ( window . AudioContext || ( window as any ) . webkitAudioContext ) ( )
158+ const now = audioContext . currentTime
159+
160+ const scheduleBellTone = ( {
161+ baseFrequency,
162+ startTime,
163+ duration
164+ } : {
165+ baseFrequency : number
166+ startTime : number
167+ duration : number
168+ } ) => {
169+ const masterGain = audioContext . createGain ( )
170+ const shimmerFilter = audioContext . createBiquadFilter ( )
171+ const resonanceFilter = audioContext . createBiquadFilter ( )
172+ const delayNode = audioContext . createDelay ( )
173+ const feedbackGain = audioContext . createGain ( )
174+
175+ shimmerFilter . type = "highpass"
176+ shimmerFilter . frequency . value = 180
177+
178+ resonanceFilter . type = "bandpass"
179+ resonanceFilter . frequency . value = baseFrequency * 1.3
180+ resonanceFilter . Q . value = 6
181+
182+ delayNode . delayTime . value = 0.42
183+ feedbackGain . gain . value = 0.38
184+
185+ masterGain . connect ( shimmerFilter )
186+ shimmerFilter . connect ( resonanceFilter )
187+ resonanceFilter . connect ( audioContext . destination )
188+ resonanceFilter . connect ( delayNode )
189+ delayNode . connect ( feedbackGain )
190+ feedbackGain . connect ( resonanceFilter )
191+
192+ masterGain . gain . setValueAtTime ( 0 , startTime )
193+ masterGain . gain . linearRampToValueAtTime ( 0.95 , startTime + 0.015 )
194+ masterGain . gain . setValueAtTime ( 0.92 , startTime + 0.18 )
195+ masterGain . gain . exponentialRampToValueAtTime ( 0.000005 , startTime + duration )
196+
197+ const partials = [
198+ { ratio : 1 , gain : 1 } ,
199+ { ratio : 1.99 , gain : 0.42 } ,
200+ { ratio : 2.54 , gain : 0.3 } ,
201+ { ratio : 3.01 , gain : 0.2 } ,
202+ { ratio : 3.96 , gain : 0.14 } ,
203+ { ratio : 5.43 , gain : 0.08 }
204+ ]
205+
206+ partials . forEach ( ( { ratio, gain } ) => {
207+ const osc = audioContext . createOscillator ( )
208+ const partialGain = audioContext . createGain ( )
209+
210+ osc . type = "sine"
211+ osc . frequency . value = baseFrequency * ratio
212+ osc . detune . value = ( Math . random ( ) - 0.5 ) * 6
213+ partialGain . gain . value = gain
214+
215+ osc . connect ( partialGain )
216+ partialGain . connect ( masterGain )
217+
218+ osc . start ( startTime )
219+ osc . stop ( startTime + duration + 0.8 )
220+ } )
221+ }
222+
223+ // Ding (higher chime) followed by Dong (lower chime) with longer resonance
224+ scheduleBellTone ( { baseFrequency : 659 , startTime : now , duration : 4 } )
225+ scheduleBellTone ( { baseFrequency : 415 , startTime : now + 0.45 , duration : 4.8 } )
226+ } catch ( error ) {
227+ // Fallback: silently fail if audio context is not available
228+ console . warn ( "Could not play doorbell sound:" , error )
229+ }
230+ }
0 commit comments