diff --git a/manifold/src/console/CompositeStage.tsx b/manifold/src/console/CompositeStage.tsx index 5dd192e..bf3917a 100644 --- a/manifold/src/console/CompositeStage.tsx +++ b/manifold/src/console/CompositeStage.tsx @@ -22,6 +22,7 @@ import { VirtualJoystick, XYPad } from '../primitives'; import { Manifold } from './Manifold'; import { OutputStage } from './OutputStage'; import { MiniMeters } from './shared-ui'; +import { GROUP_COLOR } from './model'; import type { MFMode, MFParam } from './model'; import type { FeedbackMarker, Pin } from './types'; @@ -29,15 +30,6 @@ const SNAPS = [0.14, 0.33, 0.5, 0.66, 0.86]; const MAGNET = 0.026; const SHUT = 0.1; -const GC: Record = { - formant: '--accent', - pitch: '--accent-2', - amp: '--good', - filter: '--warn', - fx: '--info', - mod: '--accent-3', -}; - const CORNERS: Record = { tl: { top: 62, left: 14 }, tr: { top: 62, right: 14 }, @@ -261,7 +253,7 @@ export function CompositeStage({ > {params.map((p, i) => { const eff = values[i] ?? 0; - const gc = `var(${GC[p.group] || '--accent'})`; + const gc = `var(${GROUP_COLOR[p.group] || '--accent'})`; const dim = p.status === 'off'; const set = (cx: number, el: HTMLDivElement) => { const r = el.getBoundingClientRect(); diff --git a/manifold/src/console/CurvePad.tsx b/manifold/src/console/CurvePad.tsx index ca87f92..e6518f8 100644 --- a/manifold/src/console/CurvePad.tsx +++ b/manifold/src/console/CurvePad.tsx @@ -1,9 +1,14 @@ /** * CurvePad — a square response-curve plot. Vertical drag reshapes the curve. - * `curve` is 0..1 where ~0.43 reads as linear; mirrors the engine's applyCurve. + * `curve` is 0..1 where 0.5 is exactly linear. + * + * The preview draws the SAME `applyCurve` the output path uses + * (`backends/mapping.ts`) rather than re-deriving an exponent, so the knob, the + * on-screen bars and the value actually sent to a backend can never disagree. * Ported from `CurvePad.jsx`. */ import { useEffect, useRef } from 'react'; +import { applyCurve } from '../backends/mapping'; import type { PointerEvent as ReactPointerEvent } from 'react'; export interface CurvePadProps { @@ -48,14 +53,13 @@ export function CurvePad({ curve = 0.5, onChange, size = 116 }: CurvePadProps) { ctx.setLineDash([]); const css = getComputedStyle(cv); const accent = css.getPropertyValue('--accent').trim() || '#ff6a00'; - const e = 0.25 + curve * 1.75; ctx.strokeStyle = accent; ctx.lineWidth = 2; ctx.beginPath(); const pad = 3; for (let p = 0; p <= 80; p++) { const xv = p / 80; - const yv = Math.pow(xv, e); + const yv = applyCurve(xv, curve); const px = pad + xv * (size - 2 * pad); const py = size - pad - yv * (size - 2 * pad); if (p === 0) ctx.moveTo(px, py); @@ -95,7 +99,7 @@ export function CurvePad({ curve = 0.5, onChange, size = 116 }: CurvePadProps) { - {(0.25 + curve * 1.75).toFixed(2)} + {Math.pow(2, 4 * (curve - 0.5)).toFixed(2)} = { - formant: '--accent', - pitch: '--accent-2', - amp: '--good', - filter: '--warn', - fx: '--info', - mod: '--accent-3', -}; const OUT_NEXT: Record = { off: 'fixed', fixed: 'live', live: 'off' }; export interface OutputStageProps { @@ -94,7 +87,7 @@ export function OutputStage({ params, values, onChange, compact = false }: Outpu > {params.map((p, i) => { const eff = values[i] ?? 0; - const gc = `var(${OUT_GROUP_COLOR[p.group] || '--accent'})`; + const gc = `var(${GROUP_COLOR[p.group] || '--accent'})`; const dim = p.status === 'off'; const placeRight = i > params.length - 4; return ( @@ -119,7 +112,7 @@ export function OutputStage({ params, values, onChange, compact = false }: Outpu color: p.status === 'live' ? 'var(--fg-mute)' - : `var(${OUT_GROUP_COLOR[p.group] || '--accent'})`, + : `var(${GROUP_COLOR[p.group] || '--accent'})`, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', diff --git a/manifold/src/console/shared-ui.tsx b/manifold/src/console/shared-ui.tsx index d70cad8..27c52ad 100644 --- a/manifold/src/console/shared-ui.tsx +++ b/manifold/src/console/shared-ui.tsx @@ -6,17 +6,9 @@ * Manifold ships a single "composite" altitude). MiniMeters survives — it is * the live glanceable output readout used by CompositeStage's minimap. */ +import { GROUP_COLOR } from './model'; import type { MFParam } from './model'; -const MM_GROUP_COLOR: Record = { - formant: '--accent', - pitch: '--accent-2', - amp: '--good', - filter: '--warn', - fx: '--info', - mod: '--accent-3', -}; - /** MiniMeters — glanceable read-only output bars (no interaction). */ export function MiniMeters({ params, values }: { params: MFParam[]; values: number[] }) { return ( @@ -41,7 +33,7 @@ export function MiniMeters({ params, values }: { params: MFParam[]; values: numb right: 0, bottom: 0, height: `${v * 100}%`, - background: `var(${MM_GROUP_COLOR[params[i]?.group] || '--accent'})`, + background: `var(${GROUP_COLOR[params[i]?.group] || '--accent'})`, opacity: 0.3 + v * 0.6, }} /> diff --git a/manifold/src/dock/OutputControlRow.tsx b/manifold/src/dock/OutputControlRow.tsx index 3da9022..54bb452 100644 --- a/manifold/src/dock/OutputControlRow.tsx +++ b/manifold/src/dock/OutputControlRow.tsx @@ -9,6 +9,7 @@ */ import { useRef } from 'react'; import type { CSSProperties, PointerEvent as ReactPointerEvent } from 'react'; +import { GROUP_COLOR } from '../console/model'; import type { MFParam, ParamStatus } from '../console/model'; import { CurvePad } from '../console/CurvePad'; @@ -18,15 +19,6 @@ const STATE_META: { v: ParamStatus; label: string; color: string }[] = [ { v: 'live', label: 'live', color: 'var(--accent)' }, ]; -const GROUP_COLOR: Record = { - formant: '--accent', - pitch: '--accent-2', - amp: '--good', - filter: '--warn', - fx: '--info', - mod: '--accent-3', -}; - /** A compact dual-thumb min/max range (min blue, max orange — dock-spec §3.1). */ function DualRange({ min,