diff --git a/src/features/dsp408/components/ChannelSection/ChannelSection.tsx b/src/features/dsp408/components/ChannelSection/ChannelSection.tsx index 1ed5ce5..aaada2a 100644 --- a/src/features/dsp408/components/ChannelSection/ChannelSection.tsx +++ b/src/features/dsp408/components/ChannelSection/ChannelSection.tsx @@ -36,6 +36,12 @@ import { getPeqCurvePoints, getPeqPhasePoints } from '../../utils/graph/peq'; import { logSpace, sumCurvePoints, sumPhaseCurvePoints } from '../../utils/graph/curve'; import { getCrossoverCurvePoints, getCrossoverPhasePoints } from '../../utils/graph/crossover'; +function nearestQValue(q: number, values: readonly number[] = PEAK_Q_VALUES): number { + return values.reduce((nearest, current) => + Math.abs(current - q) < Math.abs(nearest - q) ? current : nearest + ); +} + type GraphView = 'both' | 'magnitude' | 'phase'; function GraphViewToggle({ @@ -133,9 +139,11 @@ function PeqPanelSliders({ const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type); const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type); + const nearestQ = nearestQValue(peq.q); + const commit = (overrides: Partial) => { const next = { ...peq, ...overrides }; - return setPEQ(next.gain, next.frequency, next.q, next.filter_type, next.bypass); + return setPEQ(next.gain, next.frequency, overrides.q ?? nearestQ, next.filter_type, next.bypass); }; return ( @@ -169,9 +177,9 @@ function PeqPanelSliders({ max={qMax} values={PEAK_Q_VALUES} formatValue={(v) => v.toFixed(2)} - value={peq.q} + value={nearestQ} onChange={(value) => commit({ q: value })} - onInput={(value) => onLiveValue({ ...peq, q: value })} + onInput={(value) => onLiveValue({ ...peq, q: nearestQValue(value) })} disabled={qDisabled} style={{ width: '100%', flex: '1 1 auto' }} /> @@ -318,9 +326,16 @@ function PeqBandList({ bandsOrder: PEQBand[]; }) { const commit = (band: PEQBand, overrides: Partial) => { - const peq = liveBands[band] ?? bands[band]; - const next = { ...peq, ...overrides }; - return setPEQ(band, next.gain, next.frequency, next.q, next.filter_type, next.bypass); + const live = liveBands[band] ?? bands[band]; + const next = { ...live, ...overrides }; + return setPEQ( + band, + next.gain, + next.frequency, + nearestQValue(next.q), + next.filter_type, + next.bypass + ); }; return ( @@ -340,6 +355,7 @@ function PeqBandList({ const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type]; const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax); + const qValue = nearestQValue(peq.q, qValues.length ? qValues : PEAK_Q_VALUES); const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type); const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type); @@ -376,7 +392,7 @@ function PeqBandList({ v.toFixed(2)} disabled={qDisabled} @@ -401,7 +417,14 @@ function PeqBandList({ formatLabel={(v) => PEQ_FILTERS_LABEL.find((opt) => opt.value === v)?.label ?? String(v) } - onChange={(value) => commit(band, { filter_type: value, q: 3.0 })} + onChange={(value) => { + const [min, max] = PEQ_Q_RANGES[value]; + const allowed = PEAK_Q_VALUES.filter((q) => q >= min && q <= max); + commit(band, { + filter_type: value, + q: nearestQValue(3.0, allowed.length ? allowed : PEAK_Q_VALUES), + }); + }} />