
Як я побудував аудіо-реактивний 3D-візуалізатор з Three.js та Web Audio API
https://ift.tt/SMhWPeE
Я нещодавно випустив Audio Reactive 3D Visualizer v1.0.0, відкритий браузерний застосунок, який перетворює завантажені музика та зображення на візуали у реальному часі.
Користувачі можуть генерувати синхронізовані 3D-об’єкти, частинки, хвилі та ефекти зображень, а потім експортувати результат як 1920×1080 MP4 при 30 FPS.
Повний процес обробки медіа виконується локально у браузері.
– Живий демо: https://waveform.tranjectories.xyz/
– Вихідний код: https://ift.tt/byjrsFJ
– Реліз v1.0.0: https://ift.tt/zCEUtl2
In this article, I will explain the main ideas behind the project:
– Розкодування аудіо за допомогою Web Audio API
– Перетворення даних частот на стабільні візуальні сигнали
– Карти аудіоданих у сцени Three.js
– Створення аудіо-реактивних систем частинок
– Розділення відтворення в реальному часі від експорту відео
– Експорт MP4 за допомогою WebCodecs
– Резервний варіант з використанням ffmpeg.wasm
– Обробка завантаженого медіа локально
Чому я це створив
Я працюю як фронтенд-розробник та музичний продюсер.
Незалежні музиканти часто потребують візуального контенту для релізів, соціальних мереж, живих виступів, проєктів вокального синтезу та концептів музичного відео.
Однак повноцінне музичне відео може бути дорогим та витратним за часом.
Є також комунікаційна проблема: музикант може точно знати, де візуалізація має пульсувати, спотворюватися, розширюватися чи сжиматися, але передати ці темпи та атмосферу іншому творцю досить складно.
Я хотів створити інструмент, який швидко перетворює структуру та енергію треку на візуальний відправний пункт.
Головні вимоги були такі:
– Не потрібний настільний відеоредактор
– Відображення в реальному часі, яке реагує на аудіо
– Кілька візуальних стилів
– Налаштовувані параметри
– Повноекранний режим Live / VJ
– Повний експорту HD MP4
– Локальна обробка завантаженого медіа
– Відкрита база коду, яку розробники можуть вивчати та розширювати
Технологічний стек
Застосунок побудований з:
– React 19
– TypeScript
– Vite
– Three.js
– React Three Fiber
– React Three Drei
– WebGL
– Canvas 2D
– Web Audio API
– Zustand
– Material UI
– WebCodecs
– mp4-muxer
– ffmpeg.wasm
– Cloudflare Workers Static Assets
В даний час є три основних візуальних режими:
3D Visualizer
– Багато сцен-п presets, системи частинок, керування геометрією, рух камери, морфінг, шейдери та ефекти постобробки
Wave Visualizer
– Відображає завантажену аудіо у вигляді горизонтальних, кругових або стовпчикових хвиль
Image FX
– Накладає аудіо-реактивний світло-емісію, розмиття, RGB-зсув, шум, спотворення та пульс на завантажені зображення
Декодування аудіо в браузері
Перший крок — завантаження та декодування аудіофайлу за допомогою Web Audio API.
Спрощена реалізація вигляє так:
const audioContext = new AudioContext();
const arrayBuffer = await file.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
Результуючий AudioBuffer містить декодовані PCM-дані аудіо.
Це корисно для офлайн-обробки, таких як:
– Створення попередніх переглядів хвиль
– Вибірка повного треку
– Виявлення пік
– Оцінювання BPM
– Підготовка детермінованих даних експорту
Для відтворення в реальному часі аналайзерний вузол забезпечує безперервно оновлювані дані частот та хвиль:
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2048;
analyser.smoothingTimeConstant = 0.8;
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
const waveformData = new Uint8Array(analyser.fftSize);
Під час відтворення масиви оновлюються за допомогою:
analyser.getByteFrequencyData(frequencyData);
analyser.getByteTimeDomainData(waveformData);
Ці масиви утворюють з’єднання між аудіо-двигуном та рендеринг-системою.
Перетворення FFT-даних на візуальні сигнали
Сирі дані FFT не настільки корисні для анімації без попередньої обробки. Рендерер зазвичай потребує набір нормалізованих сигналів, таких як:
– Загальна енергія
– Енергія басів
– Енергія середнього діапазону
– Енергія високих частот
– Інтенсивність пік
– Зміщення хвиль
– Згладжена амплітуда
Приклад посередника для усереднення діапазону частот:
function averageRange(values: Uint8Array, start: number, end: number): number {
let sum = 0;
const safeEnd = Math.min(end, values.length);
for (let index = start; index < safeEnd; index += 1) {
sum += values[index];
}
const count = Math.max(1, safeEnd - start);
return sum / count / 255;
}
Спектр можна ділити на широкі діапазони:
const bass = averageRange(frequencyData, 0, 24);
const mids = averageRange(frequencyData, 24, 96);
const highs = averageRange(frequencyData, 96, 256);
Ідеальні діапазони залежати від розміру FFT, частоти дискретизації, стилю музики та бажаної візуальної реакції.
Для електронної музики басові транзєнти добре підходять для великомасштабного руху, тоді як високі частоти можуть керувати частинками, спалахами, шумом та дрібними деталями.
Сглажування руху аудіо-реактивності
Безпосереднє відображення значень частот на об’єкти несе нестабільний рух. Аудіоданні змінюються надзвичайно швидко, тому сигнали згладжуються перед передачею рендереру:
function smoothValue(current: number, target: number, factor: number): number {
return current + (target - current) * factor;
}
Приклади:
smoothedBass = smoothValue(smoothedBass, bass, 0.12);
smoothedMids = smoothValue(smoothedMids, mids, 0.08);
smoothedHighs = smoothValue(smoothedHighs, highs, 0.06);
Різні значення згладжування створюють різні візуальні характеристики:
- Швидке згладжування: відчувається як швидке та агресивне
- Повільне згладжування: плавне та атмосферне
- Відокремлена атака та реліз—резкі удари з більш м’яким затемненням
Цей етап підсилення сигнала є одним з найважливіших у аудіо-реактивній системі.
Картування аудіоданих до Three.js
React Three Fiber дозволяє керувати сцени Three.js через компоненти React.
Спрощена реактивна сітка може виглядати так:
import { useFrame } from "@react-three/fiber";
import { useRef } from "react";
import { Mesh } from "three";
type ReactiveMeshProps = {
getEnergy: () => number;
};
export function ReactiveMesh({ getEnergy }: ReactiveMeshProps) {
const meshRef = useRef
useFrame(() => {
if (!meshRef.current) {
return;
}
const energy = getEnergy();
const scale = 1 + energy * 0.8;
meshRef.current.scale.setScalar(scale);
meshRef.current.rotation.x += 0.002 + energy * 0.01;
meshRef.current.rotation.y += 0.003 + energy * 0.015;
});
return (
);
}
У повній програмі аудіосигнали можуть керувати:
– деформацією геометрії
– позиціями частинок
– розміром частинок
– рухом камери
– uniforms шейдерів
– рівнем шуму
– силою пост-обробки
– спотворенням зображення
– RGB-зміщенням
– сяйвом та розмиттям
– радіусом хвиль
– прозорістю шарів
Важливим архітектурним рішенням було зберегти аналіз аудіо окремо від візуального рендерингу. Аналізний шар виробляє нормалізовані сигнали. Кожний візуалізатор споживає ці сигнали, не потребуючи знати, як саме аудіо декодувалося.
Створення аудіо-реактивних частинок
Системи частинок особливо корисні для візуалізації музики, оскільки різні діапазони частот контролюють різні частини сцени. Наприклад:
– Бас керує загальним радіусом
– Середній діапазон керує турбулентністю
– Високі частоти керують миготінням
– Загальна енергія контролює розмір частинок
– Значення хвиль зміщують окремі позиції частинок
Спрощене оновлення частинок може виглядати так:
for (let index = 0; index < particleCount; index += 1) {
const offset = index * 3;
const waveformIndex = index % waveformData.length;
const waveformValue = (waveformData[waveformIndex] - 128) / 128;
positions[offset] = basePositions[offset] + waveformValue * intensity;
positions[offset + 1] = basePositions[offset + 1] + Math.sin(time + index * 0.01) * mids;
positions[offset + 2] = basePositions[offset + 2] + bass * depth;
}
Після зміни BufferAttribute Three.js його потрібно позначити для оновлення:
positionAttribute.needsUpdate = true;
Оновлення тисяч частинок кожної кадром може бути дорогим. Продуктивність залежить від:
- кількості частинок
- частоти оновлення атрибутів
- складності геометрії
- коефіцієнта піксельного відображення пристрою
- проходів пост-обробки
- розподілі пам’яті в циклі рендерингу
Уникнення зайвого створення об’єктів під час кожного кадру є особливо важливим.
Керування станом застосунку
Візуалізатор має багато регульованих параметрів:
- Візуальний режим
- Активний пресет
- Кількість частинок
- Розмір частинок
- Форма частинки
- Відстань до камери
- Інтенсивність морфінгу
- Стиль хвиль
- Ефекти зображення
- Порядок шарів
- Позиція відтворення
- Прогрес експорту
- Контролі живого режиму
Я використовую Zustand для спільного стану застосунку.
type VisualizerState = {
bass: number;
mids: number;
highs: number;
particleSize: number;
setAudioBands: (bass: number, mids: number, highs: number) => void;
};
const useVisualizerStore = create
bass: 0,
mids: 0,
highs: 0,
particleSize: 1,
setAudioBands: (bass, mids, highs) => set({ bass, mids, highs }),
}));
Однак не кожне оновлення аудо-кадру має викликати повний перерендер React. Для швидко змінюваних значень може бути ефективніше використати рефи, спеціалізовані об’єкти аналізу або прямий доступ до сховища. UI-стан та стан рендер-цикла мають різні вимоги до продуктивності.
Реальний час відтворення та використання відеовиходу мають різний годинник
– Реальне відтворення може користуватися анімаційним цикл браузера (requestAnimationFrame(render)).
– Відеоекспорт потребує детерміністичних кадрів. При 30 FPS кожен кадр time = frameIndex / 30; Результат не повинен залежати від швидкості рендерингу комп’ютера.
Експорт MP4 за допомогою WebCodecs
– WebCodecs надає низькорівневий доступ до кодувальників медіа браузера.
– Пропускний конвеєр приблизно такий:
1) Рендер кадру візуалізації
2) Створити VideoFrame
3) Надіслати його до VideoEncoder
4) Отримати зашифровані уривки
5) Об’єднати відео та аудіо треки в MP4
6) Створити завантажуваний Blob
Спрощена функція кодування виглядає так:
function encodeCanvasFrame(canvas: HTMLCanvasElement, encoder: VideoEncoder, frameIndex: number, fps: number): void {
const timestamp = Math.round((frameIndex / fps) * 1_000_000);
const frame = new VideoFrame(canvas, { timestamp });
encoder.encode(frame);
frame.close();
}
Завдання закриття кожного VideoFrame важливе. Якщо кадри залишаться відкритими під час довгого експорту, пам’ять може швидко зростати.
Приклад конфігурації кодувальника:
const config: VideoEncoderConfig = {
codec: “avc1.42001f”,
width: 1920,
height: 1080,
bitrate: 8_000_000,
framerate: 30,
};
Перш ніж почати, застосунок має перевірити, чи браузер підтримує запитану конфігурацію:
const support = await VideoEncoder.isConfigSupported(config);
if (!support.supported) {
throw new Error(“The requested encoder configuration is not supported”);
}
Проєкт використовує mp4-muxer для об’єднання закодованого відео та аудіо в кінцевий MP4.
Чому я додав резервний шлях через ffmpeg.wasm
WebCodecs може бути швидким, але підтримка кодеків різниться між браузерами та ОС.
Браузер може підтримувати WebCodecs, але відхиляти певну конфігурацію кодека.
Тому застосунок повторно намагається зробити експорт через ffmpeg.wasm, якщо попередні спроби не вдалися.
try {
await exportWithWebCodecs();
} catch (error) {
console.warn(“WebCodecs export failed. Falling back to ffmpeg.wasm.”, error);
await exportWithFfmpeg();
}
Шлях за замовчуванням повільніший і зазвичай потребує більше пам’яті, але забезпечує роботу застосунку в більшому колі середовищ.
Розгорнутий застосунок спочатку намагається завантажити ядро ffmpeg локально. Якщо ці файли недоступні, він може отримати потрібні рантайм-ресурси з jsDelivr.
Зберігання завантаженого медіа локально
Завантажена музика та зображення не надсилаються на бекенд.
Браузер обробляє:
– Декодування аудіо
– Аналіз частот
– Взяття зразків хвиль
– Обробку зображень
– Рендеринг Three.js
– Кодування відео
– Генерацію MP4
Завершений MP4 формуються як браузерний Blob та завантажується локально.
Переваги:
– Приватна або не випущена музика не потребує завантаження
– Жодних затримок завантаження медіа
– Небагато витрат на серверне зберігання медіа
– Витрати на хостинг залишаються відносно низькими
– Застосунок можна розгортати переважно як статичні ресурси
Обмеження:
– Великі файли споживають пам’ять браузера
– Довгі експорти можуть навантажувати CPU
– Продуктивність залежить від пристрою
– Потрібно, щоб вкладка браузера залишалася відкритою
– Складні сцени важкі на менш потужних мобільних пристроях
Live / VJ режим
Застосунок також включає режим Live / VJ на повноекранному екрані.
Він:
– Приховує інтерфейс редагування
– Розширює візуальний вивід
– Підтримує управління клавіатурою
– Надає тимчасові прискорення ефектів
– Дозволяє використати візуалізатор під час експериментів з перфомансом
Головне завдання — тримати редагування та контроль за продуктивністю прив’язаними до одного стану, не дозволяючи UI-оновленням переривати рендеринг. Розділення оболонки застосунку та візуальних компонентів полегшило це.
Структура проекту
Кодова база організована за відповідальністю:
src/
├── audio/ # Декодування, FFT, хвиль, BPM та аналіз
├── export/ # WebCodecs, MP4 мікшування та fallback ffmpeg
├── ui/ # Контроли та канви візуалізатора
├── visual/ # Сцени Three.js, частинки, шейдери та пресети
├── App.tsx # оболонка застосунку та координація
└── store.ts # Спільний Zustand стан
Головний принцип архітектури:
Аналіз аудіо один раз, нормалізація результату, і дозволити кільком візуальним системам споживати ті самі сигнали.
Це дозволяє трьом режимам: 3D, хвильовий та ефект зображення, ділити одне музичне темпоритмічне тло, але рендерити по-різному.
Найскладніші частини
– Синхронізація експорту відео
– Різниці браузерних кодеків
– Управління пам’яттю
– Увімкнення стабілізованого руху
– Спільне використання аудіо-логіки між режимами візуалізації
Що я хочу поліпшити далі
Поточний план розвитку включає:
– Покращити доступність клавіатури
– Покращити підтримку екранного читача
– Адаптивні розкладки для вузьких екранів
– Автоматизовані тести для аудіо аналізу та експорту
– Збережні та обмінювані пресети візуалізації
– Кращі поради щодо довготривалого експорту
– Оновлений матрикс сумісності з браузерами
– Додаткові візуальні пресети
– Бенчмарки продуктивності для складних сцен
Репозиторій уже містить інструкції з внеску, шаблони для issues, pull requests, інструкції з безпеки, кодекс поведінки та ліцензію MIT.
Внески вітаються.
Спробуйте проєкт
Живий демо
– https://waveform.tranjectories.xyz/
Вихідний код
– https://ift.tt/byjrsFJ
Реліз v1.0.0
– https://ift.tt/zCEUtl2
Музичне відео, створене з візуалізатора
– https://www.youtube.com/watch?v=R8ItWr2V_ZA
Якщо вам цікаво Three.js, Web Audio API, креативне кодування, аудіовізуальні інструменти або браузерну обробку медіа, буду вдячний за ваш відгук.
Проблеми, пул-реквести та інші внески вітаються.
Якщо проєкт буде корисний вам, подумайте про зірку репозиторію.
Дякую за увагу.
HI-FI News
через DEV Community: typescript https://ift.tt/P7yctVv
Червень 15, 2026 о 01:34 за Київським часом.
June 15, 2026 at 01:34AM

Залишити відповідь