Skip to content

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-рендер.