11const AudioManager = ( ( ) => {
22 let _ctx = null ;
3+ let soundLocks = new Set ( ) ;
4+
35 function ctx ( ) {
46 if ( ! _ctx ) _ctx = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
57 if ( _ctx . state === 'suspended' ) _ctx . resume ( ) ;
68 return _ctx ;
79 }
10+
811 function enabled ( ) {
912 const v = localStorage . getItem ( 'soundEnabled' ) ;
1013 return v === null ? true : v === 'true' ;
1114 }
12- function beep ( freq , dur , type = 'sine' , gain = 0.3 , t = null ) {
13- const c = ctx ( ) ; const now = t ?? c . currentTime ;
14- const osc = c . createOscillator ( ) , g = c . createGain ( ) ;
15- osc . connect ( g ) ; g . connect ( c . destination ) ;
16- osc . type = type ; osc . frequency . setValueAtTime ( freq , now ) ;
15+
16+ function beep ( freq , dur , type = 'sine' , gain = 0.3 , t = null ) {
17+ const c = ctx ( ) ;
18+ const now = t ?? c . currentTime ;
19+
20+ const osc = c . createOscillator ( ) ,
21+ g = c . createGain ( ) ;
22+
23+ osc . connect ( g ) ;
24+ g . connect ( c . destination ) ;
25+
26+ osc . type = type ;
27+ osc . frequency . setValueAtTime ( freq , now ) ;
28+
1729 g . gain . setValueAtTime ( 0 , now ) ;
18- g . gain . linearRampToValueAtTime ( gain , now + 0.01 ) ;
19- g . gain . exponentialRampToValueAtTime ( 0.001 , now + dur ) ;
20- osc . start ( now ) ; osc . stop ( now + dur + 0.01 ) ;
30+ g . gain . linearRampToValueAtTime ( gain , now + 0.01 ) ;
31+ g . gain . exponentialRampToValueAtTime ( 0.001 , now + dur ) ;
32+
33+ osc . start ( now ) ;
34+ osc . stop ( now + dur + 0.01 ) ;
2135 }
22- function noise ( dur = 0.1 , gain = 0.2 ) {
23- const c = ctx ( ) , buf = c . createBuffer ( 1 , c . sampleRate * dur , c . sampleRate ) ;
36+
37+ function noise ( dur = 0.1 , gain = 0.2 ) {
38+ const c = ctx ( ) ,
39+ buf = c . createBuffer ( 1 , c . sampleRate * dur , c . sampleRate ) ;
40+
2441 const d = buf . getChannelData ( 0 ) ;
25- for ( let i = 0 ; i < d . length ; i ++ ) d [ i ] = Math . random ( ) * 2 - 1 ;
26- const src = c . createBufferSource ( ) ; src . buffer = buf ;
27- const g = c . createGain ( ) , f = c . createBiquadFilter ( ) ;
28- f . type = 'highpass' ; f . frequency . value = 1000 ;
29- src . connect ( f ) ; f . connect ( g ) ; g . connect ( c . destination ) ;
42+ for ( let i = 0 ; i < d . length ; i ++ ) d [ i ] = Math . random ( ) * 2 - 1 ;
43+
44+ const src = c . createBufferSource ( ) ;
45+ src . buffer = buf ;
46+
47+ const g = c . createGain ( ) ,
48+ f = c . createBiquadFilter ( ) ;
49+
50+ f . type = 'highpass' ;
51+ f . frequency . value = 1000 ;
52+
53+ src . connect ( f ) ;
54+ f . connect ( g ) ;
55+ g . connect ( c . destination ) ;
56+
3057 const t = c . currentTime ;
3158 g . gain . setValueAtTime ( gain , t ) ;
32- g . gain . exponentialRampToValueAtTime ( 0.001 , t + dur ) ;
33- src . start ( t ) ; src . stop ( t + dur + 0.01 ) ;
59+ g . gain . exponentialRampToValueAtTime ( 0.001 , t + dur ) ;
60+
61+ src . start ( t ) ;
62+ src . stop ( t + dur + 0.01 ) ;
3463 }
64+
3565 const sounds = {
36- snake_eat ( ) { const c = ctx ( ) , n = c . currentTime ; beep ( 300 , .06 , 'square' , .25 , n ) ; beep ( 600 , .08 , 'square' , .20 , n + .05 ) ; } ,
37- snake_die ( ) { const c = ctx ( ) , n = c . currentTime ; beep ( 400 , .12 , 'sawtooth' , .3 , n ) ; beep ( 200 , .20 , 'sawtooth' , .25 , n + .10 ) ; beep ( 100 , .30 , 'sine' , .20 , n + .25 ) ; } ,
38- mole_hit ( ) { noise ( .08 , .35 ) ; const c = ctx ( ) ; beep ( 180 , .10 , 'sine' , .2 , c . currentTime + .02 ) ; } ,
39- card_flip ( ) { noise ( .05 , .12 ) ; const c = ctx ( ) ; beep ( 800 , .04 , 'sine' , .08 , c . currentTime + .01 ) ; } ,
40- card_deal ( ) { noise ( .07 , .18 ) ; const c = ctx ( ) ; beep ( 300 , .06 , 'sine' , .12 , c . currentTime + .02 ) ; } ,
41- game_win ( ) { const c = ctx ( ) , n = c . currentTime ; [ 523 , 659 , 784 , 1047 ] . forEach ( ( f , i ) => beep ( f , .15 , 'sine' , .25 , n + i * .12 ) ) ; } ,
42- game_over ( ) { const c = ctx ( ) , n = c . currentTime ; beep ( 440 , .15 , 'sawtooth' , .3 , n ) ; beep ( 349 , .20 , 'sawtooth' , .28 , n + .18 ) ; beep ( 262 , .35 , 'sine' , .25 , n + .40 ) ; } ,
43- score_point ( ) { beep ( 880 , .08 , 'sine' , .2 ) ; } ,
44- wrong ( ) { beep ( 150 , .15 , 'sawtooth' , .2 ) ; } ,
45- click ( ) { beep ( 600 , .04 , 'sine' , .1 ) ; } ,
66+ snake_eat ( ) {
67+ const c = ctx ( ) ,
68+ n = c . currentTime ;
69+ beep ( 300 , 0.06 , 'square' , 0.25 , n ) ;
70+ beep ( 600 , 0.08 , 'square' , 0.2 , n + 0.05 ) ;
71+ } ,
72+
73+ snake_die ( ) {
74+ const c = ctx ( ) ,
75+ n = c . currentTime ;
76+ beep ( 400 , 0.12 , 'sawtooth' , 0.3 , n ) ;
77+ beep ( 200 , 0.2 , 'sawtooth' , 0.25 , n + 0.1 ) ;
78+ beep ( 100 , 0.3 , 'sine' , 0.2 , n + 0.25 ) ;
79+ } ,
80+
81+ mole_hit ( ) {
82+ noise ( 0.08 , 0.35 ) ;
83+ const c = ctx ( ) ;
84+ beep ( 180 , 0.1 , 'sine' , 0.2 , c . currentTime + 0.02 ) ;
85+ } ,
86+
87+ card_flip ( ) {
88+ noise ( 0.05 , 0.12 ) ;
89+ const c = ctx ( ) ;
90+ beep ( 800 , 0.04 , 'sine' , 0.08 , c . currentTime + 0.01 ) ;
91+ } ,
92+
93+ card_deal ( ) {
94+ noise ( 0.07 , 0.18 ) ;
95+ const c = ctx ( ) ;
96+ beep ( 300 , 0.06 , 'sine' , 0.12 , c . currentTime + 0.02 ) ;
97+ } ,
98+
99+ game_win ( ) {
100+ const c = ctx ( ) ,
101+ n = c . currentTime ;
102+ [ 523 , 659 , 784 , 1047 ] . forEach ( ( f , i ) =>
103+ beep ( f , 0.15 , 'sine' , 0.25 , n + i * 0.12 )
104+ ) ;
105+ } ,
106+
107+ game_over ( ) {
108+ const c = ctx ( ) ,
109+ n = c . currentTime ;
110+ beep ( 440 , 0.15 , 'sawtooth' , 0.3 , n ) ;
111+ beep ( 349 , 0.2 , 'sawtooth' , 0.28 , n + 0.18 ) ;
112+ beep ( 262 , 0.35 , 'sine' , 0.25 , n + 0.4 ) ;
113+ } ,
114+
115+ score_point ( ) {
116+ beep ( 880 , 0.08 , 'sine' , 0.2 ) ;
117+ } ,
118+
119+ wrong ( ) {
120+ beep ( 150 , 0.15 , 'sawtooth' , 0.2 ) ;
121+ } ,
122+
123+ click ( ) {
124+ beep ( 600 , 0.04 , 'sine' , 0.1 ) ;
125+ } ,
46126 } ;
127+
47128 return {
48129 play ( name ) {
49130 if ( ! enabled ( ) ) return false ;
50- if ( sounds [ name ] ) { try { sounds [ name ] ( ) ; } catch ( e ) { } return true ; }
131+
132+ // 🛑 Strict permanent lock until game reset explicitly clears it
133+ if ( name === "snake_die" || name === "game_over" ) {
134+ if ( soundLocks . has ( name ) ) return false ;
135+ soundLocks . add ( name ) ;
136+ }
137+
138+ if ( sounds [ name ] ) {
139+ try {
140+ sounds [ name ] ( ) ;
141+ } catch ( e ) { }
142+ return true ;
143+ }
144+
51145 return false ;
52146 } ,
53- setEnabled ( v ) { localStorage . setItem ( 'soundEnabled' , String ( ! ! v ) ) ; } ,
54- isEnabled ( ) { return enabled ( ) ; } ,
55- list ( ) { return Object . keys ( sounds ) ; } ,
147+
148+ setEnabled ( v ) {
149+ localStorage . setItem ( 'soundEnabled' , String ( ! ! v ) ) ;
150+ } ,
151+
152+ isEnabled ( ) {
153+ return enabled ( ) ;
154+ } ,
155+
156+ list ( ) {
157+ return Object . keys ( sounds ) ;
158+ } ,
159+
160+ reset ( ) {
161+ soundLocks . clear ( ) ;
162+ }
56163 } ;
57164} ) ( ) ;
58- window . AudioManager = AudioManager ;
165+ window . AudioManager = AudioManager ;
0 commit comments