Morph Blog
Inżynieria

Jak zbudowaliśmy DAW w przeglądarce.

Studio muzyczne działa na WebAssembly, Web Audio API i 25 latach badań DSP — wszystko po stronie klienta. Pełna historia inżynierska: grafika dźwiękowa, worklety, porty Airwindows, prawdziwy limiter piku, host wtyczek.

OD ZESPOŁU MORPH12 MINUT PRZECZYTANIAAKTUALIZOWANO KWIECIEŃ 2026

To jest techniczny post o tym, jak Morph Studio muzyczne — pełny cyfrowy system dźwiękowy z 5 wbudowanymi instrumentami, mikserem z łańcuchami efektów i pomocniczymi wysyłkami, 30+ efektami (w tym 5 portów Airwindows Tier-1 i prawdziwym limiterem true-peak), szafą MIDI z efektami, hostem wtyczek WAM v2 oraz warstwą współpracy Yjs CRDT — działa całkowicie w karcie przeglądarki.

Prawdziwe pytanie brzmi: czy to naprawdę konkurowało z desktopowymi DAW? Szczera odpowiedź: dla większości prac producerów, tak. Dla głębokiego ekosystemu urządzeń trzecich, nie jeszcze. Otwórz Studio muzyczne, aby usłyszeć sam produkt. Ten post to inżynieria.

Stos

1. Grafika dźwiękowa

Każda ścieżka w Studiu muzycznym przechodzi przez łańcuch: instrument → łańcuch efektów ścieżki → wysyłka do pomocniczego → pasek miksera → główna magistrala → limiter → cel. Każdy strzałka to połączenie Web Audio AudioNode.

Web Audio daje nam AudioContext, GainNode, BiquadFilterNode, ConvolverNode, AnalyserNode i inne. Są one zoptymalizowane przez silniki przeglądarek (V8/SpiderMonkey/JSC głęboko zaangażowane). Dla standardowych efektów — EQ, automatyzacja głośności, podstawowe filtry — używamy natywnych węzłów. Są one szybkie i dobrze przetestowane.

Dla niestandardowych efektów — porty Airwindows, nasza saturacja, nasz prawdziwy limiter szczytowy — wchodzimy do AudioWorkletProcessor, który działa na wątku audio, gwarantując real-time priorytet, i jest poprawnym miejscem architekturalnym dla niestandardowych 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);

