Spaces:
Running
Running
Download audio.js from CyberMax-tools/fellwing: direct link, hf CLI and curl.
- Browser
- Download file 5.61 kB
-
https://huggingface.co/spaces/CyberMax-tools/fellwing/resolve/main/audio.js
- Command line
-
hf download hf://spaces/CyberMax-tools/fellwing/audio.js
-
curl -L -o audio.js https://huggingface.co/spaces/CyberMax-tools/fellwing/resolve/main/audio.js
5.61 kB
| // Fellwing sound, synthesised with Web Audio (no sound files): piston or turbofan engine, wind that rises with | |
| // airspeed, the stall horn, tyre rumble on the runway and a gate chime. Starts on the first tap or key. | |
| // (c) 2026 CyberMax. All rights reserved. | |
| export class Sound { | |
| constructor() { this.ctx = null; this.on = true; this.kind = 'prop'; } | |
| start() { | |
| if (this.ctx) { if (this.ctx.state === 'suspended') this.ctx.resume(); return; } | |
| const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return; | |
| const c = this.ctx = new AC(); | |
| this.master = c.createGain(); this.master.gain.value = this.on ? 0.55 : 0; this.master.connect(c.destination); | |
| const noiseBuf = c.createBuffer(1, c.sampleRate * 2, c.sampleRate); const d = noiseBuf.getChannelData(0); | |
| let b = 0; for (let i = 0; i < d.length; i++) { const w = Math.random() * 2 - 1; b = 0.97 * b + 0.03 * w; d[i] = b * 6 + w * 0.15; } | |
| const noise = () => { const s = c.createBufferSource(); s.buffer = noiseBuf; s.loop = true; s.start(); return s; }; | |
| // engine: two detuned saws through a low-pass + a filtered noise bed | |
| this.eLp = c.createBiquadFilter(); this.eLp.type = 'lowpass'; this.eLp.frequency.value = 600; this.eLp.Q.value = 2; | |
| this.eG = c.createGain(); this.eG.gain.value = 0; this.eLp.connect(this.eG); this.eG.connect(this.master); | |
| this.o1 = c.createOscillator(); this.o1.type = 'sawtooth'; this.o2 = c.createOscillator(); this.o2.type = 'square'; | |
| this.o1.connect(this.eLp); const g2 = c.createGain(); g2.gain.value = 0.35; this.o2.connect(g2); g2.connect(this.eLp); this.o1.start(); this.o2.start(); | |
| this.eN = noise(); this.eNf = c.createBiquadFilter(); this.eNf.type = 'bandpass'; this.eNf.frequency.value = 900; this.eNf.Q.value = 0.7; | |
| this.eNg = c.createGain(); this.eNg.gain.value = 0; this.eN.connect(this.eNf); this.eNf.connect(this.eNg); this.eNg.connect(this.master); | |
| // jet whine | |
| this.jw = c.createOscillator(); this.jw.type = 'sine'; this.jwG = c.createGain(); this.jwG.gain.value = 0; this.jw.connect(this.jwG); this.jwG.connect(this.master); this.jw.start(); | |
| // wind | |
| this.wN = noise(); this.wF = c.createBiquadFilter(); this.wF.type = 'lowpass'; this.wF.frequency.value = 400; this.wG = c.createGain(); this.wG.gain.value = 0; | |
| this.wN.connect(this.wF); this.wF.connect(this.wG); this.wG.connect(this.master); | |
| // stall horn | |
| this.hO = c.createOscillator(); this.hO.type = 'square'; this.hO.frequency.value = 440; this.hG = c.createGain(); this.hG.gain.value = 0; | |
| const hf = c.createBiquadFilter(); hf.type = 'lowpass'; hf.frequency.value = 1400; this.hO.connect(hf); hf.connect(this.hG); this.hG.connect(this.master); this.hO.start(); | |
| // rumble | |
| this.rN = noise(); this.rF = c.createBiquadFilter(); this.rF.type = 'lowpass'; this.rF.frequency.value = 120; this.rG = c.createGain(); this.rG.gain.value = 0; | |
| this.rN.connect(this.rF); this.rF.connect(this.rG); this.rG.connect(this.master); | |
| } | |
| setOn(on) { this.on = on; if (this.master) this.master.gain.setTargetAtTime(on ? 0.55 : 0, this.ctx.currentTime, 0.05); } | |
| update(P, paused) { | |
| if (!this.ctx) return; | |
| const t = this.ctx.currentTime, k = 0.08, thr = paused || P.crashed ? 0 : P.surf.thr, v = paused ? 0 : P.tas; | |
| const jet = P.A.engine === 'jet'; | |
| const rpm = jet ? 0.25 + 0.75 * thr : 0.3 + 0.7 * thr + Math.min(0.15, v / 400); | |
| if (jet) { | |
| this.eG.gain.setTargetAtTime(0, t, k); | |
| this.eNf.frequency.setTargetAtTime(500 + rpm * 2400, t, k); this.eNg.gain.setTargetAtTime(paused || P.crashed ? 0 : 0.05 + rpm * 0.22, t, k); | |
| this.jw.frequency.setTargetAtTime(2200 + rpm * 4200, t, k); this.jwG.gain.setTargetAtTime(paused || P.crashed ? 0 : 0.012 + rpm * 0.03, t, k); | |
| } else { | |
| const f = 18 + rpm * 62; | |
| this.o1.frequency.setTargetAtTime(f, t, k); this.o2.frequency.setTargetAtTime(f * 2.01, t, k); | |
| this.eLp.frequency.setTargetAtTime(260 + rpm * 1100, t, k); | |
| this.eG.gain.setTargetAtTime(paused || P.crashed ? 0 : 0.10 + thr * 0.16, t, k); | |
| this.eNf.frequency.setTargetAtTime(700 + rpm * 900, t, k); this.eNg.gain.setTargetAtTime(paused || P.crashed ? 0 : 0.02 + thr * 0.06, t, k); | |
| this.jwG.gain.setTargetAtTime(0, t, k); | |
| } | |
| this.wF.frequency.setTargetAtTime(250 + v * 9, t, k); this.wG.gain.setTargetAtTime(Math.min(0.32, (v / 120) ** 2 * 0.2), t, k); | |
| this.hG.gain.setTargetAtTime(!paused && !P.crashed && P.buffet > 0.4 && !P.onGround ? 0.06 : 0, t, 0.02); | |
| this.rG.gain.setTargetAtTime(!paused && P.onGround ? Math.min(0.5, v / 30) * 0.5 : 0, t, k); | |
| } | |
| chime(hi = false) { | |
| if (!this.ctx) return; | |
| const c = this.ctx, t = c.currentTime; | |
| for (const [f, d] of hi ? [[660, 0], [880, 0.09], [1320, 0.18]] : [[880, 0], [1175, 0.08]]) { | |
| const o = c.createOscillator(), g = c.createGain(); o.type = 'triangle'; o.frequency.value = f; g.gain.setValueAtTime(0, t + d); | |
| g.gain.linearRampToValueAtTime(0.18, t + d + 0.01); g.gain.exponentialRampToValueAtTime(0.001, t + d + 0.5); o.connect(g); g.connect(this.master); o.start(t + d); o.stop(t + d + 0.6); | |
| } | |
| } | |
| crash() { | |
| if (!this.ctx) return; | |
| const c = this.ctx, t = c.currentTime, s = c.createBufferSource(); s.buffer = this.eN.buffer; | |
| const f = c.createBiquadFilter(); f.type = 'lowpass'; f.frequency.setValueAtTime(1800, t); f.frequency.exponentialRampToValueAtTime(80, t + 1.4); | |
| const g = c.createGain(); g.gain.setValueAtTime(0.9, t); g.gain.exponentialRampToValueAtTime(0.001, t + 1.6); | |
| s.connect(f); f.connect(g); g.connect(this.master); s.start(); s.stop(t + 1.7); | |
| } | |
| } | |