feat: add peq foor input
This commit is contained in:
@@ -3,18 +3,22 @@ import VerticalSlider, {
|
||||
makeLogStep,
|
||||
makeLogQuantize,
|
||||
} from '../../../../components/VerticalSlider/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, Crossover } from '../../types';
|
||||
import {
|
||||
CROSSOVER_FILTER_LABEL,
|
||||
PEQ_BANDS_ORDER,
|
||||
PEQ_FILTERS_LABEL,
|
||||
PEQ_Q_RANGES,
|
||||
PEAK_Q_VALUES,
|
||||
} from '../../constants';
|
||||
import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Crossover } from '../../types';
|
||||
import { DSP408 as DSP } from '../../../../types';
|
||||
import { CrossoverFilter } from '../../types';
|
||||
import { LinearGraph } from '../../../../components/LinearGraph/LinearGraph';
|
||||
import { LinearGraph, LinearGraphHandle } from '../../../../components/LinearGraph/LinearGraph';
|
||||
import { useRef, useState } from 'react';
|
||||
import './InputSection.css';
|
||||
import ChannelGroup from '../../../../components/ChannelGroup/ChannelGroup';
|
||||
import Button from '../../../../components/Button/Button';
|
||||
import MeterBar from '../../../../components/MeterBar/MeterBar';
|
||||
import { buildTotalCurveDraw, buildTotalPhaseCurveDraw } from '../../utils/totalCurve';
|
||||
import {
|
||||
FREQUENCY_MAX,
|
||||
FREQUENCY_MIN,
|
||||
@@ -25,114 +29,161 @@ import {
|
||||
GRAPH_Y_MIN,
|
||||
} from '../../constants';
|
||||
|
||||
const PEQ_BANDS: PEQBand[] = [
|
||||
PEQBand.B1,
|
||||
PEQBand.B2,
|
||||
PEQBand.B3,
|
||||
PEQBand.B4,
|
||||
PEQBand.B5,
|
||||
PEQBand.B6,
|
||||
PEQBand.B7,
|
||||
PEQBand.B8,
|
||||
];
|
||||
type GraphView = 'both' | 'magnitude' | 'phase';
|
||||
|
||||
// ASSUMPTION: adjust these to match the real PEQFilter enum in ../../types
|
||||
const PEQ_FILTER_TYPE_OPTIONS: { value: PEQFilter; label: string }[] = [
|
||||
{ value: PEQFilter.Peak, label: 'Peak' },
|
||||
{ value: PEQFilter.LowShelf, label: 'Low Shelf' },
|
||||
{ value: PEQFilter.HighShelf, label: 'High Shelf' },
|
||||
];
|
||||
function GraphViewToggle({
|
||||
view,
|
||||
onChange,
|
||||
}: {
|
||||
view: GraphView;
|
||||
onChange: (v: GraphView) => void;
|
||||
}) {
|
||||
const options: { value: GraphView; label: string }[] = [
|
||||
{ value: 'both', label: 'Both' },
|
||||
{ value: 'magnitude', label: 'Mag' },
|
||||
{ value: 'phase', label: 'Phase' },
|
||||
];
|
||||
|
||||
const FILTER_TYPE_OPTIONS: { value: CrossoverFilter; label: string }[] = [
|
||||
{ value: CrossoverFilter.Bw6, label: 'BW-6' },
|
||||
{ value: CrossoverFilter.Bw12, label: 'BW-12' },
|
||||
{ value: CrossoverFilter.Bw18, label: 'BW-18' },
|
||||
{ value: CrossoverFilter.Bw24, label: 'BW-24' },
|
||||
{ value: CrossoverFilter.Bw30, label: 'BW-30' },
|
||||
{ value: CrossoverFilter.Bw36, label: 'BW-36' },
|
||||
{ value: CrossoverFilter.Bw42, label: 'BW-42' },
|
||||
{ value: CrossoverFilter.Bw48, label: 'BW-48' },
|
||||
{ value: CrossoverFilter.Lk12, label: 'LR-12' },
|
||||
{ value: CrossoverFilter.Lk24, label: 'LR-24' },
|
||||
{ value: CrossoverFilter.Lk36, label: 'LR-36' },
|
||||
{ value: CrossoverFilter.Lk48, label: 'LR-48' },
|
||||
];
|
||||
return (
|
||||
<div className="input-graph-view-toggle">
|
||||
{options.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
className={`input-graph-view-toggle__btn${view === opt.value ? ' active' : ''}`}
|
||||
onClick={() => onChange(opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const PEQ_SLIDER_PARAMS = [
|
||||
{ key: 'frequency', label: 'Frequency', min: 19.7, max: 20160, step: 0.5, unit: ' Hz' },
|
||||
{ key: 'q', label: 'Q', min: 1, max: 999, step: 1, unit: '' },
|
||||
{ key: 'gain', label: 'Gain', min: -12, max: +12, step: 0.1, unit: ' dB' },
|
||||
] as const;
|
||||
|
||||
type SetPEQFn = (
|
||||
channel: Channel,
|
||||
band: PEQBand,
|
||||
gainDb: number,
|
||||
freqHz: number,
|
||||
q: number,
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
|
||||
type PeqParamKey = 'frequency' | 'q' | 'gain';
|
||||
|
||||
type PEQValue = {
|
||||
frequency: number;
|
||||
q: number;
|
||||
gain: number;
|
||||
filter_type: PEQFilter;
|
||||
function InputActions({
|
||||
bypass,
|
||||
resetting,
|
||||
onBypass,
|
||||
onReset,
|
||||
}: {
|
||||
bypass: boolean;
|
||||
};
|
||||
resetting: boolean;
|
||||
onBypass: () => void;
|
||||
onReset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="geq-channel-selector">
|
||||
<Button
|
||||
variant="toggle"
|
||||
className={bypass ? 'active-warning' : ''}
|
||||
onClick={onBypass}
|
||||
style={{
|
||||
width: '100%',
|
||||
fontSize: '3.5cqh',
|
||||
paddingTop: '2cqh',
|
||||
paddingBottom: '2cqh',
|
||||
minHeight: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{bypass ? 'Bypassed' : 'Bypass'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="toggle"
|
||||
onClick={onReset}
|
||||
disabled={resetting}
|
||||
style={{
|
||||
width: '100%',
|
||||
fontSize: '3.5cqh',
|
||||
paddingTop: '2cqh',
|
||||
paddingBottom: '2cqh',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{resetting ? 'Resetting…' : 'Reset'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PeqPanelSliders({
|
||||
channel,
|
||||
dsp,
|
||||
band,
|
||||
peq,
|
||||
setPEQ,
|
||||
// onLiveValue,
|
||||
onLiveValue,
|
||||
}: {
|
||||
channel: InputChannel;
|
||||
dsp: DSP;
|
||||
band: PEQBand;
|
||||
setPEQ: SetPEQFn;
|
||||
// onLiveValue: (channel: InputChannel, key: PeqParamKey, value: number) => void;
|
||||
peq: PEQ;
|
||||
setPEQ: (
|
||||
gainDb: number,
|
||||
freqHz: number,
|
||||
q: number,
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
onLiveValue: (value: PEQ) => void;
|
||||
}) {
|
||||
const inputState = dsp.state.current_config.input_states[channel];
|
||||
const peq = inputState.peq_chain.bands[band] as PEQ;
|
||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||
|
||||
const commit = (overrides: Partial<PEQValue>) => {
|
||||
const commit = (overrides: Partial<PEQ>) => {
|
||||
const next = { ...peq, ...overrides };
|
||||
return setPEQ(
|
||||
{ Input: channel },
|
||||
band,
|
||||
next.gain,
|
||||
next.frequency,
|
||||
next.q,
|
||||
next.filter_type,
|
||||
next.bypass
|
||||
);
|
||||
return setPEQ(next.gain, next.frequency, next.q, next.filter_type, next.bypass);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="input-panel">
|
||||
<div className="input-panel__title">{'PEQ Parameters'}</div>
|
||||
<div className="input-panel__title">PEQ Parameters</div>
|
||||
|
||||
<div className="input-panel__grid">
|
||||
{PEQ_SLIDER_PARAMS.map((param) => (
|
||||
<div className={`input-panel__cell input-panel__cell--${param.key}`} key={param.key}>
|
||||
<VerticalSlider
|
||||
min={param.min}
|
||||
max={param.max}
|
||||
step={() => param.step}
|
||||
unit={param.unit}
|
||||
value={peq[param.key]}
|
||||
// onInput={(value) => onLiveValue(channel, param.key, value)}
|
||||
onChange={(value) => commit({ [param.key]: value } as Partial<PEQValue>)}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
<div className="input-panel__cell-label">{param.label}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="input-panel__cell input-panel__cell--frequency">
|
||||
<VerticalSlider
|
||||
min={FREQUENCY_MIN}
|
||||
max={FREQUENCY_MAX}
|
||||
scale="log"
|
||||
step={makeLogStep(FREQUENCY_MIN, FREQUENCY_MAX, FREQUENCY_QUANTIZATION)}
|
||||
quantize={makeLogQuantize(FREQUENCY_MIN, FREQUENCY_MAX, FREQUENCY_QUANTIZATION)}
|
||||
unit={(value) => (value >= 1000 ? 'kHz' : 'Hz')}
|
||||
formatValue={(value) =>
|
||||
value >= 1000 ? `${(value / 1000).toFixed(2)}` : `${value.toFixed(1)}`
|
||||
}
|
||||
value={peq.frequency}
|
||||
onChange={(value) => commit({ frequency: value })}
|
||||
onInput={(value) => onLiveValue({ ...peq, frequency: value })}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Frequency</div>
|
||||
</div>
|
||||
|
||||
<div className="input-panel__cell input-panel__cell--q">
|
||||
<VerticalSlider
|
||||
min={qMin}
|
||||
max={qMax}
|
||||
values={PEAK_Q_VALUES}
|
||||
formatValue={(v) => v.toFixed(2)}
|
||||
value={peq.q}
|
||||
onChange={(value) => commit({ q: value })}
|
||||
onInput={(value) => onLiveValue({ ...peq, q: value })}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Q</div>
|
||||
</div>
|
||||
|
||||
<div className="input-panel__cell input-panel__cell--gain">
|
||||
<VerticalSlider
|
||||
min={-12}
|
||||
max={12}
|
||||
step={() => 0.1}
|
||||
unit="dB"
|
||||
value={peq.gain}
|
||||
formatValue={(v, d) => (v > 0 ? `+${v.toFixed(d)}` : v.toFixed(d))}
|
||||
onChange={(value) => commit({ gain: value })}
|
||||
onInput={(value) => onLiveValue({ ...peq, gain: value })}
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Gain</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -154,7 +205,7 @@ function CrossoverPanel({
|
||||
const bypass = filter_type === CrossoverFilter.Bypass;
|
||||
|
||||
const lastFilterType = useRef<CrossoverFilter>(
|
||||
filter_type !== CrossoverFilter.Bypass ? filter_type : CrossoverFilter.Lk24
|
||||
filter_type !== CrossoverFilter.Bypass ? filter_type : CrossoverFilter.Lk48
|
||||
);
|
||||
|
||||
const commit = async (overrides: {
|
||||
@@ -196,7 +247,7 @@ function CrossoverPanel({
|
||||
unit={(value) => (value >= 1000 ? 'kHz' : 'Hz')}
|
||||
value={frequency}
|
||||
formatValue={(value) =>
|
||||
value >= 1000 ? `${(value / 1000).toFixed(2)}` : `${Math.round(value)}`
|
||||
value >= 1000 ? `${(value / 1000).toFixed(2)}` : `${value.toFixed(1)}`
|
||||
}
|
||||
onChange={(value) => commit({ frequency: value })}
|
||||
onInput={(value) => onLiveValue(value)}
|
||||
@@ -208,7 +259,7 @@ function CrossoverPanel({
|
||||
value={bypass ? lastFilterType.current : filter_type}
|
||||
onChange={(e) => commit({ filter_type: e.target.value as CrossoverFilter })}
|
||||
>
|
||||
{FILTER_TYPE_OPTIONS.map((opt) => (
|
||||
{CROSSOVER_FILTER_LABEL.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
@@ -236,32 +287,29 @@ function CrossoverPanel({
|
||||
}
|
||||
|
||||
function PeqBandList({
|
||||
channel,
|
||||
dsp,
|
||||
bands,
|
||||
liveBands,
|
||||
activeBand,
|
||||
setActiveBand,
|
||||
setPEQ,
|
||||
}: {
|
||||
channel: InputChannel;
|
||||
dsp: DSP;
|
||||
bands: Record<PEQBand, PEQ>;
|
||||
liveBands: Partial<Record<PEQBand, PEQ>>;
|
||||
activeBand: PEQBand;
|
||||
setActiveBand: (band: PEQBand) => void;
|
||||
setPEQ: SetPEQFn;
|
||||
setPEQ: (
|
||||
band: PEQBand,
|
||||
gainDb: number,
|
||||
freqHz: number,
|
||||
q: number,
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
}) {
|
||||
const inputState = dsp.state.current_config.input_states[channel];
|
||||
|
||||
const commit = (band: PEQBand, overrides: Partial<PEQValue>) => {
|
||||
const peq = inputState.peq_chain.bands[band] as PEQ;
|
||||
const commit = (band: PEQBand, overrides: Partial<PEQ>) => {
|
||||
const peq = liveBands[band] ?? bands[band];
|
||||
const next = { ...peq, ...overrides };
|
||||
return setPEQ(
|
||||
{ Input: channel },
|
||||
band,
|
||||
next.gain,
|
||||
next.frequency,
|
||||
next.q,
|
||||
next.filter_type,
|
||||
next.bypass
|
||||
);
|
||||
return setPEQ(band, next.gain, next.frequency, next.q, next.filter_type, next.bypass);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -275,8 +323,8 @@ function PeqBandList({
|
||||
<div className="peq-band-list__cell">Bypass</div>
|
||||
</div>
|
||||
|
||||
{PEQ_BANDS.map((band, i) => {
|
||||
const peq = inputState.peq_chain.bands[band] as PEQ;
|
||||
{PEQ_BANDS_ORDER.map((band, i) => {
|
||||
const peq = liveBands[band] ?? bands[band];
|
||||
const isActive = band === activeBand;
|
||||
|
||||
return (
|
||||
@@ -293,14 +341,22 @@ function PeqBandList({
|
||||
{`B${i + 1}`}
|
||||
</Button>
|
||||
|
||||
{/* Frequency */}
|
||||
<input
|
||||
className="peq-band-list__input"
|
||||
type="number"
|
||||
step={0.5}
|
||||
min={19.7}
|
||||
max={20160}
|
||||
value={peq.frequency}
|
||||
onChange={(e) => commit(band, { frequency: Number(e.target.value) })}
|
||||
type="text"
|
||||
value={
|
||||
peq.frequency >= 1000 ? (peq.frequency / 1000).toFixed(2) : peq.frequency.toFixed(1)
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
|
||||
if (!Number.isNaN(value)) {
|
||||
commit(band, {
|
||||
frequency: peq.frequency >= 1000 ? value * 1000 : value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
@@ -326,9 +382,9 @@ function PeqBandList({
|
||||
<select
|
||||
className="peq-band-list__select"
|
||||
value={peq.filter_type}
|
||||
onChange={(e) => commit(band, { filter_type: e.target.value as PEQFilter })}
|
||||
onChange={(e) => commit(band, { filter_type: e.target.value as PEQFilter, q: 3.0 })}
|
||||
>
|
||||
{PEQ_FILTER_TYPE_OPTIONS.map((opt) => (
|
||||
{PEQ_FILTERS_LABEL.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
@@ -356,15 +412,74 @@ type SetFilterFn = (
|
||||
filterType: CrossoverFilter
|
||||
) => Promise<boolean>;
|
||||
|
||||
type setPeqBandFn = (
|
||||
channel: Channel,
|
||||
band: PEQBand,
|
||||
gainDb: number,
|
||||
freqHz: number,
|
||||
q: number,
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
/** Small DOM-overlay markers positioned on top of the magnitude graph via its exposed toPxX/toPxY. */
|
||||
// function GraphMarkers({
|
||||
// graphRef,
|
||||
// bands,
|
||||
// activeBand,
|
||||
// setActiveBand,
|
||||
// hpFreq,
|
||||
// hpBypassed,
|
||||
// lpFreq,
|
||||
// lpBypassed,
|
||||
// }: {
|
||||
// graphRef: React.RefObject<LinearGraphHandle>;
|
||||
// bands: Record<PEQBand, PEQ>;
|
||||
// activeBand: PEQBand;
|
||||
// setActiveBand: (band: PEQBand) => void;
|
||||
// hpFreq: number;
|
||||
// hpBypassed: boolean;
|
||||
// lpFreq: number;
|
||||
// lpBypassed: boolean;
|
||||
// }) {
|
||||
// const g = graphRef.current;
|
||||
// if (!g) return null;
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {PEQ_BANDS_ORDER.map((band, i) => {
|
||||
// const peq = bands[band];
|
||||
// const left = g.toPxX(peq.frequency);
|
||||
// const top = g.toPxY(peq.gain);
|
||||
// const isActive = band === activeBand;
|
||||
|
||||
// return (
|
||||
// <button
|
||||
// key={band}
|
||||
// type="button"
|
||||
// className={`graph-marker graph-marker--peq${isActive ? ' graph-marker--active' : ''}${
|
||||
// peq.bypass ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left, top }}
|
||||
// onClick={() => setActiveBand(band)}
|
||||
// title={`B${i + 1} · ${Math.round(peq.frequency)} Hz · ${peq.gain.toFixed(1)} dB`}
|
||||
// >
|
||||
// {`B${i + 1}`}
|
||||
// </button>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <div
|
||||
// className={`graph-marker graph-marker--crossover graph-marker--hp${
|
||||
// hpBypassed ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left: g.toPxX(hpFreq), top: g.toPxY(0) }}
|
||||
// title={`HPF · ${Math.round(hpFreq)} Hz`}
|
||||
// >
|
||||
// HP
|
||||
// </div>
|
||||
// <div
|
||||
// className={`graph-marker graph-marker--crossover graph-marker--lp${
|
||||
// lpBypassed ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left: g.toPxX(lpFreq), top: g.toPxY(0) }}
|
||||
// title={`LPF · ${Math.round(lpFreq)} Hz`}
|
||||
// >
|
||||
// LP
|
||||
// </div>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
function InputSection({
|
||||
dsp,
|
||||
@@ -378,16 +493,31 @@ function InputSection({
|
||||
channel: InputChannel;
|
||||
setHighPass: SetFilterFn;
|
||||
setLowPass: SetFilterFn;
|
||||
setPeq: setPeqBandFn;
|
||||
setPeq: (
|
||||
channel: Channel,
|
||||
band: PEQBand,
|
||||
gainDb: number,
|
||||
freqHz: number,
|
||||
q: number,
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
setPeqBypass: (channel: Channel, bypass: boolean) => Promise<boolean>;
|
||||
}) {
|
||||
// UI variables
|
||||
const [activeBand, setActiveBand] = useState<PEQBand>(PEQBand.B1);
|
||||
const [graphView, setGraphView] = useState<GraphView>('both');
|
||||
const [resetting, setResetting] = useState(false);
|
||||
|
||||
// Live values
|
||||
const [liveHpFreq, setLiveHpFreq] = useState<number>(FREQUENCY_MIN);
|
||||
const [liveLpFreq, setLiveLpFreq] = useState<number>(FREQUENCY_MAX);
|
||||
const [livePeq, setLivePeq] = useState<Partial<Record<PEQBand, PEQValue>>>({});
|
||||
const [livePeq, setLivePeq] = useState<Partial<Record<PEQBand, PEQ>>>({});
|
||||
|
||||
// Forces one extra render after the graph mounts so marker positions
|
||||
// (which read from the graph's ref) are available on first paint.
|
||||
const [, forceRender] = useState(0);
|
||||
const magnitudeGraphRef = useRef<LinearGraphHandle>(null);
|
||||
|
||||
// DSP values
|
||||
const activeInputState = dsp.state.current_config.input_states[channel];
|
||||
@@ -395,6 +525,24 @@ function InputSection({
|
||||
const ActiveLowPassFilter = activeInputState.crossover.low_pass;
|
||||
const ActivePeq = activeInputState.peq_chain;
|
||||
|
||||
const handleResetPeq = async () => {
|
||||
if (resetting) return;
|
||||
setResetting(true);
|
||||
|
||||
for (const band of PEQ_BANDS_ORDER) {
|
||||
const current = displayPeq[band];
|
||||
if (current.gain === 0 || current.filter_type === PEQFilter.Peak) continue; // skip bands already flat
|
||||
|
||||
await commitPEQ(band, 0, current.frequency, current.q, PEQFilter.Peak, current.bypass);
|
||||
}
|
||||
|
||||
setResetting(false);
|
||||
};
|
||||
|
||||
const handleBypassClick = () => {
|
||||
setPeqBypass({ Input: channel }, !ActivePeq.bypass);
|
||||
};
|
||||
|
||||
// Live handlers
|
||||
const handleLiveHighPassValue = (value: number) => {
|
||||
setLiveHpFreq(value);
|
||||
@@ -404,7 +552,7 @@ function InputSection({
|
||||
setLiveLpFreq(value);
|
||||
};
|
||||
|
||||
const handleLivePEQValue = (band: PEQBand, value: PEQValue) => {
|
||||
const handleLivePEQValue = (band: PEQBand, value: PEQ) => {
|
||||
setLivePeq((prev) => ({
|
||||
...prev,
|
||||
[band]: value,
|
||||
@@ -439,34 +587,86 @@ function InputSection({
|
||||
const success = await setPeq({ Input: channel }, band, gain, frequency, q, filter_type, bypass);
|
||||
if (success) {
|
||||
setLivePeq((prev) => {
|
||||
const channelLive = prev[band];
|
||||
if (!channelLive || !(band in channelLive)) return prev;
|
||||
const next = { ...channelLive };
|
||||
return { ...prev, [channel]: next };
|
||||
const next = { ...prev };
|
||||
delete next[band];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
return success;
|
||||
};
|
||||
|
||||
// Diplay value (merge live and real)
|
||||
const displayPeq: Record<PEQBand, PEQ> = {
|
||||
...ActivePeq.bands,
|
||||
...livePeq,
|
||||
};
|
||||
|
||||
const highPassSpec = { frequency: liveHpFreq, filter_type: ActiveHighPassFilter.slope };
|
||||
const lowPassSpec = { frequency: liveLpFreq, filter_type: ActiveLowPassFilter.slope };
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="input-graph-section">
|
||||
<div className="input-graph-panel">
|
||||
<div className="input-graph__title">{activeInputState.name} - Filters</div>
|
||||
<LinearGraph
|
||||
xMin={GRAPH_X_MIN}
|
||||
xMax={GRAPH_X_MAX}
|
||||
xScale="log"
|
||||
yMin={GRAPH_Y_MIN}
|
||||
yMax={GRAPH_Y_MAX}
|
||||
yStep={3}
|
||||
xLabel="Hz"
|
||||
yLabel="dB"
|
||||
showGrid={true}
|
||||
formatX={(v) => Math.round(v).toString()}
|
||||
formatY={(v) => Math.round(v).toString()}
|
||||
draw={() => {}}
|
||||
/>
|
||||
<div className="input-graph__title-row">
|
||||
<div className="input-graph__title">{activeInputState.name} - Filters</div>
|
||||
<GraphViewToggle view={graphView} onChange={setGraphView} />
|
||||
</div>
|
||||
|
||||
{(graphView === 'both' || graphView === 'magnitude') && (
|
||||
<div className="input-graph-panel__magnitude-wrap">
|
||||
<LinearGraph
|
||||
ref={magnitudeGraphRef}
|
||||
xMin={GRAPH_X_MIN}
|
||||
xMax={GRAPH_X_MAX}
|
||||
xScale="log"
|
||||
yMin={GRAPH_Y_MIN}
|
||||
yMax={GRAPH_Y_MAX}
|
||||
yStep={3}
|
||||
xLabel="Hz"
|
||||
yLabel="dB"
|
||||
showGrid={true}
|
||||
formatX={(v) => Math.round(v).toString()}
|
||||
formatY={(v) => Math.round(v).toString()}
|
||||
draw={buildTotalCurveDraw({
|
||||
bands: displayPeq,
|
||||
highPass: highPassSpec,
|
||||
lowPass: lowPassSpec,
|
||||
xMin: GRAPH_X_MIN,
|
||||
xMax: GRAPH_X_MAX,
|
||||
yMin: GRAPH_Y_MIN,
|
||||
yMax: GRAPH_Y_MAX,
|
||||
dimmed: ActivePeq.bypass,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(graphView === 'both' || graphView === 'phase') && (
|
||||
<LinearGraph
|
||||
xMin={GRAPH_X_MIN}
|
||||
xMax={GRAPH_X_MAX}
|
||||
xScale="log"
|
||||
yMin={-180}
|
||||
yMax={180}
|
||||
yStep={45}
|
||||
xLabel="Hz"
|
||||
yLabel="°"
|
||||
showGrid={true}
|
||||
formatX={(v) => Math.round(v).toString()}
|
||||
formatY={(v) => Math.round(v).toString()}
|
||||
draw={buildTotalPhaseCurveDraw({
|
||||
bands: displayPeq,
|
||||
highPass: highPassSpec,
|
||||
lowPass: lowPassSpec,
|
||||
xMin: GRAPH_X_MIN,
|
||||
xMax: GRAPH_X_MAX,
|
||||
yMin: -180,
|
||||
yMax: 180,
|
||||
dimmed: ActivePeq.bypass,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -474,27 +674,42 @@ function InputSection({
|
||||
<div className="input-control-panel">
|
||||
<ChannelGroup title="PEQ">
|
||||
<PeqBandList
|
||||
channel={channel}
|
||||
dsp={dsp}
|
||||
bands={ActivePeq.bands}
|
||||
liveBands={livePeq}
|
||||
activeBand={activeBand}
|
||||
setActiveBand={setActiveBand}
|
||||
setPEQ={setPeq}
|
||||
setPEQ={commitPEQ}
|
||||
/>
|
||||
<PeqPanelSliders
|
||||
peq={ActivePeq.bands[activeBand]}
|
||||
setPEQ={(gain, frequency, q, filter_type, bypass) =>
|
||||
commitPEQ(activeBand, gain, frequency, q, filter_type, bypass)
|
||||
}
|
||||
onLiveValue={(value) => handleLivePEQValue(activeBand, value)}
|
||||
/>
|
||||
<PeqPanelSliders channel={channel} dsp={dsp} band={activeBand} setPEQ={setPeq} />
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="CrossOver">
|
||||
<CrossoverPanel
|
||||
crossover={ActiveHighPassFilter}
|
||||
title="HighPass"
|
||||
setCrossover={commitHighPass}
|
||||
onLiveValue={handleLiveHighPassValue}
|
||||
/>
|
||||
<CrossoverPanel
|
||||
crossover={ActiveLowPassFilter}
|
||||
title="LowPass"
|
||||
setCrossover={commitLowPass}
|
||||
onLiveValue={handleLiveLowPassValue}
|
||||
/>
|
||||
<CrossoverPanel
|
||||
crossover={ActiveHighPassFilter}
|
||||
title="HighPass"
|
||||
setCrossover={commitHighPass}
|
||||
onLiveValue={handleLiveHighPassValue}
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Actions">
|
||||
<InputActions
|
||||
bypass={ActivePeq.bypass}
|
||||
resetting={resetting}
|
||||
onBypass={handleBypassClick}
|
||||
onReset={handleResetPeq}
|
||||
/>
|
||||
</ChannelGroup>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user