Skip to main content

Data Layer (react_ui/src/data/)

State management, engine↔JS bridge, and real-time metering.

Files

FilePurpose
bridge.tsEngine↔JS communication layer. StateStorage for Zustand persistence (via invoke() from @/sync/transports/promiseHandler), addStateListener for engine events.
meters.tsReal-time metering: useMeterStore (Zustand), raw rtBuffer, ballistic smoothing engine, subscribeRtBuffer API.
store.tsPrimary Zustand stores (useTransportStore, useMixerStore, etc.) with engine persistence.
plugins.tsPlugin scanning and management state.
sliderDrag.tsShared slider drag utilities.
inlineGenFlag.tsFeature flag for inline AI generation (Option+click-drag).
slices/collab.tsCollaboration state slice: connected users, room info, cursor positions.
slices/inlineGenerate.tsInline generation state slice: drag region, progress, results.
slices/customEditors.tsCustom editor panel state: active stock plugin UI, editor visibility.
slices/scriptFxEditors.tsScriptFX editor state: open editors, script content, compilation status.

Real-Time Metering Pipeline

Engine (30Hz) → bridge.ts  →  rtBuffer (mutable)  →  rAF ballistic smoothing  →  displayBuffer  →  DOM
                  (no log)       (raw data)              (60Hz, instant attack,      (smoothed)       (GPU transforms)
                                                          ~300ms release decay)

Three-Layer Architecture

  1. rtBuffer — Raw mutable object, written by the engine event handler. No subscriptions, no notifications. Just a data landing zone.
  2. displayBuffer — Smoothed values computed by the requestAnimationFrame loop. Applies ballistic smoothing so meters look fluid even with 30Hz data input. Subscribers receive this.
  3. useMeterStore (Zustand) — Throttled to ~10Hz for the few React components that still use reactive selectors. Most components use subscribeRtBuffer instead.

Ballistic Smoothing Constants

ElementRelease FactorEffective DecayRationale
Level meters0.88~300msMatches professional PPM meter ballistics
Spectrum bars0.82~200msSlightly faster for responsive spectrum display
Stereo/phase0.85~250msBidirectional smoothing (not attack/release)
CPU0.95~600msSlow-moving data, avoid visual noise
Attack is always instant — when the new value exceeds the current smoothed value, it snaps immediately.

API

// Subscribe to smoothed display-rate data (~60Hz via rAF)
const unsub = subscribeRtBuffer((buf) => {
  // Direct DOM updates here — no React re-renders
  el.style.transform = `scaleY(${buf.master.left / 100})`;
});

// Get the raw (unsmoothed) latest data
const raw = getRtBuffer();

Rendering Best Practices

All meter components use direct DOM manipulation via subscribeRtBuffer (no React re-renders):
✅ Do❌ Don’t
style.transform = 'scaleY(...)'style.height = '...'
style.transform = 'scaleX(...)'style.width = '...'
style.transform = 'translateX(...)'style.left = '...'
will-change: transformCSS transition-* on high-freq elements
subscribeRtBuffer()useMeterStore(selector) for meters
Why transforms? height/width/left trigger browser layout recalculation (~0.5ms per property). transform is GPU-composited — the browser sends a matrix to the GPU with no layout cost.

Event Flow

Engine PlaybackInfo (30Hz timer)
  → ctx.send("meters:rt_frame", json)
  → bridge.ts addStateListener("meters:rt_frame", ...)
      (pushLog skipped for rt_frame — would saturate 200-entry buffer)
  → JSON.parse → write to rtBuffer
  → markNewData() — ensures rAF loop is running
  → rAF loop applies ballistic smoothing → displayBuffer
  → subscribeRtBuffer callbacks → direct DOM updates