Це технічний пост про те, як Morph Музична студія — повний цифровий аудіо-студія, що включає 5 вбудованих інструментів, мікшер з ефектами та допоміжними каналами, 30+ ефектів (включаючи 5 портів Airwindows Tier-1 та справжній лімітер true-peak), розкладку MIDI-ефектів, хост відповідного плагіна WAM v2 та шар спільної роботи на основі Yjs CRDT — працює виключно в вікні браузера.
Практичне питання: чи це дійсно конкурує з десктопними DAW? Щирі відповіді: для більшості робочих процесів продюсера — так. Для глибоких екосистем пристроїв сторонніх виробників — ще ні. Відкрийте Музична студія, щоб почути сам продукт. Цей пост — про інженерію.
Стек
- React + Vite frontend
- Web Audio API для аудіо-графу
- AudioWorkletProcessor для власних DSP (поза головним потоком)
- WebAssembly для високопродуктивної DSP (власні збірки WASM)
- MessagePort + ring buffers для комунікації UI ↔ worklet
- Yjs для CRDT-співпраці
- IndexedDB для зберігання проектів та зразків
- WebMIDI для введення даних з апаратних контролерів
- OfflineAudioContext для bounce/export
1. Аудіограф
Кожна трек у Музичній студії проходить через ланцюг: інструмент → ланцюг ефектів треку → aux send → смуга мікшера → головний бус → лімітер → призначення. Кожна стрілка є з'єднанням 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 портів Tier-1 Airwindows
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) вимагають істинної пікової нормалізації гучності за ITU-R BS.1770-5. Пікові значення і міжпікові піки — це різні речі; сигнал може досягати -1 dBFS по кожному зразку, але перевищувати 0 dBFS істинного пікового після перетворення D/A. Нелімітатор істинного пікового буде обрізати в DAC, навіть якщо ваш DAW показує, що все чисто.
Наш лімітер реалізує повний стандарт ITU: 4x зразування, детекція міжзразкових піків, попереднє виявлення, м'яке відпускання. Він постачається у формі AudioWorklet за шляхом public/audio-worklets/true-peak-limiter-processor.js.
4. Комунікація за межами основного потоку
Аудіографічний процесор працює на аудіо-потоці, інтерфейс користувача — на головному потоці. Зміни параметрів (повернути регулятор, змінити гучність треку) повинні передаватися від інтерфейсу до аудіо без перерв у звуці.
Web Audio використовує AudioParam для стандартних параметрів (GainNode gain, BiquadFilterNode frequency) безпосередньо. Для спеціальних робіт з використанням власних вузлів, ми застосовуємо MessagePort зі структурованим клонуванням для повідомлень інтерфейсу, а також SharedArrayBuffer-підтримку для буферів з циклічним записом для високочастотних даних автоматизації.
SharedArrayBuffer вимагає Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp заголовків. Ці заголовки встановлені в public/_headers для /soundbend + /pulse маршрутів. Tape Delay використовує коловий буфер на 96 кГц для уникнення затримки порту повідомлень.
5. 5 нативних інструментів
Музична студія надає 5 інструментів, всі спеціальні:
FM Synth — 6-операторна FM-синтез, моделювана на DX7 / Operator. Візуалізатор алгоритму + повна матриця модуляції. ~800 LOC JS для інтерфейсу, ~300 LOC для робот-коду голосового розподілювача.
Wavetable Synth — синтез, що використовує перехід між таблицями. Спектральне викривлення + унісон + стерео детюн. 30+ заводських вейвтейблів. Використовує ConvolverNode для перехідного змішування вейвтейблів на аудіо-частоті.
Аналоговий віднімний — полісінтез з 2 осциляторами та фільтрами Cytomic SVF. Cytomic — це топологія фільтра змінної частоти, яка звучить правильно; наївний SVF має проблеми з алиасингом при fc > SR/6. Cytomic порт вирішує це за допомогою математики нульового затримки.
DrumRack + Sampler — відтворення зразків за допомогою падів із налаштуванням під кожен пад: висота, фільтр, гучність. Зразки зберігаються в IndexedDB для швидкого виклику. 5 заводських наборів за жанрами (boombap, trap, house, dnb, afrobeat).
SeedSynth + Geist (у спадковому AudioStudioEngine — тепер об'єднано в Музичній студії з версії Wave 14) — генетичний генератор патчів (Synplant 2-клас) та барабанна машинка стилю Geist. Це пропрієтарний IP, який зробив консолідаційну доктрину важливою — ми повинні бути зберегти біт-ідентичну поведінку під час об'єднання 30 тис. рядків коду. Пройдено 100-сімейний нуль-тест після об'єднання.
6. Міксер + допоміжні виходи
Справжній Mixer — не просто регулятори гучності на кожній дорозі, але вставки ефектів на кожній дорозі, преред/пост-фейдерні допоміжні відправлення, головний бус — це те, що відрізняє DAW від sequencer. Mixer клас Музичної студії ~655 LOC (src/audio/mixer.js). Кожна доріжка є графом AudioNode з точками вставки, точками відправлення та вимірюванням.
Складна частина — це передфейдерні та післяфейдерні допоміжні виходи. Передфейдерні виходи надсилають сигнал на бус звукового ефекту незалежно від гучності треку (класичне використання: паралельний ревербератор, який залишається під час зміни гучності треку). Післяфейдерні змінюються разом з гучністю треку. Обидва типи встановлені.
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 UI + реальним часом аудіо + SharedArrayBuffer + WASM + кількома worklets вимагає тісного бюджету. Деякі числа:
- Аудіо-потік: ~2ms / буфер 128 зразків при 48kHz (під-фрейм)
- Головний потік: 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 у безголовому Chromium, нуль-тестують отриманий WAV проти збереженого золотого, і завершують будівлю, якщо залишковий пік перевищує -60 dBFS.
Wave 14F додав 100 базових ліній SeedSynth та 32 золотих патернів для барабанів Geist. Коли ми об’єднали Музична студія у AudioStudio (Wave 14B-G), нуль-тест виявив все, що могло б тихо порушити пропрієтарні інструменти. 96 з 100 базових ліній SeedSynth пройшли перевірку збігу бітів. 4 помилки були попередніми помилками Chromium Web Audio (дрейф резонансу високої якості між екземплярами 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