JavaScript · 3164 bytes Raw Blame History
1 // Web Audio API sound synthesis for dougk
2
3 let audioContext = null
4
5 function getContext() {
6 if (!audioContext) {
7 audioContext = new (window.AudioContext || window.webkitAudioContext)()
8 }
9 return audioContext
10 }
11
12 // Damp crunch sound - wet bread being chomped
13 export function playMonch() {
14 const ctx = getContext()
15 const now = ctx.currentTime
16
17 // Resume context if suspended (browser autoplay policy)
18 if (ctx.state === 'suspended') {
19 ctx.resume()
20 }
21
22 // Create noise buffer for the crunch texture
23 const noiseLength = 0.15
24 const bufferSize = ctx.sampleRate * noiseLength
25 const noiseBuffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate)
26 const noiseData = noiseBuffer.getChannelData(0)
27
28 // Fill with noise
29 for (let i = 0; i < bufferSize; i++) {
30 noiseData[i] = Math.random() * 2 - 1
31 }
32
33 // Noise source
34 const noise = ctx.createBufferSource()
35 noise.buffer = noiseBuffer
36
37 // Low-pass filter for the "damp" wet quality
38 const dampFilter = ctx.createBiquadFilter()
39 dampFilter.type = 'lowpass'
40 dampFilter.frequency.setValueAtTime(800, now)
41 dampFilter.frequency.exponentialRampToValueAtTime(300, now + 0.08)
42 dampFilter.Q.value = 2
43
44 // Bandpass for crunch character
45 const crunchFilter = ctx.createBiquadFilter()
46 crunchFilter.type = 'bandpass'
47 crunchFilter.frequency.value = 400
48 crunchFilter.Q.value = 1.5
49
50 // Envelope for the noise burst
51 const noiseGain = ctx.createGain()
52 noiseGain.gain.setValueAtTime(0, now)
53 noiseGain.gain.linearRampToValueAtTime(0.4, now + 0.01) // Quick attack
54 noiseGain.gain.exponentialRampToValueAtTime(0.15, now + 0.04) // Initial drop
55 noiseGain.gain.exponentialRampToValueAtTime(0.01, now + 0.12) // Tail off
56
57 // Low thump for the bite impact
58 const thump = ctx.createOscillator()
59 thump.type = 'sine'
60 thump.frequency.setValueAtTime(120, now)
61 thump.frequency.exponentialRampToValueAtTime(50, now + 0.06)
62
63 const thumpGain = ctx.createGain()
64 thumpGain.gain.setValueAtTime(0, now)
65 thumpGain.gain.linearRampToValueAtTime(0.3, now + 0.005)
66 thumpGain.gain.exponentialRampToValueAtTime(0.01, now + 0.08)
67
68 // Secondary squelch - adds wetness
69 const squelch = ctx.createOscillator()
70 squelch.type = 'triangle'
71 squelch.frequency.setValueAtTime(200, now)
72 squelch.frequency.exponentialRampToValueAtTime(80, now + 0.05)
73
74 const squelchGain = ctx.createGain()
75 squelchGain.gain.setValueAtTime(0, now + 0.01)
76 squelchGain.gain.linearRampToValueAtTime(0.15, now + 0.02)
77 squelchGain.gain.exponentialRampToValueAtTime(0.01, now + 0.07)
78
79 // Master output with slight compression feel
80 const master = ctx.createGain()
81 master.gain.value = 0.6
82
83 // Connect noise chain
84 noise.connect(dampFilter)
85 dampFilter.connect(crunchFilter)
86 crunchFilter.connect(noiseGain)
87 noiseGain.connect(master)
88
89 // Connect thump
90 thump.connect(thumpGain)
91 thumpGain.connect(master)
92
93 // Connect squelch
94 squelch.connect(squelchGain)
95 squelchGain.connect(master)
96
97 // Output
98 master.connect(ctx.destination)
99
100 // Play
101 noise.start(now)
102 noise.stop(now + noiseLength)
103 thump.start(now)
104 thump.stop(now + 0.1)
105 squelch.start(now)
106 squelch.stop(now + 0.08)
107 }