Звуки в игре без единого аудиофайла

· #игра #javascript #звук

В игре нет ни одного звукового файла. Мяуканье, писк мыши, гром и гул пылесоса собираются прямо в браузере через Web Audio API. Весит это несколько десятков строк, а не мегабайты mp3.

Простой тон

Основа всего — осциллятор и регулятор громкости, который быстро затухает:

function toneAt(freq, dur, type, vol, t) {
    var osc = audio.createOscillator();
    var gain = audio.createGain();
    osc.type = type || 'triangle';
    osc.frequency.setValueAtTime(freq, t);
    gain.gain.setValueAtTime(vol || 0.05, t);
    gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);
    osc.connect(gain);
    gain.connect(audio.destination);
    osc.start(t);
    osc.stop(t + dur + 0.02);
}

Из таких коротких тонов собраны звуки поимки рыбы, бонусов и проигрыша. Звук пойманной рыбы чуть повышается с ростом множителя серии, поэтому серию слышно, даже не глядя на счёт.

Мяу

Мяуканье — это тон, который плавно едет вверх, а потом вниз:

sweep(520, 860, 0.13, 'triangle', 0.05);        // «мя…»
sweep(860, 470, 0.32, 'triangle', 0.05, 0.12);  // «…у»

Внутри sweep та же схема, только частота меняется через exponentialRampToValueAtTime. Писк мыши устроен так же, просто вдвое выше и короче.

Гром

Гром тоном не сделать, нужен шум. Я один раз заполняю буфер случайными числами, а потом пропускаю его через фильтр низких частот, который постепенно закрывается:

filter.type = 'lowpass';
filter.frequency.setValueAtTime(500, t);
filter.frequency.exponentialRampToValueAtTime(125, t + dur);

Короткий резкий щелчок плюс длинный глухой раскат дают вполне правдоподобный удар молнии.

Что важно не забыть

Браузер не даст воспроизвести звук, пока человек не нажал что-нибудь на странице. Поэтому AudioContext создаётся только по нажатию «Играть», а не при загрузке.

Ещё у exponentialRampToValueAtTime нельзя указывать ноль: экспонента до нуля не доходит, и браузер выдаёт ошибку. Поэтому везде стоит 0.0001.