Worklety są dostarczane w public/audio-worklets/*.js i ładują się za pomocą audioContext.audioWorklet.addModule(path). Po załadowaniu dowolna część grafu może zainicjować AudioWorkletNode dla tego procesora. Przetwarzanie odbywa się poza głównym wątkiem — animacje interfejsu nie zatrzymują dźwięku.

2. 5 portów Airwindows Tier-1

Chris Johnson's Airwindows to 2 500+ dźwiękowych wtyczek, licencjonowanych na zasadach MIT, legendarnie smakowitych DSP. Przenosimy 5 z nich do przeglądarki w postaci AudioWorklets dla Wave 12 w Studiu muzycznym:

Każdy port to ~30-80 linii kodu matematycznego w AudioWorkletProcessor. Porty nie są zgodne bit po bit z Airwindows (różne obsługę FMA/denormal między silnikami numerycznymi C++ i JS), ale są dźwiękowo zgodne. Testy null utrzymują wyjścia ≤ -80 dBFS względem oryginałów, więc zmiany DSP są wykrywane.

3. Prawdziwy limiter szczytowy

Nowoczesne platformy streamingowe (Spotify, Apple Music, YouTube) wymagają prawdziwej głośności szczytowej zgodnie z ITU-R BS.1770-5. Szczytowe próbki i szczytowe próbki międzypróbki to różne rzeczy; sygnał może osiągać -1 dBFS na poziomie próbek, ale przekroczyć 0 dBFS prawdziwej głośności szczytowej po konwersji D/A. Nieprawdziwy limiter głośności przycina w DAC, nawet jeśli Twój DAW pokazuje, że jest czysty.

Nasza limiter implementuje pełny specyfikację ITU: 4x próbkowanie nadprzeciętnego, wykrywanie inter-peak, lookahead, miękki mechanizm zwalniania. Dostarczany jest w formie AudioWorklet pod public/audio-worklets/true-peak-limiter-processor.js.

4. Komunikacja poza głównym wątkiem

Wykres dźwiękowy działa na wątku dźwiękowym. Interfejs użytkownika działa na głównym wątku. Zmiany parametrów (obracanie pokrętła, fade tracku) muszą być przekazywane z UI → audio bez zakłóceń dźwięku.

Web Audio's AudioParam obsługuje to natywnie dla standardowych parametrów (GainNode gain, BiquadFilterNode frequency). Dla własnych prac z workletem, używamy MessagePort z strukturalnym klonowaniem dla komunikatów interfejsu, a SharedArrayBuffer-backed ring buffers dla danych automatyzacji o wysokiej częstotliwości.

SharedArrayBuffer wymaga Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp nagłówków. Są one ustawione w public/_headers dla tras /soundbend + /pulse. Tape Delay używa bufora kołowego na 96kHz, aby uniknąć opóźnień wiadomości.

5. 5 natywnych instrumentów

Studio muzyczne dostarcza 5 instrumentów, wszystkie niestandardowe:

FM Synth — syntezator FM z 6 operatorami modelowany na DX7 / Operator. Wizualizator algorytmu + pełny macierz modulacji. ~800 LOC JS dla interfejsu, ~300 LOC worklet dla alokatora głosu.

Wavetable Synth — syntezator między tabelami. Wymiana spektralna + unison + stereodetune. 30+ fabrycznych tabel. Używa ConvolverNode do przeplotu tabel na poziomie dźwięku.

Analog Subtractive — syntezator wielofrekencyjny z 2 oscylatorami i filtrami Cytomic SVF. Cytomic to topologia filtru zmiennego, która brzmi dobrze; prosta topologia SVF ma problemy z aliasingiem powyżej fc > SR/6. Port Cytomic rozwiązuje to za pomocą matematyki zero-delay feedback.

DrumRack + Sampler — odtwarzanie próbek oparte na padach z osobistymi ustawieniami wysokości dźwięku, filtru i głośności na każdy pad. Próbki przechowywane w IndexedDB dla szybkiego odzyskania. 5 fabrycznych zestawów dla różnych gatunków (boombap, trap, house, dnb, afrobeat).

SeedSynth + Geist (w dziedziczonej AudioStudioEngine — teraz scalone z Studia muzycznego od Wave 14) — generator genetyczny (Synplant 2-class) i perkusyjny generator stylu Geist. To są własne IP, które sprawiły, że konsolidacja stworzyła dużą sensację — musieliśmy zachować identyczność bitową przez scalenie 30k linii kodu. Przetestowano 100-seed null-test po scaleniu.

6. Mixer + wysyłki pomocnicze

Prawdziwy Mixer — nie tylko regulatory poziomu na poszczególnych śledach, ale także wtyczki efektów na poszczególnych śledach, pre/post-fader aux sends, bus główny — to co oddziela DAW od sequencer'a. Mixer w Studiu muzycznym ma ~655 linii kodu (src/audio/mixer.js). Każdy śled jest grafem AudioNode z miejscami na wtyczki, punktami do podłączenia wysyłek i metrami.

Trudnym elementem jest pre-fader vs post-fader aux sends. Pre-fader wysyła do busu rewerberacji niezależnie od głośności ścieżki (klasyczne zastosowanie: równoległa rewerberacja, która przetrwa spadek głośności ścieżki). Post-fader skaluje się z głośnością ścieżki. Oba są domyślnie włączone.

7. MIDI + WAM plugin host

WebMIDI jest dojrzałym standardem — wejście kontrolera z dowolnej klawiatury USB-MIDI działa natychmiast. Nasz rack MIDI (Arpeggiator, Skala, Akord) znajduje się między kontrolerem a instrumentem, przekształcając strumienie nut.

Dla wtyczek hostujemy Web Audio Modules v2 (WAM) — wtyczki natively dla przeglądarki. Mniejsza ekosystem niż VST (25-letni przewaga) ale rośnie. Załadowanie WAM odbywa się przez getWamHost().load(wamUri), które zwraca AudioNode, który możesz wstawić do dowolnej łańcuchu.

WAMs wymagają per-route CSP, ponieważ ładują dowolny, dostarczony przez użytkownika JS. Nasze _headers ograniczają CSP dla /soundbend za pomocą listy dozwolonych script-src dla znanych rejestrów WAM.

8. OfflineAudioContext dla bounce

Eksport działa poprzez OfflineAudioContext — renderuje szybciej niż w czasie rzeczywistym (lub wolniej, w zależności od złożoności łańcucha) do statycznego AudioBuffer, a następnie koduje do WAV za pomocą naszego w przeglądarce kodera.

Uwaga: nie wszystkie węzły działają w OfflineAudioContext. MediaStreamAudioSourceNode nie działa (nie można rejestrować mikrofonu na żywo w trybie offline, oczywiście). Niektóre worklety wymagają starannego resetu stanu między przejściami online → offline. Napotkaliśmy to wielokrotnie i teraz Mixer.toJSON() + Mixer.fromJSON(offlineCtx) jest kanonicznym wzorcem: serializuj stan mixer, zbuduj równoległy mixer w kontekście offline, renderuj.

9. Współpraca przez Yjs

Yjs to biblioteka CRDT obsługująca współbieżną edycję wielu użytkowników z automatycznym rozwiązywaniem konfliktów. Nasza klasa SoundbendSession otacza Yjs w strukturze dokumentu na poziomie ścieżki (Y.Map dla metadanych ścieżki, Y.Array dla kroków wzoru, Y.Text dla tekstu/notek).

Dwa do czterech producentów może jednocześnie edytować ten sam projekt z synchronizacją śledzenia próbek. Jest to trudniejsze niż współpraca w dokumentach tekstowych, ponieważ dane audio są o kilka rzędów wielkości większe — dzielimy się metadane i wzory przez Yjs, a nie surowym dźwiękiem. Surowe próbki pozostają lokalnie (IndexedDB na użytkownika).

10. Budżet wydajności

Uruchomienie pełnego DAW w przeglądarce z interfejsem 60fps + dźwiękiem w czasie rzeczywistym + SharedArrayBuffer + WASM + wiele workletów wymaga precyzyjnego zarządzania zasobami. Niektóre liczby:

11. Testowy harmonogram null

Regressje DSP są niezauważalne — dźwięk brzmi inaczej w subtelny sposób, który nie można złapać w przeglądzie kodu. Studio muzyczne dostarcza zintegrowany test null w tests/audio-null/ oparty na Puppeteer.

Dla każdego efektu × preset × sygnału testowego, narzędzie renderuje za pomocą OfflineAudioContext w bezgłowym Chromium, przeprowadza null-test wynikowego WAV w porównaniu z zatwierdzonym złotym, i zatrzymuje budowę, jeśli residualny szczyt przekracza -60 dBFS.

Wave 14F dodał 100 podstawowych sekwencji SeedSynth i 32 złote wzory perkusyjne Geist. Gdy połączyliśmy Studio muzyczne z AudioStudio (Wave 14B-G), test null wykrył wszystko, co mogłoby niezauważalnie uszkodzić proprietaryjne instrumenty. 96 z 100 sekwencji SeedSynth przeszło bit-identyczne testy. 4 niepowodzenia były już istniejącymi błędami w Chromium Web Audio (zachowanie rezonansu wysokiej jakości w filtrach między OfflineAudioContext), a nie wynikami połączenia, i nie dało się ich naprawić z JavaScript.

DSP-regresje są cisze. Przegląd kodu nie może ich słyszeć. Testowy zestaw null-testów może — dlatego jest blokujący w CI.

12. Dlaczego przeglądarka + klient-side to była dobra decyzja

Natywne DAW mają 30-letni przewagę. Wygrywanie w funkcjonalności na poziomie konkurencji to długa droga. Ale cena — to było możliwe od razu dzięki innym architektonicznym decyzjom.

WebAssembly przekroczył próg wydajności w latach 2022-2024. Web Audio AudioWorklets zostały szeroko wdrożone w tym samym czasie. SharedArrayBuffer + COOP/COEP został stabilizowany. Chromium, Firefox i Safari wszystkie obsługują potrzebne interfejsy API z odpowiednią wydajnością.

Wynik ceny: jeśli przetwarzanie dźwięku odbywa się na urządzeniu użytkownika, nie płacimy za to. Nasz koszt marginalny dla subskrybenta Pro jest bliski zera.

Przeczytaj manifesto na temat ekonomii. Dla DSP to większość historii.

Wypróbuj

Studio muzyczne jest dostępny pod morph.cool/music-studio. Wersja bezpłatna działa bez rejestracji. Klawiatura WebMIDI jest mile widziana, ale nie jest wymagana.

Otwórz Studio muzyczne

— zespół Morph
22 kwietnia 2026