zeroed-some/dougk / f86d428

Browse files

tweak sound a bnit

Authored by espadonne
SHA
f86d4288f7acf974765964bc1ab7f4a1a11811e8
Parents
704f01b
Tree
5a62231

2 changed files

StatusFile+-
M src/renderers/three/index.js 9 0
M src/renderers/three/sounds.js 28 11
src/renderers/three/index.jsmodified
@@ -38,6 +38,15 @@ function createToonGradient() {
38
 export function start(container) {
38
 export function start(container) {
39
   if (animationId) return
39
   if (animationId) return
40
 
40
 
41
+  // Unlock audio on any user interaction (document level for mobile)
42
+  const unlockEvents = ['touchstart', 'touchend', 'pointerdown', 'click', 'keydown']
43
+  const unlockHandler = () => {
44
+    unlockAudio()
45
+    // Remove all listeners after first unlock
46
+    unlockEvents.forEach(e => document.removeEventListener(e, unlockHandler))
47
+  }
48
+  unlockEvents.forEach(e => document.addEventListener(e, unlockHandler, { passive: true }))
49
+
41
   // Scene
50
   // Scene
42
   scene = new THREE.Scene()
51
   scene = new THREE.Scene()
43
   scene.background = new THREE.Color(0x55a04b)
52
   scene.background = new THREE.Color(0x55a04b)
src/renderers/three/sounds.jsmodified
@@ -13,34 +13,51 @@ function getContext() {
13
 // Must be called on first user interaction to enable audio on mobile
13
 // Must be called on first user interaction to enable audio on mobile
14
 export function unlockAudio() {
14
 export function unlockAudio() {
15
   if (unlocked) return
15
   if (unlocked) return
16
+  unlocked = true // Set immediately to prevent multiple attempts
16
 
17
 
17
-  const ctx = getContext()
18
+  // Create context fresh during user gesture (mobile requirement)
19
+  if (!audioContext) {
20
+    audioContext = new (window.AudioContext || window.webkitAudioContext)()
21
+  }
22
+
23
+  const ctx = audioContext
18
 
24
 
19
-  // Resume if suspended
25
+  // Resume synchronously - don't wait for promise
20
   if (ctx.state === 'suspended') {
26
   if (ctx.state === 'suspended') {
21
     ctx.resume()
27
     ctx.resume()
22
   }
28
   }
23
 
29
 
24
-  // Play a silent buffer to fully unlock on iOS/mobile
30
+  // Immediately play a sound to force audio pipeline open
25
-  const silentBuffer = ctx.createBuffer(1, 1, ctx.sampleRate)
31
+  // Must happen synchronously in the user gesture
26
-  const source = ctx.createBufferSource()
32
+  try {
27
-  source.buffer = silentBuffer
33
+    const osc = ctx.createOscillator()
28
-  source.connect(ctx.destination)
34
+    const gain = ctx.createGain()
29
-  source.start(0)
35
+
36
+    osc.type = 'triangle'
37
+    osc.frequency.value = 200
38
+    gain.gain.value = 0.001 // Nearly silent
30
 
39
 
31
-  unlocked = true
40
+    osc.connect(gain)
41
+    gain.connect(ctx.destination)
42
+
43
+    osc.start(0)
44
+    osc.stop(ctx.currentTime + 0.1)
45
+  } catch (e) {
46
+    console.warn('Unlock sound failed:', e)
47
+  }
32
 }
48
 }
33
 
49
 
34
 // Damp crunch sound - wet bread being chomped
50
 // Damp crunch sound - wet bread being chomped
35
 export function playMonch() {
51
 export function playMonch() {
36
   const ctx = getContext()
52
   const ctx = getContext()
37
-  const now = ctx.currentTime
38
 
53
 
39
-  // Resume context if suspended (browser autoplay policy)
54
+  // Try to resume if needed, but don't block
40
   if (ctx.state === 'suspended') {
55
   if (ctx.state === 'suspended') {
41
     ctx.resume()
56
     ctx.resume()
42
   }
57
   }
43
 
58
 
59
+  const now = ctx.currentTime
60
+
44
   // Master output - keep it gentle
61
   // Master output - keep it gentle
45
   const master = ctx.createGain()
62
   const master = ctx.createGain()
46
   master.gain.value = 0.25
63
   master.gain.value = 0.25