Это техническая статья о том, как Morph Музыкальная студия — полноценная цифровая рабочая станция для аудио с 5 встроенными инструментами, микшером с цепями эффектов и вспомогательными отправлениями, 30+ эффектами (включая 5 портов Airwindows Tier-1 и истинный пиковый лимитер), rack-модулем MIDI-эффектов, хостом плагинов WAM v2 и слоем сотрудничества на основе Yjs CRDT — работает полностью в вкладке браузера.
Практический вопрос: насколько это на самом деле конкурирует с настольными DAW? Честный ответ: для большинства рабочих процессов продюсеров — да. Для глубоких экосистем сторонних устройств — пока нет. Откройте Музыкальная студия, чтобы услышать сам продукт. Эта статья — об инженерии.
Стек технологий
- React + Vite фронтенд
- Web Audio API для аудиографа
- AudioWorkletProcessor для пользовательских DSP (в фоновом потоке)
- WebAssembly для производительности критичных к DSP (собственные сборки WASM)
- MessagePort + ring buffers для обмена данными между интерфейсом и worklet
- Yjs для CRDT-сотрудничества
- IndexedDB для сохранения проектов и хранения образцов
- WebMIDI для ввода с аппаратных контроллеров
- OfflineAudioContext для bounce/export
1. Аудиограф
Каждый трек в Музыкальной студии проходит через цепочку: инструмент → цепочка эффектов трека → отправка на вспомогательный канал → полоса микшера → основной бус → лимитер → назначение. Каждая стрелка — это соединение Web Audio AudioNode.
Web Audio предоставляет нам AudioContext, GainNode, BiquadFilterNode, ConvolverNode, AnalyserNode и другие. Эти узлы оптимизированы движками браузеров (V8/SpiderMonkey/JSC глубоко инвестировали в них). Для стандартных эффектов — EQ, автоматизации громкости, базовой фильтрации — мы используем нативные узлы. Они быстрые и хорошо протестированы.
Для пользовательских эффектов — портов Airwindows, нашей насыщенности, нашего истинного пикового лимитера — мы используем AudioWorkletProcessor, который работает на аудио-потоке, гарантирует приоритет реального времени и является правильным архитектурным местом для нестандартной DSP.
class PurestDriveProcessor extends AudioWorkletProcessor {
process(inputs, outputs) {
const input = inputs[0];
const output = outputs[0];
// Airwindows PurestDrive port — ~30 LOC of saturation math
for (let ch = 0; ch < input.length; ch++) {
for (let i = 0; i < input[ch].length; i++) {
const s = input[ch][i];
// [...tape-saturation math...]
output[ch][i] = processed;
}
}
return true;
}
}
registerProcessor('purest-drive', PurestDriveProcessor);
Worklets поставляются в папке public/audio-worklets/*.js и загружаются через audioContext.audioWorklet.addModule(path). После загрузки любая часть графика может создавать AudioWorkletNode для этого процессора. Обработка выполняется вне основного потока — анимации интерфейса не мешают аудио.
2. 5 портов Airwindows Tier-1
Chris Johnson's Airwindows — это 2 500+ аудио-плагинов, лицензированных по MIT, легендарно вкусный DSP. Мы перенесли 5 из них в браузерные AudioWorklets для Wave 12 в Музыкальной студии:
- Pressure6 — прозрачная компрессия, которая дышит
- ToTape9 — моделирование аналогового тапа
- PurestDrive — минимальная фазовая насыщенность без резких высоких частот
- CreamCoat — тепло + гармонический экстрактор
- NotJustAnotherDither — TPDF-диффузия для снижения глубины бит (мастеринг)
Каждый порт составляет ~30-80 строк кода математики в AudioWorkletProcessor. Порты не совпадают с Airwindows бит за битом (разные обработки FMA/денормализации между C++ и JS численными двигателями), но совпадают по звуку. Тесты на ноль сохраняют выходные данные ≤ -80 dBFS остаточного уровня против эталонов, поэтому изменения DSP обнаруживаются.
3. Истинный пиковый лимитер
Современные стриминговые платформы (Spotify, Apple Music, YouTube) применяют нормализацию громкости по true-peak в соответствии с ITU-R BS.1770-5. Пиковые значения и интер-образные пики — это разные вещи; сигнал может достигать -1 dBFS по образцу, но превышать 0 dBFS true-peak после преобразования ЦАП. Нелимитатор true-peak будет обрезать сигнал в ЦАП, даже если ваш DAW показывает его чистым.
Наш лимитер реализует полный стандарт ITU: 4x пересampling, обнаружение пиков между образцами, lookahead, мягкий край сброса. Он поставляется в форме AudioWorklet по адресу public/audio-worklets/true-peak-limiter-processor.js.
4. Коммуникация вне основного потока
Аудиограф работает на аудиопотоке. Интерфейс работает на основном потоке. Изменения параметров (поворот регулятора, затухание трека) должны передаваться от интерфейса к аудио без сбоя аудио.
Web Audio использует AudioParam нативно для стандартных параметров (GainNode gain, BiquadFilterNode frequency). Для кастомных параметров в worklet мы используем MessagePort с структурированным клонированием для сообщений интерфейса, а для высокочастотных данных автоматизации — буферы с SharedArrayBuffer.
SharedArrayBuffer требует Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp заголовков. Эти заголовки установлены в public/_headers для маршрутов /soundbend + /pulse. Тайм-делей использует кольцевой буфер на 96 кГц для избежания задержек порта сообщений.
5. 5 нативных инструментов
Музыкальная студия поставляется с 5 инструментами, все кастомные:
FM-синтез — 6-операторный FM-синтез, моделированный на DX7 / Operator. Визуализатор алгоритма + полная матрица модуляции. ~800 LOC JS для интерфейса, ~300 LOC для работы с голосовым распределителем.
Вейвлет-синтез — синтез с морфингом таблиц. Спектральное искажение + унисон + стерео-детюн. 30+ заводских вейвлет-таблиц. Использует ConvolverNode для плавного переключения между вейвлет-таблицами в реальном времени.
Аналоговый вычитающий — полифонический синтезатор с 2 осцилляторами и фильтрами Cytomic SVF. Cytomic — это топология фильтра переменного типа, который звучит правильно; простой SVF имеет проблемы с алиасингом при fc > SR/6. Cytomic порт решает эту проблему с помощью математики нулевой задержки.
DrumRack + Sampler — воспроизведение образцов по кнопкам с настройкой тона, фильтра и громкости на каждую кнопку. Образцы хранятся в IndexedDB для быстрого доступа. 5 заводских наборов по жанрам (боомбап, трэп, хаус, DNB, афробит).
SeedSynth + Geist (в устаревшем AudioStudioEngine — теперь объединено в Музыкальной студии с Wave 14) — генетический генератор патчей (Synplant 2-класс) и барабанная машина в стиле Geist. Это собственная IP-собственность, которая сделала консолидационную доктрину важной — нам пришлось сохранить идентичное поведение на уровне битов после слияния 30 000 строк кода. 100-семейный нулевой тест пройден после слияния.
6. Микшер + дополнительные выходы
Настоящий микшер — не просто регуляторы громкости на каждом треке, но и вставки эффектов на каждом треке, пред/пост-фейдерные вспомогательные отправки, мастер-бус — это то, что отличает DAW от секвенсора. Микшер Музыкальная студия имеет класс ~655 LOC (src/audio/mixer.js). Каждый трек представляет собой граф AudioNode с точками вставки, точками отправки и измерениями.
Сложная часть — предфейдерные и постфейдерные aux-сигналы. Предфейдерные отправляют сигнал на бус (реверберация) независимо от громкости трека (классический случай: параллельная реверберация, которая сохраняется при затухании трека). Постфейдерные масштабируются вместе с громкостью трека. Оба варианта включены.
7. MIDI + WAM-хост плагинов
WebMIDI成熟 — контроллерный ввод с любого USB-MIDI клавиатуры работает без проблем. Наша MIDI-рейк (Арпеджиатор, Шкала, Хорд) находится между контроллером и инструментом, преобразуя поток нот.
Для плагинов мы предоставляем Web Audio Modules v2 (WAM) — плагины, совместимые с браузером. Экосистема меньше, чем у VST (25-летний форпост), но растёт. Загрузка WAM осуществляется через getWamHost().load(wamUri), который возвращает AudioNode, который можно вставить в любую цепочку.
WAMs требуют перенаправленного CSP для каждой маршрута, так как загружают произвольный пользовательский JS. Наши _headers ограничивают CSP для /soundbend с разрешением script-src для известных WAM-регистров.
8. OfflineAudioContext для bounce
Экспорт работает через OfflineAudioContext — обработка быстрее, чем в реальном времени (или медленнее, в зависимости от сложности цепочки), чтобы создать статический AudioBuffer, затем кодирование в WAV с помощью нашего встроенного кодировщика.
Примечание: не все узлы работают в OfflineAudioContext. MediaStreamAudioSourceNode не работает (очевидно, нельзя записывать живой микрофон оффлайн). Некоторые ворклеты требуют тщательной сброса состояния при переходе между онлайн и оффлайн. Мы сталкивались с этим многократно, и теперь Mixer.toJSON() + Mixer.fromJSON(offlineCtx) — это канонический паттерн: сериализуем состояние микшера, восстанавливаем параллельный микшер в оффлайн-контексте и рендерим.
9. Сотрудничество через Yjs
Yjs — библиотека CRDT, которая обрабатывает совместное редактирование с несколькими пользователями с автоматическим разрешением конфликтов. Наш класс SoundbendSession обертывает Yjs с переключением по трекам (Y.Map для метаданных трека, Y.Array для шагов паттерна, Y.Text для текста песен/нот).
Два-четыре продюсера могут одновременно редактировать один проект с точностью до отсчёта образцов. Это сложнее, чем совместная работа с текстовыми документами, потому что аудиоданные в разы больше — мы делимся метаданными и паттернами через Yjs, а не сырым аудио. Сырые образцы остаются локально (IndexedDB на пользователя).
10. Бюджет производительности
Запуск полноценного DAW в браузере с интерфейсом 60fps + реальным временем аудио + SharedArrayBuffer + WASM + несколько worklets требует тщательного управления бюджетом. Некоторые цифры:
- Аудиопоток: ~2 мс / буфер 128 образцов при 48 кГц (подфрейм)
- Основной поток: 16,6 мс / 60 кадров в секунду; обработка аудио должна оставаться <1 мс на кадр
- Настройка Worklet: одноразовая стоимость ~10-50 мс на загрузку worklet (отправить 15+ worklet — мы группируем)
- Выделение SharedArrayBuffer: 4KB буферы-кольца на каждый динамический параметр worklet
- Размер фрагмента Музыкальной студии (после консолидации): ~496 КБ несжатого, ~120 КБ gzip
- Полная загрузка проекта (холодный старт): ~800 мс на среднем ноутбуке
- Экспорт: ~0,3x до 2x реального времени в зависимости от сложности цепочки эффектов
11. Тест-холст для нулевых проверок
DSP-регрессии незаметны — звук звучит по-разному в тонких деталях, которые не могут обнаружить проверки кода. Музыкальная студия поставляется с нулевой тестовой средой, управляемой Puppeteer, в tests/audio-null/.
Для каждого эффекта × предустановки × тестового сигнала тестовый стенд отображается через OfflineAudioContext внутри headless Chromium, нулевые тесты результата WAV против зафиксированного золотого образца, и сборка завершается неудачей, если остаточный пик превышает -60 dBFS.
Wave 14F добавил 100 базовых настроек SeedSynth и 32 золотых паттерна драм-барабанов Geist. Когда мы объединили Музыкальная студия в AudioStudio (Wave 14B-G), нулевой тест обнаружил все, что могло бы тихо сломать проприетарные инструменты. 96 из 100 базовых настроек SeedSynth прошли проверку на идентичность битов. 4 сбоя были предыдущими проблемами в Chromium Web Audio (дрейф резонанса фильтра высокого Q между экземплярами OfflineAudioContext) — не регрессии при слиянии, и не исправимы из JavaScript.
DSP-регрессии тихие. Проверка кода их не слышит. Нулевой тестовый комплекс может — поэтому он блокирует CI.
12. Почему браузер + клиент-сторона был правильным выбором
Универсальные DAW имеют 30-летний преимущества. Превзойти их по функционалу — долгий путь. Но цена — это была победа, которую можно было одержать сразу, сделав другие архитектурные выборы.
WebAssembly преодолел порог производительности в 2022–2024 годах. Web Audio AudioWorklets широко внедрились примерно в то же время. SharedArrayBuffer + COOP/COEP стабилизировались. Chromium, Firefox и Safari поддерживают необходимые API с достаточной производительностью.
Импликация цены: если обработка аудио происходит на устройстве пользователя, мы не платим за это. Наши предельные издержки на каждого подписчика Pro почти нулевые.
Читайте манифест для экономики. Для DSP это большая часть истории.
Попробуйте
Музыкальная студия доступен по morph.cool/music-studio. Бесплатный тариф работает без регистрации. Поддерживается WebMIDI-клавиатура, но не обязательна.
— команда Morph
22 апреля 2026 г.