From 388552f1c4a627e1845808b5868e4882f3654d24 Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Fri, 2 Oct 2026 20:38:23 +0200 Subject: [PATCH] fix: matrix gain --- README.md | 7 +- .../ChoiceStepper/ChoiceStepper.tsx | 59 +++++++++----- .../SignalFlowMatrix/SignalFlowMatrix.tsx | 80 ++++++++++++++++--- .../Matrixsection/Matrixsection.tsx | 27 ++++++- 4 files changed, 139 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index addc2d6..b7e2cd8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,10 @@ This template should help get you started developing with Tauri, React and Types - Check render to improve perfoormances - Check in meter if clip bool is limit instead - On close disconenct all ds^p -- Ma-trix gain block sometimes - Q out of range -- Check end to end behavioour \ No newline at end of file +- Check end to end behavioour + +504 27.139876100 192.168.49.83 192.168.49.30 TCP 67 2d 10020001052d31313131100328 31 31 31 56870 → 9761 [PSH, ACK] Seq=545 Ack=2312 Win=65408 Len=13 +1158 75.734957800 192.168.49.83 192.168.49.30 TCP 68 2d 10020001062d003131313110032b 00 31 31 50440 → 9761 [PSH, ACK] Seq=82 Ack=106 Win=65430 Len=14 + diff --git a/src/components/ChoiceStepper/ChoiceStepper.tsx b/src/components/ChoiceStepper/ChoiceStepper.tsx index e89e2c4..6e10dec 100644 --- a/src/components/ChoiceStepper/ChoiceStepper.tsx +++ b/src/components/ChoiceStepper/ChoiceStepper.tsx @@ -12,6 +12,23 @@ const PENDING_FALLBACK_MS = 1000; type CommitResult = Promise | boolean | void; +function useNonPassiveWheel( + ref: React.RefObject, + handler: (e: WheelEvent) => void +) { + const handlerRef = useRef(handler); + handlerRef.current = handler; + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const listener = (e: WheelEvent) => handlerRef.current(e); + el.addEventListener('wheel', listener, { passive: false }); + return () => el.removeEventListener('wheel', listener); + }, [ref]); +} + function StepperArrows({ onUp, onDown, @@ -80,6 +97,17 @@ function ChoiceStepperList({ const pendingValue = useRef(null); const wheelTimeout = useRef | null>(null); const fallbackTimeout = useRef | null>(null); + const inputRef = useRef(null); + + useNonPassiveWheel(inputRef, (e) => { + e.preventDefault(); + + if (e.deltaY < 0 && canUp) { + stepWheel(1); + } else if (e.deltaY > 0 && canDown) { + stepWheel(-1); + } + }); useEffect(() => { if (interacting.current) { @@ -180,21 +208,13 @@ function ChoiceStepperList({ return (
setText(e.target.value)} onBlur={commitText} - onWheel={(e) => { - e.preventDefault(); - - if (e.deltaY < 0 && canUp) { - stepWheel(1); - } else if (e.deltaY > 0 && canDown) { - stepWheel(-1); - } - }} onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur(); @@ -256,6 +276,17 @@ function ChoiceStepperRange({ const pendingValue = useRef(null); const wheelTimeout = useRef | null>(null); const fallbackTimeout = useRef | null>(null); + const inputRef = useRef(null); + + useNonPassiveWheel(inputRef, (e) => { + e.preventDefault(); + + if (e.deltaY < 0 && canUp) { + stepWheel(1); + } else if (e.deltaY > 0 && canDown) { + stepWheel(-1); + } + }); useEffect(() => { if (interacting.current) { @@ -344,21 +375,13 @@ function ChoiceStepperRange({ return (
setText(e.target.value)} onBlur={commitText} - onWheel={(e) => { - e.preventDefault(); - - if (e.deltaY < 0 && canUp) { - stepWheel(1); - } else if (e.deltaY > 0 && canDown) { - stepWheel(-1); - } - }} onKeyDown={(e) => { if (e.key === 'Enter') { e.currentTarget.blur(); diff --git a/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx b/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx index 32cb191..651a016 100644 --- a/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx +++ b/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx @@ -92,6 +92,23 @@ function formatGainDb(value: number) { return `${sign}${value.toFixed(1)} dB`; } +export function useNonPassiveWheel( + ref: React.RefObject, + handler: (e: WheelEvent) => void +) { + const handlerRef = useRef(handler); + handlerRef.current = handler; + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const listener = (e: Event) => handlerRef.current(e as WheelEvent); + el.addEventListener('wheel', listener, { passive: false }); + return () => el.removeEventListener('wheel', listener); + }, [ref]); +} + type GainTooltipState = { visible: boolean; x: number; @@ -104,11 +121,13 @@ function SignalFlowMatrix({ setMatrixRouting, setMatrixGain, setActiveSidebar, + onGainPreview, }: { dsp: DSP408; setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) => Promise; setMatrixGain: (input: InputChannel, output: OutputChannel, gain: number) => Promise; setActiveSidebar: React.Dispatch>; + onGainPreview: (input: InputChannel, output: OutputChannel, value: number | null) => void; }) { const nInputs = INPUT_ORDER.length; const nOutputs = OUTPUT_ORDER.length; @@ -137,9 +156,16 @@ function SignalFlowMatrix({ }); const hideTooltipTimer = useRef | null>(null); + const WHEEL_COMMIT_DELAY = 250; + + // key = `${input}-${output}` + const pendingGains = useRef>(new Map()); + const commitTimers = useRef>>(new Map()); + useEffect(() => { return () => { if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current); + commitTimers.current.forEach(clearTimeout); }; }, []); @@ -151,28 +177,55 @@ function SignalFlowMatrix({ void setMatrixRouting(output, next); }; - const handleWheelGain = (e: React.WheelEvent, output: OutputChannel, input: InputChannel) => { + const svgRef = useRef(null); + + const handleWheelGain = (e: WheelEvent, output: OutputChannel, input: InputChannel) => { e.preventDefault(); - const current = getGain(dsp, output, input); + + const key = `${input}-${output}`; + // Base on the pending value if we're mid-wheel, otherwise the real state + const current = pendingGains.current.get(key) ?? getGain(dsp, output, input); const delta = e.deltaY < 0 ? MATRIX_GAIN_STEP : -MATRIX_GAIN_STEP; const next = Math.round(Math.min(MATRIX_GAIN_MAX, Math.max(MATRIX_GAIN_MIN, current + delta)) * 10) / 10; - void setMatrixGain(input, output, next); - // Show a small tooltip with the new gain value right next to the cursor, - // then let it fade away shortly after wheeling stops. + pendingGains.current.set(key, next); + onGainPreview(input, output, next); + + // Debounced send: only the final value goes out once the wheel pauses + const existing = commitTimers.current.get(key); + if (existing) clearTimeout(existing); + commitTimers.current.set( + key, + setTimeout(async () => { + commitTimers.current.delete(key); + const value = pendingGains.current.get(key); + if (value === undefined) return; + await setMatrixGain(input, output, value); + // Only release if the user hasn't wheeled again while we were awaiting + if (pendingGains.current.get(key) === value) { + pendingGains.current.delete(key); + onGainPreview(input, output, null); + } + }, WHEEL_COMMIT_DELAY) + ); + + // Tooltip (unchanged, shows the pending value) if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current); - setGainTooltip({ - visible: true, - x: e.clientX, - y: e.clientY, - label: formatGainDb(next), - }); + setGainTooltip({ visible: true, x: e.clientX, y: e.clientY, label: formatGainDb(next) }); hideTooltipTimer.current = setTimeout(() => { setGainTooltip((t) => ({ ...t, visible: false })); }, GAIN_TOOLTIP_HOLD_MS); }; + useNonPassiveWheel(svgRef, (e) => { + const hit = (e.target as Element).closest('[data-wheel-route]'); + if (!hit) return; // not over a connected line: let the page scroll normally + const { input, output } = hit.dataset; + if (!input || !output) return; + handleWheelGain(e, output as OutputChannel, input as InputChannel); + }); + const routePairs = useMemo(() => { const pairs: { input: InputChannel; output: OutputChannel; i: number; j: number }[] = []; OUTPUT_ORDER.forEach((output, j) => { @@ -196,6 +249,7 @@ function SignalFlowMatrix({ return (
handleToggle(output, input)} - onWheel={connected ? (e) => handleWheelGain(e, output, input) : undefined} + data-wheel-route={connected ? '' : undefined} + data-input={input} + data-output={output} /> {/* Visible line, purely decorative — no pointer events, so it never competes with the hitbox above for hover/click/wheel. */} diff --git a/src/features/dsp408/components/Matrixsection/Matrixsection.tsx b/src/features/dsp408/components/Matrixsection/Matrixsection.tsx index 59bf129..46cd6ca 100644 --- a/src/features/dsp408/components/Matrixsection/Matrixsection.tsx +++ b/src/features/dsp408/components/Matrixsection/Matrixsection.tsx @@ -13,6 +13,7 @@ import { } from '../../constants'; import { SidebarId } from '../../constants'; import SignalFlowMatrix from '../../../../components/SignalFlowMatrix/SignalFlowMatrix'; +import { useState } from 'react'; import './Matrixsection.css'; const GAIN_VALUES: number[] = (() => { @@ -39,6 +40,12 @@ function getRoutedInputs(dsp: DSP, output: OutputChannel): InputChannel[] { return getMatrixRoutes(dsp, output).connected; } +function snapToGain(g: number): number { + return GAIN_VALUES.reduce((best, v) => + Math.abs(v - g) < Math.abs(best - g) ? v : best + ); +} + function MatrixSection({ dsp, setMatrixRouting, @@ -68,6 +75,21 @@ function MatrixSection({ void setMatrixGain(input, output, gain); }; + const [liveGains, setLiveGains] = useState>({}); + + // value = number while wheeling, null once the commit is done + const previewGain = (input: InputChannel, output: OutputChannel, value: number | null) => { + const key = `${input}-${output}`; + setLiveGains((prev) => { + if (value === null) { + if (!(key in prev)) return prev; + const { [key]: _removed, ...rest } = prev; + return rest; + } + return { ...prev, [key]: value }; + }); + }; + return ( <>
@@ -76,6 +98,7 @@ function MatrixSection({ setMatrixRouting={setMatrixRouting} setMatrixGain={setMatrixGain} setActiveSidebar={setActiveSidebar} + onGainPreview={previewGain} />
@@ -90,7 +113,7 @@ function MatrixSection({
{INPUT_ORDER.map((input) => { const connected = isRouted(dsp, output, input); - const gain = getLinkGain(dsp, output, input); + const gain = liveGains[`${input}-${output}`] ?? getLinkGain(dsp, output, input); return (
@@ -111,7 +134,7 @@ function MatrixSection({ `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`} onChange={(g) => handleGainChange(output, input, g)}