fix: matrix gain
This commit is contained in:
@@ -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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -12,6 +12,23 @@ const PENDING_FALLBACK_MS = 1000;
|
||||
|
||||
type CommitResult = Promise<boolean | void> | boolean | void;
|
||||
|
||||
function useNonPassiveWheel(
|
||||
ref: React.RefObject<HTMLElement | null>,
|
||||
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<T>({
|
||||
const pendingValue = useRef<T | null>(null);
|
||||
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(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<T>({
|
||||
return (
|
||||
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={id}
|
||||
className="choice-stepper__input"
|
||||
value={text}
|
||||
disabled={disabled}
|
||||
onChange={(e) => 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<number | null>(null);
|
||||
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(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 (
|
||||
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={id}
|
||||
className="choice-stepper__input"
|
||||
value={text}
|
||||
disabled={disabled}
|
||||
onChange={(e) => 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();
|
||||
|
||||
@@ -92,6 +92,23 @@ function formatGainDb(value: number) {
|
||||
return `${sign}${value.toFixed(1)} dB`;
|
||||
}
|
||||
|
||||
export function useNonPassiveWheel(
|
||||
ref: React.RefObject<Element | null>,
|
||||
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<boolean>;
|
||||
setMatrixGain: (input: InputChannel, output: OutputChannel, gain: number) => Promise<boolean>;
|
||||
setActiveSidebar: React.Dispatch<React.SetStateAction<SidebarId>>;
|
||||
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<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const WHEEL_COMMIT_DELAY = 250;
|
||||
|
||||
// key = `${input}-${output}`
|
||||
const pendingGains = useRef<Map<string, number>>(new Map());
|
||||
const commitTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(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<SVGSVGElement>(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<SVGElement>('[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 (
|
||||
<div className="signal-flow">
|
||||
<svg
|
||||
ref={svgRef}
|
||||
viewBox={`0 0 ${W} ${H}`}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
className="signal-flow__svg"
|
||||
@@ -221,7 +275,9 @@ function SignalFlowMatrix({
|
||||
strokeWidth={LINE_HITBOX_WIDTH}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => 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. */}
|
||||
|
||||
@@ -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<Record<string, number>>({});
|
||||
|
||||
// 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 (
|
||||
<>
|
||||
<div className="matrix-flow">
|
||||
@@ -76,6 +98,7 @@ function MatrixSection({
|
||||
setMatrixRouting={setMatrixRouting}
|
||||
setMatrixGain={setMatrixGain}
|
||||
setActiveSidebar={setActiveSidebar}
|
||||
onGainPreview={previewGain}
|
||||
/>
|
||||
</div>
|
||||
<div className="matrix-section">
|
||||
@@ -90,7 +113,7 @@ function MatrixSection({
|
||||
<div className="matrix-panel__rows">
|
||||
{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 (
|
||||
<div className="matrix-panel__row" key={String(input)}>
|
||||
@@ -111,7 +134,7 @@ function MatrixSection({
|
||||
|
||||
<ChoiceStepper
|
||||
className="matrix-gain-stepper"
|
||||
value={gain}
|
||||
value={snapToGain(gain)}
|
||||
values={GAIN_VALUES}
|
||||
formatLabel={(v: number) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||
onChange={(g) => handleGainChange(output, input, g)}
|
||||
|
||||
Reference in New Issue
Block a user