// mindX studio — the settings rack as dreamknob hardware. // // IT OWNS NO STATE. Every value comes from window.mindxStudio.knobs() and every turn goes back // through window.mindxStudio.setKnob(); the rack repaints on the window 'mindx:studio' event, which // the engine fires (once per frame at most) whenever a knob moves — by hand here, by a fallback // slider, or by a live mapping from .pixel .gesture .mic .cam. So a knob that is following your // microphone turns on its own, and the readout under it is what the audio graph was handed. // Delete this script and studio.js shows plain sliders that drive the same engine. // // dreamknob 1.1.4 — MIT, (c) Dreamodus Software Inc. Bundled with React 18 by rack/build.mjs. import { useEffect, useRef, useState } from 'react' import { createRoot } from 'react-dom/client' import { DreamknobProvider, Rack, MetalKnob, PanKnob, Fader, IndicatorLamp, linearTaper, logTaper, powTaper } from 'dreamknob' const MONO = "'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace" const TAPERS = { lin: linearTaper, log: logTaper, pow2: powTaper(2) } const SECTIONS = ['OUTPUT', 'EQ', 'FX', 'SCOPE'] const api = () => window.mindxStudio const colors = () => (api() && api().colors ? api().colors() : {}) function themeFrom(c, light) { return { accent: c.gold || '#d6b25e', track: c.line2 || 'rgba(232,230,223,.08)', face: c.canvas || '#0a0d0c', text: c.text || '#e8e6df', label: c.text3 || '#6d7771', ticks: c.text3 || '#6d7771', focusRing: c.gold || '#d6b25e', panel: light ? 'rgba(255,255,255,.85)' : 'rgba(14,18,17,.85)', ledGreen: c.green || '#52d98a', ledAmber: c.gold || '#d6b25e', fontMono: MONO, } } function useWidth(ref) { const [w, setW] = useState(800) useEffect(() => { const el = ref.current if (!el) return const measure = () => setW(el.clientWidth || 800) measure() if (typeof ResizeObserver !== 'undefined') { const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect() } window.addEventListener('resize', measure) return () => window.removeEventListener('resize', measure) }, [ref]) return w } function Cell({ k, size }) { const set = (v) => api().setKnob(k.id, v) const common = { value: k.value, min: k.min, max: k.max, step: k.step || undefined, taper: TAPERS[k.taper] || linearTaper, defaultValue: k.def, onChange: set, label: k.label, 'aria-label': k.label, detents: k.detents, origin: k.origin, wheelRequiresFocus: true, } const c = colors() let control if (k.kind === 'fader') { control = k.text} color={c.gold} tickCount={9} editable /> } else if (k.kind === 'pan') { control = k.text} color={c.gold} /> } else { control = k.text} sublabel={k.sub || undefined} valueBubble bubbleFormat={() => k.text} editable /> } return (
{control}
) } function StudioRack() { const [, tick] = useState(0) const [light, setLight] = useState(() => document.documentElement.style.colorScheme === 'light' || getComputedStyle(document.documentElement).colorScheme === 'light') const ref = useRef(document.getElementById('rack')) const width = useWidth(ref) useEffect(() => { const on = () => tick((n) => n + 1) const th = () => { setLight(getComputedStyle(document.documentElement).colorScheme === 'light'); tick((n) => n + 1) } window.addEventListener('mindx:studio', on) window.addEventListener('mindx:studio-theme', th) return () => { window.removeEventListener('mindx:studio', on); window.removeEventListener('mindx:studio-theme', th) } }, []) const knobs = api().knobs() const size = width < 420 ? 58 : width < 760 ? 64 : 72 const anyLive = knobs.some((k) => k.live) return (
{SECTIONS.map((sec) => (
{knobs.filter((k) => k.sec === sec).map((k) => )}
))}
) } function mount() { const host = document.getElementById('rack') if (!host || !window.mindxStudio || host.__mounted) return !!(host && host.__mounted) host.__mounted = true window.__mindxStudioRack = true createRoot(host).render() return true } if (!mount()) { const t0 = Date.now() const iv = setInterval(() => { if (mount() || Date.now() - t0 > 10000) clearInterval(iv) }, 120) }