Appearance
AR Engine — музыка, которую видно
AR Engine — это аудио-реактивный визуализатор, который полностью работает в браузере: никакого сервера, загрузки файлов на чужие диски или установки. Бросаете трек (или целую папку) в окно, и музыка превращается в живую графику — облака плазмы, узоры Хладни или пульсирующую «радужку», которая расширяет зрачок на каждый удар.
→ Открыть живое демо (нужен Chrome 113+ с поддержкой WebGPU)
Зачем
Я инженер и музыкант, и мне давно хотелось не просто слышать звук, а видеть его структуру: где бас, где атака, насколько «яркий» тембр в данный момент. Большинство визуализаторов реагируют на одну общую громкость. Хотелось честного частотного анализа в реальном времени — и чтобы это можно было запустить с любого устройства, не доверяя свою музыку облаку. Отсюда два жёстких ограничения проекта: всё считается локально, и всё работает на нативных браузерных API без CDN.
Как это устроено
Движок разделён на два слоя, которые общаются через компактный вектор признаков.
1. DSP-ядро на C → WASM
Анализ звука написан на чистом C и собирается в engine.wasm через Emscripten. Каждый кадр в ядро уходит блок PCM-сэмплов, а наружу отдаётся структура AudioFrame — плоский массив из 13 чисел с плавающей точкой, который JS читает прямо из WASM-кучи:
- радикс-2 FFT → спектр магнитуд;
- 4 полосы RMS-огибающих: sub (20–80 Гц), bass (80–250), mid (250–4000), high (4000–16000);
- детектор битов по спектральному потоку + оценка BPM;
- спектральные дескрипторы: centroid (яркость тембра) и tonalness (тональность против шума).
ABI устроен по принципу «только добавлять поля в конец» — так все существующие смещения в JS-куче остаются валидными при расширении. Это позволяет наращивать признаки, не ломая бридж. Сами дескрипторы — чистые функции спектра, поэтому покрыты нативными C-юнит-тестами (их можно собрать обычным cc, без Emscripten).
2. Рендер на WebGPU
Графику считает GPU. Симуляция гоняет до 500 000 частиц через compute-шейдеры на WGSL:
particles_update.wgsl— компьют-проход с тремя силовыми полями (vortex / chladni / nova);particles_draw.wgsl— аддитивная отрисовка точек;render.wgsl— тонмаппинг, палитра и финальная тонировка.
Поверх лежит обратная связь (ping-pong текстуры): след частиц затухает с настраиваемым коэффициентом, создавая шлейфы и «дыхание» картинки в такт музыке.
Вместо упрощённого 2D-фолбэка движок честно требует WebGPU, но деградирует по мощности GPU: рендерер определяет класс адаптера (Apple Silicon и дискретные NVIDIA/AMD → high, встроенная Intel → low) и масштабирует число частиц — 150k / 300k / 500k. В тишине цикл дросселируется до ~20 fps, чтобы зря не греть GPU.
Режимы и автопилот
Четыре пресета, каждый со своей палитрой и физикой:
- Fluid — аврора/плазменное облако, дышащее с музыкой.
- Cymatics — фигуры Хладни: тёмно-синяя пластина с белыми узловыми линиями.
- Nova — «радужка глаза»: частицы текут по объёму сферы (ортографическая проекция), яркий лимб возникает естественно из-за уплотнения к краю (
sin φ), а удар расширяет зрачок — настоящий эффект мидриаза. - Auto — автопилот, переключающий режимы по музыкальной сетке.
Авто-режим — самая интересная часть. Он считает удары по нарастающим фронтам beat_pulse и меняет картинку каждые 4 / 8 / 16 битов (энергичные пассажи — чаще, спокойные — реже). Какой режим включить, подсказывает медленная EMA спектрального профиля: бас тянет к Nova, верх и тишина — к Cymatics, баланс — к Fluid. Для эмбиента и треков с редкими ударами есть запасной таймер от BPM, чтобы картинка не «застывала», а переход всегда идёт через эффект scatter-dissolve — частицы рассыпаются и собираются в новую форму.
Тембр → цвет
Отдельный модуль превращает пару дескрипторов (centroid, tonal) в оттенок. Это не классификация инструментов (на 60 fps она нереалистична), а импрессионистская 2D-карта: точки-«якоря» инструментов (мужской/женский голос, струнные, перкуссия, бас) на плоскости, а текущий цвет интерполируется между ними по обратным расстояниям (веса Шепарда). Оттенок усредняется по кругу — через единичные векторы и atan2, иначе блендинг ломался бы на стыке 1.0/0.0 цветового круга.
Что было сложно
- Музыкальные переходы. Сделать так, чтобы смена режима попадала на фразу, а не на случайный кадр — отсюда битовая сетка с фолбэком по BPM.
- Объёмная Nova. Заставить плоские частицы читаться как объём глаза: поток по меридианам сферы, затухание задней полусферы (
smoothstepпоcos), мидриаз на бит. Геометрия продублирована вnova_project.jsи покрыта тестами, чтобы JS и WGSL не разъезжались. - Запись. Параллельный full-res путь 1920×1080 в оффскрин-канвас, экспорт в
.webm; при записи дросселирование отключается, иначе кодировщик «голодает» в тихих местах.
Текущее состояние
Работают все четыре режима, drag-and-drop файлов и папок с плейлистом, захват звука вкладки (например, Яндекс Музыки) через getDisplayMedia, запись в .webm, полноэкранный режим с автоскрытием интерфейса и живая подстройка Nova прямо из консоли. WGSL проверяется по косвенным признакам (чистая сборка, согласованность UBO-разметки, зеркальные JS-тесты) плюс ручной визуальный прогон в Chrome.
→ Попробовать самому: /ar/
Из блога
- Новый сингл — музыка, ради которой всё это и затевалось.
- Critical Rendering Path — как браузер превращает данные в пиксели; фундамент, на котором стоит весь GPU-рендер.