Урок · Початковий рівень · ~45 хв
Візуалізатор спектра на WebAudio API
Вузол AnalyserNode з Web Audio API надає FFT-дані в
реальному часі, які можна відображати у вигляді стовпців, осцилографа
або тривимірного частотного ландшафту — і все це без надсилання аудіо
на сервер. У цьому уроці ми пропустимо сигнал з мікрофона через
аудіограф і намалюємо кілька візуалізацій.
1Налаштування AudioContext та AnalyserNode
const ctx = new AudioContext(); const analyser = ctx.createAnalyser();
// Розмір FFT має бути степенем 2; frequencyBinCount = fftSize / 2
analyser.fftSize = 2048; // 1024 кошиків analyser.smoothingTimeConstant =
0.85; // 0 = без згладжування, 1 = максимальне // буфери для зчитування даних const
freqData = new Uint8Array(analyser.frequencyBinCount); // 0–255 на
кошик const timeData = new Uint8Array(analyser.fftSize); // форма хвилі //
з'єднуємо граф: source → analyser → destination
analyser.connect(ctx.destination);
frequencyBinCount — це половина
fftSize.
Кожен кошик охоплює sampleRate / fftSize Гц. За
fftSize = 2048 на частоті 44100 Гц це ~21,5 Гц на кошик.
2Запит доступу до мікрофона
let source; document.getElementById('start').addEventListener('click',
async () => { // Має запускатися дією користувача — без автовідтворення await
ctx.resume(); try { const stream = await
navigator.mediaDevices.getUserMedia({ audio: true, video: false });
source = ctx.createMediaStreamSource(stream);
source.connect(analyser); // ПРИМІТКА: НЕ з'єднуйте analyser з
ctx.destination, якщо не хочете акустичного зворотного зв'язку draw(); } catch (err)
{ console.error('Мікрофон недоступний:', err); } });
3Малювання 2D-діаграми спектра
const canvas = document.getElementById('spectrum'); const c2d =
canvas.getContext('2d'); const W = canvas.width, H = canvas.height;
function drawSpectrum() { analyser.getByteFrequencyData(freqData);
c2d.clearRect(0, 0, W, H); const barW = W / freqData.length; for (let
i = 0; i < freqData.length; i++) { const value = freqData[i]; //
0–255 const barH = (value / 255) * H; // Колір: низькі частоти = синій, високі
= червоний const hue = 240 - (value / 255) * 240; c2d.fillStyle =
`hsl(${hue},100%,50%)`; c2d.fillRect(i * barW, H - barH, barW - 1,
barH); } } function draw() { requestAnimationFrame(draw);
drawSpectrum(); }
4Малювання хвилі осцилографа
const waveCanvas = document.getElementById('waveform'); const wCtx =
waveCanvas.getContext('2d'); function drawWaveform() {
analyser.getByteTimeDomainData(timeData); wCtx.clearRect(0, 0, W, H);
wCtx.lineWidth = 2; wCtx.strokeStyle = '#22c55e'; wCtx.beginPath();
const sliceW = W / timeData.length; let x = 0; for (let i = 0; i <
timeData.length; i++) { const v = timeData[i] / 128.0; // нормалізуємо
до 0-2, центр у 1 const y = (v / 2) * H; if (i === 0) wCtx.moveTo(x,
y); else wCtx.lineTo(x, y); x += sliceW; } wCtx.lineTo(W, H / 2);
wCtx.stroke(); } // Викликайте drawWaveform() усередині циклу draw()
разом із drawSpectrum()
getByteTimeDomainData повертає необроблений буфер
відліків (PCM). Значення лежать у діапазоні 0–255, де 128 — тиша.
Осцилограф — найпростіший спосіб переконатися, що аудіо надходить.
53D-стовпці з Three.js InstancedMesh
import * as THREE from
'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
const BINS = 128; // використовуємо перші 128 частотних кошиків const geo = new
THREE.BoxGeometry(0.8, 1, 0.8); const mat = new
THREE.MeshStandardMaterial({ color: 0x22c55e }); const mesh = new
THREE.InstancedMesh(geo, mat, BINS); scene.add(mesh); const dummy =
new THREE.Object3D(); const colors = new THREE.Color(); function
update3DBars() { analyser.getByteFrequencyData(freqData); for (let i =
0; i < BINS; i++) { const v = freqData[i] / 255; // 0–1 const barH
= Math.max(0.05, v * 20); const x = (i - BINS / 2) * 1.1;
dummy.position.set(x, barH / 2, 0); dummy.scale.set(1, barH, 1);
dummy.updateMatrix(); mesh.setMatrixAt(i, dummy.matrix); // Градієнт
кольору за частотою colors.setHSL((1 - v) * 0.66, 1, 0.5);
mesh.setColorAt(i, colors); } mesh.instanceMatrix.needsUpdate = true;
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true; } //
Викликайте update3DBars() у циклі рендерингу
Використання InstancedMesh зводить усі 128 стовпців
до єдиного виклику відмалювання, тримаючи GPU у комфортному стані
навіть за високої частоти кадрів.