Шпаргалка з Web Audio API
Web Audio API моделює звук як орієнтований граф: джерела → вузли обробки → призначення. Кожна операція (підсилення, фільтрація, реверберація, синтез, FFT-аналіз) — це вузол. Ця шпаргалка охоплює основні вузли, автоматизацію параметрів, власний DSP через AudioWorklet та аналіз спектра в реальному часі.
Архітектура графа AudioContext
Кожен звуковий конвеєр починається зі створення контексту та зʼєднання вузлів, наче ланцюг сигналу:
// Always create on user gesture (click/keypress) — browsers require it
const ctx = new AudioContext();
// Source → [effect nodes] → destination (speakers)
source.connect(gainNode);
gainNode.connect(filterNode);
filterNode.connect(ctx.destination);
ctx.state === 'suspended' і викликайте
ctx.resume() усередині обробника кліку. Поширений патерн
— створювати контекст під час першого кліку на канвас.
| Тип вузла | Клас | Роль |
|---|---|---|
| Джерело | OscillatorNode, AudioBufferSourceNode, MediaStreamSourceNode | Генерує аудіосигнал |
| Ефект | GainNode, BiquadFilterNode, ConvolverNode, DynamicsCompressorNode | Обробляє сигнал |
| Аналіз | AnalyserNode | Знімає сигнал, не змінюючи його |
| Розгалужувач/Зливач | ChannelSplitterNode, ChannelMergerNode | Багатоканальна маршрутизація |
| Призначення | AudioDestinationNode | Кінцевий вихід (динаміки/навушники) |
OscillatorNode — синтез
const osc = ctx.createOscillator();
osc.type = 'sine'; // sine | square | sawtooth | triangle | custom
osc.frequency.setValueAtTime(440, ctx.currentTime); // 440 Hz = A4
const gain = ctx.createGain();
gain.gain.value = 0.3; // -1..1 safe range; >1 clips
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 2.0); // plays for 2 seconds
// Custom waveform via PeriodicWave:
const real = new Float32Array([0, 1, 0.5, 0.25]); // cosine terms (DC, f, 2f, 3f)
const imag = new Float32Array([0, 0, 0, 0]);
const wave = ctx.createPeriodicWave(real, imag);
osc.type = 'custom';
osc.setPeriodicWave(wave);
GainNode & BiquadFilterNode
// Gain: simple volume control
const gain = ctx.createGain();
gain.gain.value = 0.5; // linear amplitude; decibels: 10^(dB/20)
// BiquadFilter: EQ / tone control
const filter = ctx.createBiquadFilter();
filter.type = 'lowpass'; // lowpass|highpass|bandpass|notch|peaking|lowshelf|highshelf|allpass
filter.frequency.value = 1000; // cutoff freq (Hz)
filter.Q.value = 1.0; // resonance; >10 → ringing; Q=0.707 = Butterworth
filter.gain.value = 6; // only for peaking/shelf types (dB)
// Telephone effect (band-limited to 300–3400 Hz)
const lowCut = ctx.createBiquadFilter();
lowCut.type = 'highpass';
lowCut.frequency.value = 300;
const highCut = ctx.createBiquadFilter();
highCut.type = 'lowpass';
highCut.frequency.value = 3400;
source.connect(lowCut).connect(highCut).connect(ctx.destination);
ConvolverNode — реверберація на імпульсній характеристиці
Згорткова реверберація множить кожен семпл на записану імпульсну характеристику приміщення (IR), додаючи реалістичну акустику. IR-файли (.wav) вимірюють пострілом зі стартового пістолета або синусоїдальним свіпом у цільовому просторі.
async function loadReverb(url) {
const resp = await fetch(url);
const arrBuf = await resp.arrayBuffer();
const audioBuf = await ctx.decodeAudioData(arrBuf);
const conv = ctx.createConvolver();
conv.buffer = audioBuf;
conv.normalize = true; // auto-scale IR to prevent clipping
return conv;
}
// Wet/dry mix with GainNodes:
const dry = ctx.createGain(); dry.gain.value = 0.7;
const wet = ctx.createGain(); wet.gain.value = 0.3;
source.connect(dry).connect(ctx.destination);
source.connect(conv).connect(wet).connect(ctx.destination);
exp(-4 * t / duration) — це дає природне розсіяне
звучання зали, достатньо добре для ігор.
AnalyserNode — FFT у реальному часі
const analyser = ctx.createAnalyser();
analyser.fftSize = 2048; // must be power of 2; 32–32768
analyser.smoothingTimeConstant = 0.8; // 0=no smoothing, 1=max (sluggish)
analyser.minDecibels = -90;
analyser.maxDecibels = -10;
source.connect(analyser);
analyser.connect(ctx.destination); // analyser passes audio through unmodified
const bufLen = analyser.frequencyBinCount; // = fftSize / 2
const freqBuf = new Uint8Array(bufLen); // 0–255 per bin (magnitude)
const timeBuf = new Uint8Array(bufLen); // waveform (time domain)
function drawSpectrum(canvas) {
analyser.getByteFrequencyData(freqBuf); // fill freq data
const ctx2 = canvas.getContext('2d');
ctx2.clearRect(0, 0, canvas.width, canvas.height);
const barW = canvas.width / bufLen;
freqBuf.forEach((val, i) => {
const pct = val / 255;
ctx2.fillStyle = `hsl(${200 + pct * 160}, 80%, 50%)`;
ctx2.fillRect(i * barW, canvas.height * (1 - pct), barW - 1, canvas.height * pct);
});
requestAnimationFrame(() => drawSpectrum(canvas));
}
Частота біна i:
f = i × sampleRate / fftSize. Усталена частота
дискретизації — 44100 Гц, тож при fftSize=2048: бін 0 = 0 Гц, бін 1 =
21,5 Гц, бін 1024 = 22050 Гц (частота Найквіста).
AudioWorkletProcessor — власний DSP
ScriptProcessorNode застарів.
AudioWorkletProcessor працює у виділеному аудіопотоці
(без пауз на збирання сміття) і обробляє звук блоками по 128 семплів:
// worklet-processor.js (separate file, served as script)
class WhiteNoiseProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
const output = outputs[0];
for (const channel of output) {
for (let i = 0; i < channel.length; i++) {
channel[i] = (Math.random() * 2 - 1) * 0.3;
}
}
return true; // return false to deactivate node
}
}
registerProcessor('white-noise', WhiteNoiseProcessor);
// In main script:
await ctx.audioWorklet.addModule('worklet-processor.js');
const noise = new AudioWorkletNode(ctx, 'white-noise');
noise.connect(ctx.destination);
addModule() вимагає, щоб файл ворклета був з того самого
походження або мав заголовки CORS. Під час локальної
розробки роздавайте файли через локальний HTTP-сервер — не
використовуйте протокол file://.
Автоматизація AudioParam
Усі AudioParam підтримують планування з точністю до семпла — набагато
точніше за setTimeout:
const g = ctx.createGain();
const t = ctx.currentTime;
g.gain.setValueAtTime(0, t); // instant jump
g.gain.linearRampToValueAtTime(1, t + 0.01); // attack
g.gain.exponentialRampToValueAtTime(0.7, t + 0.3); // decay (must be >0)
g.gain.setTargetAtTime(0.5, t + 0.3, 0.1); // sustain (time constant)
g.gain.cancelScheduledValues(t + 2.0);
g.gain.linearRampToValueAtTime(0, t + 2.5); // release
// LFO modulating pitch:
const lfo = ctx.createOscillator();
lfo.frequency.value = 5; // 5 Hz vibrato
const lfoGain = ctx.createGain();
lfoGain.gain.value = 15; // ±15 Hz pitch deviation
lfo.connect(lfoGain).connect(osc.frequency);
Поширені підводні камені
-
Політика автовідтворення: AudioContext стартує
призупиненим до жесту користувача. Завжди викликайте
ctx.resume()усередині обробника click/keydown, або ж конструкторAudioContextмає бути розміщений усередині нього. -
Вузли-джерела одноразові: ви не можете перезапустити
зупинений
OscillatorNodeчиAudioBufferSourceNode. Щоразу створюйте новий екземпляр. -
Підсилення > 1 обрізає: цифрове обрізання дає
різкі спотворення. Використовуйте
DynamicsCompressorNodeу кінці ланцюга, щоб захистити вихід. -
Частотні дані Float проти Uint8:
getByteFrequencyData()повертає 0–255.getFloatFrequencyData()повертає точні значення в дБ (відʼємні, наприклад від -90 до -10). Для точних вимірювань використовуйте float. -
sampleRate: усталено 44100 Гц. Деякі системи
використовують 48000 Гц. Для обчислень частотних бінів завжди беріть
ctx.sampleRate, а не жорстко зашите значення.