2PlayerGame/chapter3/public/sound.js
2026-07-23 17:03:26 +03:00

108 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Легкий звук без файлів: усе синтезується WebAudio.
// Дощ у польового, гудіння термінала в аналітика, кліки, штамп, фінальний стінг.
"use strict";
window.Snd = (() => {
let ctx = null,
master = null,
started = false,
muted = localStorage.getItem("ch3snd") === "off";
function ensure() {
if (ctx) return;
ctx = new (window.AudioContext || window.webkitAudioContext)();
master = ctx.createGain();
master.gain.value = muted ? 0 : 1;
master.connect(ctx.destination);
}
function noiseBuf(secs) {
const b = ctx.createBuffer(1, ctx.sampleRate * secs, ctx.sampleRate);
const d = b.getChannelData(0);
for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
return b;
}
// фоновий шар: дощ (поле) або гудіння ЕПТ (аналітик)
function start(role) {
ensure();
if (started) return;
started = true;
const src = ctx.createBufferSource();
src.buffer = noiseBuf(3);
src.loop = true;
const filt = ctx.createBiquadFilter();
const amb = ctx.createGain();
if (role === "field") {
filt.type = "bandpass";
filt.frequency.value = 1100;
filt.Q.value = 0.4;
amb.gain.value = 0.045;
// пориви дощу
const lfo = ctx.createOscillator(), lg = ctx.createGain();
lfo.frequency.value = 0.11;
lg.gain.value = 0.018;
lfo.connect(lg).connect(amb.gain);
lfo.start();
} else {
filt.type = "lowpass";
filt.frequency.value = 130;
amb.gain.value = 0.02;
const hum = ctx.createOscillator(), hg = ctx.createGain();
hum.frequency.value = 50;
hg.gain.value = 0.006;
hum.connect(hg).connect(master);
hum.start();
}
src.connect(filt).connect(amb).connect(master);
src.start();
}
function blip(freq, dur, vol, type) {
if (!ctx) return;
const o = ctx.createOscillator(), gn = ctx.createGain();
o.type = type || "square";
o.frequency.value = freq;
gn.gain.setValueAtTime(vol, ctx.currentTime);
gn.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + dur);
o.connect(gn).connect(master);
o.start();
o.stop(ctx.currentTime + dur);
}
const click = () => blip(1300, 0.03, 0.05);
function stamp() {
if (!ctx) return;
blip(85, 0.16, 0.22, "sine");
const n = ctx.createBufferSource();
n.buffer = noiseBuf(0.08);
const gn = ctx.createGain();
gn.gain.setValueAtTime(0.12, ctx.currentTime);
gn.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.09);
n.connect(gn).connect(master);
n.start();
}
function sting(solved) {
if (!ctx) return;
const seq = solved ? [220, 277, 330, 440] : [196, 185, 147, 98];
seq.forEach((f, i) =>
setTimeout(() => blip(f, 0.5, 0.09, "triangle"), i * 190),
);
}
function toggle() {
muted = !muted;
localStorage.setItem("ch3snd", muted ? "off" : "on");
if (master) master.gain.value = muted ? 0 : 1;
return muted;
}
// клік на будь-якій кнопці — тихий тик
document.addEventListener(
"click",
(e) => {
if (e.target.closest("button")) click();
},
true,
);
return { start, click, stamp, sting, toggle, get muted() { return muted; } };
})();