Architecture Overview
Application Layout
MIXI uses a CSS Grid with 3 columns and 3 rows:
gridTemplateColumns: '1fr auto 1fr'
gridTemplateRows: 'auto auto 1fr'| Row | Height | Content |
|---|---|---|
| 1 | 48px (h-12) | Topbar — 3 HUD panels via CSS subgrid |
| 2 | 18px | Status bar — feedback ticker + symmetric VU |
| 3 | Remaining | Main — Deck A, Mixer, Deck B via subgrid |
The topbar and main content both use gridTemplateColumns: subgrid to share column tracks with the parent, ensuring pixel-perfect alignment between HUD panels and their corresponding deck/mixer columns.
Audio Engine
Singleton Pattern
MixiEngine is a singleton with private constructor:
MixiEngine.getInstance() → creates AudioContext(44100 Hz) on first callThe AudioContext runs at a fixed 44,100 Hz sample rate.
Top-Level Signal Flow
Deck A Source → DeckChannel A → XfaderGain A ──┐
├→ MasterBus → Output
Deck B Source → DeckChannel B → XfaderGain B ──┘
DeckChannel A → CueGain A ──┐
├→ HeadphoneBus → Headphone Output
DeckChannel B → CueGain B ──┘Optional Wasm DSP Path
When useWasmDsp is enabled in Settings, audio routes through a Rust AudioWorklet (mixi-dsp-processor) that implements the full signal chain in WebAssembly, bypassing the WebAudio EQ/FX/MasterBus nodes.
- Parameter bus: 512-byte SharedArrayBuffer
- Metering bus: 28-byte SharedArrayBuffer for VU output
The worklet loads a dedicated lean wasm (the mixi-dsp crate → public/mixi_dsp.wasm), separate from the wasm-bindgen analysis module (mixi-core). It is compiled with no wasm-bindgen and a stable extern "C" ABI (dsp_engine_new, dsp_process, dsp_*_ptr, …), so it has zero imports and the AudioWorklet can instantiate it with an empty import object. The same DSP source is shared by both crates via #[path] (single source of truth); DspEngine itself is wasm-bindgen-free. scripts/build-wasm.mjs builds the lean wasm and fails the build if it ever gains an import. The bridge fetches the wasm bytes and transfers the ArrayBuffer to the worklet (not a WebAssembly.Module, which does not clone reliably into an AudioWorklet realm).
Deck Channel
Each deck has a DeckChannel with this signal chain:
Source → Trim (GainNode, default 1.0)
→ EQ Model (selectable, see below)
→ Color FX (BiquadFilterNode)
→ DeckFx (effects chain)
→ Fader (GainNode)
→ Analyser (fftSize=256, smoothing=0.8)
→ XfaderGain (GainNode) → Master Bus
DeckFx output also taps to:
→ CueGain (GainNode, default 0) → Headphone BusCUE is a pre-fader listen — it taps the signal after effects but before the volume fader.
EQ Models
Three selectable EQ models, hot-swappable at runtime:
LR4 Isolator (Default)
Linkwitz-Riley 24 dB/octave parallel isolator.
| Band | Topology | Crossover |
|---|---|---|
| Low | 2× cascaded LP (Q=0.707 each) | 250 Hz |
| Mid | 2× HP @ 250 Hz → 2× LP @ 4000 Hz | 250 Hz / 4000 Hz |
| High | 2× cascaded HP (Q=0.707 each) | 4000 Hz |
Kill switch: when gain ≤ range minimum, band gain is set to 0 (hard kill). Other bands are 100% unaffected — parallel topology guarantees zero interaction between bands.
DJ Peak (Pioneer DJM-style)
Serial shelving/peaking EQ — no kill capability.
| Band | Type | Frequency | Q |
|---|---|---|---|
| Low | Low shelf | 80 Hz | — |
| Mid | Peaking | 1000 Hz | 0.7 |
| High | High shelf | 12000 Hz | — |
Xone Kill (Allen & Heath-style)
48 dB/octave full-kill isolator with slight resonance at crossover points.
| Band | Topology | Crossover | Order |
|---|---|---|---|
| Low | 4× cascaded LP (Q=1.0) | 200 Hz | 8th order |
| Mid | 4× HP @ 200 Hz → 4× LP @ 2500 Hz | 200 / 2500 Hz | 8th order |
| High | 4× cascaded HP (Q=1.0) | 2500 Hz | 8th order |
Color FX
Bipolar knob from -1 to +1:
| Range | Effect | Frequency |
|---|---|---|
| -1 → 0 | Lowpass sweep | 20 Hz → 20 kHz (exponential) |
| 0 | Bypass | 20 kHz (fully open) |
| 0 → +1 | Highpass sweep | 20 Hz → 20 kHz (exponential) |
Default Q = 0.707 (Butterworth). Q increases with depth for more aggressive filtering at extremes.
Master Bus
Full signal chain from input to output:
Input (GainNode)
→ Master EQ (3-band: 80 Hz / 1 kHz / 12 kHz, ±12 dB)
→ Master Filter (bipolar LP/HP sweep, 3-path crossfade)
→ Band-Split Distortion (300 Hz crossover)
→ Punch Compressor (parallel, 8:1 ratio)
→ DC Blocker (10 Hz highpass)
→ Headroom Pad (-0.3 dB)
→ Limiter (brickwall, -0.5 dB threshold)
→ Analyser → Channel Splitter → L/R AnalysersMaster EQ
| Band | Type | Frequency | Q | Range |
|---|---|---|---|---|
| Low | Low shelf | 80 Hz | — | ±12 dB |
| Mid | Peaking | 1000 Hz | 0.7 | ±12 dB |
| High | High shelf | 12000 Hz | — | ±12 dB |
Master Filter
Bipolar knob -1 to +1. Three parallel signal paths (bypass, LP, HP) with gain crossfading.
Frequency mapping: 20 × 1000^(1 - |knob|) — exponential sweep from 20 Hz to 20 kHz.
Band-Split Distortion
| Parameter | Value |
|---|---|
| Crossover | 300 Hz |
| Sub behavior | Summed to mono below 300 Hz (phase-safe for PA) |
| Waveshaper | tanh(k·x) / tanh(k), k = amount² × 80 + 1 |
| Oversampling | 4× when active, none when off |
| Curve resolution | 2048 samples |
Punch Compressor
Parallel compression — dry signal is always present, compressed signal is mixed in.
| Parameter | Value |
|---|---|
| Threshold | -14 dB |
| Ratio | 3:1 |
| Attack | 3 ms |
| Release | 120 ms |
| Knee | 10 dB |
| Wet mix | amount × 0.5, compensation = 1 / (1 + wet) |
DC Blocker
Highpass BiquadFilter at 10 Hz, Q = 0.707. Removes DC offset from distortion and compression.
Headroom Pad
Fixed -0.3 dB gain reduction before the limiter.
Limiter
| Parameter | Value |
|---|---|
| Threshold | -0.5 dB |
| Ratio | 20:1 |
| Attack | 1 ms |
| Release | 100 ms |
| Knee | 0 dB (hard) |
Visual feedback via LimiterDot:
- Idle (< 0.3 dB reduction): dark
- Gentle (0.3–3 dB): amber glow
- Emergency (> 3 dB): red flash + clip border
Headphone Bus
Stereo Mode (Default)
Both master and headphone output to all speakers.
Split Mode
Master output → right ear, CUE → left ear (via ChannelMergerNode).
CUE Mix
Equal-power cosine crossfade: cueMix = cos(mix × π/2), masterTap = sin(mix × π/2). At 0 = 100% CUE, at 0.5 = equal blend, at 1 = 100% MASTER.
Crossfader
| Curve | Formula | Character |
|---|---|---|
smooth | gainA = cos(pos × π/2), gainB = sin(pos × π/2) | Equal-power, no center dip |
sharp | Cubic curve with 2% dead zones: (1-x)³ / x³ over 0.02–0.98 range | Fast cuts, both full at center |
Position 0 = full Deck A, position 1 = full Deck B.
State Management
MIXI uses Zustand stores:
| Store | Key State |
|---|---|
mixiStore | Deck states (2), master, crossfader, headphones, AI mode, deck modes |
settingsStore | EQ model, EQ range, Wasm DSP, demo tracks, skin, update check |
browserStore | Track browser visibility, file list, sort order |
sessionStore | Recording state, session persistence |
Deck State Shape
Each deck tracks: transport (isPlaying, playbackRate, isSynced, syncMode), audio (gain, volume, eq, colorFx), analysis (bpm, originalBpm, bpmConfidence, musicalKey, dropBeats), waveform data, performance (hotCues[8], activeLoop, quantize, keyLock, slipModeActive, cueActive), and track info (trackName, duration, loadingStage).
Pluggable Deck Modes
| Mode | Key | Label | Color | Component |
|---|---|---|---|---|
| Track | track | — | Deck color | DeckSection |
| Groovebox | groovebox | GROOVEBOX | #a855f7 | GrooveboxDeck (lazy) |
| TurboKick | turbokick | TURBOKICK | #ef4444 | TurboKickDeck (lazy) |
| TurboBass | js303 | TURBOBASS | #00ff88 | JS303Deck (lazy) |
Thread Architecture & Data Flow
MIXI utilizes a multi-threaded architecture to decouple heavy visual rendering and real-time DSP operations from React's main UI thread, ensuring glitch-free audio performance and ultra-smooth interface visuals.
mermaid
graph TD
%% Define Threads/Contexts
subgraph MainThread ["React Main Thread (UI & State)"]
ReactUI["React Components <br> (DeckSection, Controls)"]
Store["Zustand Store <br> (mixiStore, settingsStore)"]
Engine["MixiEngine (Web Audio API)"]
Ticker["Main rAF Loop <br> (Time Poller)"]
CanvasElement["HTMLCanvasElement <br> (Input Target)"]
end
subgraph WorkerThread ["Web Worker Thread (Waveform Renderer)"]
Worker["Waveform Web Worker <br> (waveform.worker.ts)"]
Offscreen["OffscreenCanvas <br> (Drawing Context)"]
Interpolator["Time Interpolator <br> (performance.now)"]
end
subgraph AudioThread ["AudioWorklet Thread (Real-Time DSP)"]
WorkletNode["AudioWorkletProcessor <br> (Rust/Wasm DSP Engine)"]
RingBuffer["SharedArrayBuffer <br> (Lock-Free Ring Buffer)"]
ParamBus["SharedArrayBuffer <br> (Parameter / VU Bus)"]
end
%% Communication Flows
CanvasElement -- "1. transferControlToOffscreen()" --> Offscreen
ReactUI -- "2. Drag/Scroll Events" --> Store
Store -- "3. State Update (Structured Clone)" --> Worker
Ticker -- "4. Ticks & Zoom (Structured Clone)" --> Worker
Worker --> Interpolator
Interpolator --> Offscreen
Engine -- "5. Audio Node Controls" --> WorkletNode
ReactUI -- "6. Lock-Free Params" --> ParamBus
ParamBus -- "7. Real-Time VU Meters" --> ReactUI
Engine -- "8. Stream PCM Data" --> RingBuffer
RingBuffer -- "9. DSP Block Processing (128 samples)" --> WorkletNodeExecution Contexts & Thread Boundaries
React Main Thread:
- Responsibility: Handles user interactions (pointer drags, scroll zooms, context menus), React layout rendering, application state via Zustand stores, and general lifecycle coordination of the
MixiEngine. - Waveform Interface: Relays mouse/drag events to coordinates conversion and manages a lightweight
requestAnimationFrameticking loop that forwards the deck playhead states to the worker.
- Responsibility: Handles user interactions (pointer drags, scroll zooms, context menus), React layout rendering, application state via Zustand stores, and general lifecycle coordination of the
Waveform Web Worker Thread (
waveform.worker.ts):- Responsibility: Conducts all canvas operations for waveform rendering (additive RGB bands, beatgrid lines, loop overlays, cue markers, drops, playheads, and downbeat flashes).
- Time Interpolation: Runs its own requestAnimationFrame loop. It calculates elapsed time via
performance.now()since the last received main thread playhead tick, ensuring continuous scroll updates at up to 60fps even if React is performing heavy render operations on the main thread. - Communication/Serialization:
- Canvas rendering is offloaded by calling
canvas.transferControlToOffscreen()and passing theOffscreenCanvascontainer during initialization. - Parameter states and tick coordinates are passed via
postMessage(), serialized using the browser's built-in Structured Clone Algorithm (zero-copy for canvas and optimized object cloning).
- Canvas rendering is offloaded by calling
AudioWorklet Thread:
- Responsibility: Executes real-time digital signal processing (DSP) loops for mixers, filters, and deck channels at high-priority thread levels in the browser's audio subsystem.
- Communication/Serialization:
- Uses SharedArrayBuffer (SAB) containers for lock-free parameter transmission and real-time VU metering, avoiding main-thread message-passing latency and garbage collection interruptions.
- Utilizes lock-free ring buffers (built on SAB) to stream decrypted audio PCM blocks from the main thread database to the Wasm DSP engine.
Performance Patterns
Direct DOM Mutation
VU meters, phase meter, and limiter dot update at 30fps via requestAnimationFrame with frame-skipping, writing directly to DOM refs — zero React re-renders.
AudioWorklet Usage
| Worklet | File | Purpose |
|---|---|---|
mixi-dsp-processor | mixi-dsp-worklet.js | Full Rust/Wasm signal chain |
diode-ladder-processor | diode-ladder-processor.js | TurboBass 4-pole diode ladder filter |
recording-tap | recording-tap.js | Disk recording bridge |
React Bridge
useMixiSync and useMixiBridge hooks synchronize the Zustand store with the audio engine bidirectionally.
Watermarking
Three-tier system (all zero-impact on audio quality):
- UI Fingerprint — Invisible canvas overlay with per-session build hash at sub-1% opacity
- Code Fingerprint — Zero-Width Character steganography in compiled CSS/skin files
- Audio Container — Encrypted build metadata appended to exported recording containers (no audio samples modified)