From a0c9fbb674b0ab00edb80474b7ff9d80dda616ad Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Thu, 13 Aug 2026 11:32:06 +0200 Subject: [PATCH] feat: add behaviour to hp/lp filters --- src-tauri/tauri.conf.json | 4 +- .../VerticalSlider/VerticalSlider.tsx | 6 +- .../components/GeqSection/GeqSection.tsx | 12 +- .../components/InputSection/InputSection.css | 2 +- .../components/InputSection/InputSection.tsx | 281 ++++++++---------- src/features/dsp408/constants.ts | 5 + src/features/dsp408/dsp408.ts | 31 +- 7 files changed, 167 insertions(+), 174 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ff4ac39..7297fb3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "thomann-dsp-app", - "width": 1200, - "height": 800, + "width": 1600, + "height": 950, "minWidth": 1200, "minHeight": 600 } diff --git a/src/components/VerticalSlider/VerticalSlider.tsx b/src/components/VerticalSlider/VerticalSlider.tsx index 3518e8f..7f03ec1 100644 --- a/src/components/VerticalSlider/VerticalSlider.tsx +++ b/src/components/VerticalSlider/VerticalSlider.tsx @@ -26,7 +26,7 @@ type VerticalSliderProps = { onChange?: (value: number) => Promise; onInput?: (value: number) => void; - unit?: string; + unit?: string | ((value: number) => string); formatValue?: (value: number, decimals: number) => string; showTicks?: boolean; @@ -198,6 +198,8 @@ export default function VerticalSlider({ const formattedValue = (formatValue ?? formatValueDefault)(displayValue, decimals); + const displayUnit = typeof unit === 'function' ? unit(displayValue) : unit; + function normalizeValue(input: number) { const clamped = Math.min(max, Math.max(min, input)); @@ -434,7 +436,7 @@ export default function VerticalSlider({ } }} /> - {unit && {unit}} + {displayUnit && {' ' + displayUnit}} ); diff --git a/src/features/dsp408/components/GeqSection/GeqSection.tsx b/src/features/dsp408/components/GeqSection/GeqSection.tsx index 593552a..eb911e7 100644 --- a/src/features/dsp408/components/GeqSection/GeqSection.tsx +++ b/src/features/dsp408/components/GeqSection/GeqSection.tsx @@ -208,7 +208,7 @@ function GeqSection({ }) { const [activeChannel, setActiveChannel] = useState(INPUT_ORDER[0]); const [liveGeq, setLiveGeq] = useState>>({}); - const [resetting, setResetting] = useState(false); // NEW + const [resetting, setResetting] = useState(false); const handleLiveValue = (channel: InputChannel, band: DiscreteFrequency, value: number) => { setLiveGeq((prev) => ({ @@ -231,7 +231,11 @@ function GeqSection({ return success; }; - // NEW: sequential per-band reset, since hardware has no bulk reset command. + const handleBypassClick = () => { + setGeqBypass(activeChannel, !activeGeq.bypass); + }; + + // Sequential per-band reset, since hardware has no bulk reset command. const handleReset = async (channel: InputChannel) => { if (resetting) return; setResetting(true); @@ -251,10 +255,6 @@ function GeqSection({ const activeGeq = activeInputState.geq; const activeLiveBands = liveGeq[activeChannel]; - const handleBypassClick = () => { - setGeqBypass(activeChannel, !activeGeq.bypass); - }; - const channelOptions = INPUT_ORDER.map((channel) => ({ channel, label: dsp.state.current_config.input_states[channel].name, diff --git a/src/features/dsp408/components/InputSection/InputSection.css b/src/features/dsp408/components/InputSection/InputSection.css index feaf6b4..09b2b90 100644 --- a/src/features/dsp408/components/InputSection/InputSection.css +++ b/src/features/dsp408/components/InputSection/InputSection.css @@ -228,7 +228,7 @@ .crossover-panel { position: relative; height: 100%; - aspect-ratio: 0.28; + aspect-ratio: 0.3; flex: 0 0 auto; display: flex; diff --git a/src/features/dsp408/components/InputSection/InputSection.tsx b/src/features/dsp408/components/InputSection/InputSection.tsx index 05c6b63..c4154da 100644 --- a/src/features/dsp408/components/InputSection/InputSection.tsx +++ b/src/features/dsp408/components/InputSection/InputSection.tsx @@ -6,7 +6,7 @@ import VerticalSlider, { // ASSUMPTION: an InputChannel enum exists alongside OutputChannel, mapping // input pages 1-8 similarly (In1 -> 0, In2 -> 1, ...). Adjust import/values // if the actual enum differs. -import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Meter } from '../../types'; +import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Meter, Crossover } from '../../types'; import { DSP408 as DSP } from '../../../../types'; import { CrossoverFilter } from '../../types'; import { LinearGraph } from '../../../../components/LinearGraph/LinearGraph'; @@ -15,6 +15,15 @@ import './InputSection.css'; import ChannelGroup from '../../../../components/ChannelGroup/ChannelGroup'; import Button from '../../../../components/Button/Button'; import MeterBar from '../../../../components/MeterBar/MeterBar'; +import { + FREQUENCY_MAX, + FREQUENCY_MIN, + FREQUENCY_QUANTIZATION, + GRAPH_X_MAX, + GRAPH_X_MIN, + GRAPH_Y_MAX, + GRAPH_Y_MIN, +} from '../../constants'; const PEQ_BANDS: PEQBand[] = [ PEQBand.B1, @@ -67,7 +76,7 @@ type SetPEQFn = ( type PeqParamKey = 'frequency' | 'q' | 'gain'; -type PEQValues = { +type PEQValue = { frequency: number; q: number; gain: number; @@ -91,7 +100,7 @@ function PeqPanelSliders({ const inputState = dsp.state.current_config.input_states[channel]; const peq = inputState.peq_chain.bands[band] as PEQ; - const commit = (overrides: Partial) => { + const commit = (overrides: Partial) => { const next = { ...peq, ...overrides }; return setPEQ( { Input: channel }, @@ -118,7 +127,7 @@ function PeqPanelSliders({ unit={param.unit} value={peq[param.key]} // onInput={(value) => onLiveValue(channel, param.key, value)} - onChange={(value) => commit({ [param.key]: value } as Partial)} + onChange={(value) => commit({ [param.key]: value } as Partial)} style={{ width: '100%', flex: '1 1 auto' }} />
{param.label}
@@ -129,45 +138,49 @@ function PeqPanelSliders({ ); } -const FREQ_MIN = 19.7; -const FREQ_MAX = 20160; -const FREQ_POINTS = 300; -const FREQ_STEP = makeLogStep(FREQ_MIN, FREQ_MAX, FREQ_POINTS); -const FREQ_QUANTIZE = makeLogQuantize(FREQ_MIN, FREQ_MAX, FREQ_POINTS); - function CrossoverPanel({ - channel, - dsp, + crossover, title, setCrossover, + onLiveValue, }: { - channel: Channel; - dsp: DSP; + crossover: Crossover; title: string; - setCrossover: ( - channel: Channel, - frequency: number, - filter_type: CrossoverFilter - ) => Promise; + setCrossover: (frequency: number, filter_type: CrossoverFilter) => Promise; + onLiveValue: (value: number) => void; }) { - const channelState = - 'Input' in channel - ? dsp.state?.current_config.input_states[channel.Input] - : dsp.state?.current_config.output_states[channel.Output]; + const frequency = crossover.frequency; + const filter_type = crossover.slope; + const bypass = filter_type === CrossoverFilter.Bypass; - const crossover = channelState?.crossover; + const lastFilterType = useRef( + filter_type !== CrossoverFilter.Bypass ? filter_type : CrossoverFilter.Lk24 + ); - const frequency = crossover.high_pass.frequency; - const filter_type = crossover.high_pass.slope; - const bypass = false; - - const commit = (overrides: { + const commit = async (overrides: { frequency?: number; filter_type?: CrossoverFilter; bypass?: boolean; }) => { - const next = { frequency, filter_type, bypass, ...overrides }; - return setCrossover(channel, next.frequency, next.filter_type); + const nextFrequency = overrides.frequency ?? frequency; + + const selectedFilterType = + overrides.filter_type !== undefined + ? overrides.filter_type + : bypass + ? lastFilterType.current + : filter_type; + + // Always remember the latest non-bypass selection. + if (selectedFilterType !== CrossoverFilter.Bypass) { + lastFilterType.current = selectedFilterType; + } + + const nextBypass = overrides.bypass ?? bypass; + + const nextFilterType = nextBypass ? CrossoverFilter.Bypass : selectedFilterType; + + return setCrossover(nextFrequency, nextFilterType); }; return ( @@ -175,20 +188,24 @@ function CrossoverPanel({
{title}
(value >= 1000 ? 'kHz' : 'Hz')} value={frequency} + formatValue={(value) => + value >= 1000 ? `${(value / 1000).toFixed(2)}` : `${Math.round(value)}` + } onChange={(value) => commit({ frequency: value })} + onInput={(value) => onLiveValue(value)} style={{ width: '100%', flex: '1 1 auto' }} /> handleFilterTypeChange(e.target.value as CrossoverFilter)} - > - {FILTER_TYPE_OPTIONS.map((opt) => ( - - ))} - - - - - */} diff --git a/src/features/dsp408/constants.ts b/src/features/dsp408/constants.ts index 718e1c4..32d5b3c 100644 --- a/src/features/dsp408/constants.ts +++ b/src/features/dsp408/constants.ts @@ -93,6 +93,11 @@ export const GEQ_GAIN_MIN = -12; export const GEQ_GAIN_MAX = 12; export const GEQ_GAIN_STEP = 0.1; +// ------ Filter constants ------ +export const FREQUENCY_MIN = 19.7; +export const FREQUENCY_MAX = 20160; +export const FREQUENCY_QUANTIZATION = 300; + // ------ Matrix constants ------ export const MATRIX_GAIN_MIN = -60; export const MATRIX_GAIN_MAX = 0; diff --git a/src/features/dsp408/dsp408.ts b/src/features/dsp408/dsp408.ts index 661cfea..6a3d3a4 100644 --- a/src/features/dsp408/dsp408.ts +++ b/src/features/dsp408/dsp408.ts @@ -11,6 +11,7 @@ import { PEQFilter, PEQBand, InputSource, + Crossover, } from './types'; import { SetDsps, NotifyFn, updateDSP, setPollingPaused } from '../../dsp/dspShared'; import { registerPollStopper } from '../../dsp/pollRegistry'; @@ -1144,9 +1145,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { if (!d.state) return d; const cfg = d.state.current_config; - const lowPass = { + + const lowPass: Crossover = { frequency: freq, - filter_type: filterType, + slope: filterType, }; if ('Input' in channel) { @@ -1160,7 +1162,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { ...cfg.input_states, [channel.Input]: { ...cfg.input_states[channel.Input], - low_pass: lowPass, + crossover: { + ...cfg.input_states[channel.Input].crossover, + low_pass: lowPass, + }, }, }, }, @@ -1178,7 +1183,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { ...cfg.output_states, [channel.Output]: { ...cfg.output_states[channel.Output], - low_pass: lowPass, + crossover: { + ...cfg.output_states[channel.Output].crossover, + low_pass: lowPass, + }, }, }, }, @@ -1227,9 +1235,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { if (!d.state) return d; const cfg = d.state.current_config; - const highPass = { + + const highPass: Crossover = { frequency: freq, - filter_type: filterType, + slope: filterType, }; if ('Input' in channel) { @@ -1243,7 +1252,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { ...cfg.input_states, [channel.Input]: { ...cfg.input_states[channel.Input], - high_pass: highPass, + crossover: { + ...cfg.input_states[channel.Input].crossover, + high_pass: highPass, + }, }, }, }, @@ -1261,7 +1273,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { ...cfg.output_states, [channel.Output]: { ...cfg.output_states[channel.Output], - high_pass: highPass, + crossover: { + ...cfg.output_states[channel.Output].crossover, + high_pass: highPass, + }, }, }, },