753 lines
22 KiB
TypeScript
753 lines
22 KiB
TypeScript
// InputSection.tsx
|
|
import VerticalSlider, {
|
|
makeLogStep,
|
|
makeLogQuantize,
|
|
} from '../../../../components/VerticalSlider/VerticalSlider';
|
|
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, 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 { buildTotalCurveDraw, buildTotalPhaseCurveDraw } from '../../utils/totalCurve';
|
|
import {
|
|
FREQUENCY_MAX,
|
|
FREQUENCY_MIN,
|
|
FREQUENCY_QUANTIZATION,
|
|
GRAPH_X_MAX,
|
|
GRAPH_X_MIN,
|
|
GRAPH_Y_MAX,
|
|
GRAPH_Y_MIN,
|
|
} from '../../constants';
|
|
import { drawCurves } from '../../utils/graph/draw';
|
|
import { getPeqCurvePoints, getPeqPhasePoints } from '../../utils/graph/peq';
|
|
import { logSpace, sumCurvePoints, sumPhaseCurvePoints } from '../../utils/graph/curve';
|
|
import { getCrossoverCurvePoints, getCrossoverPhasePoints } from '../../utils/graph/crossover';
|
|
|
|
type GraphView = 'both' | 'magnitude' | 'phase';
|
|
|
|
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' },
|
|
];
|
|
|
|
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>
|
|
);
|
|
}
|
|
|
|
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({
|
|
peq,
|
|
setPEQ,
|
|
onLiveValue,
|
|
}: {
|
|
peq: PEQ;
|
|
setPEQ: (
|
|
gainDb: number,
|
|
freqHz: number,
|
|
q: number,
|
|
filterType: PEQFilter,
|
|
bypass: boolean
|
|
) => Promise<boolean>;
|
|
onLiveValue: (value: PEQ) => void;
|
|
}) {
|
|
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
|
|
|
const commit = (overrides: Partial<PEQ>) => {
|
|
const next = { ...peq, ...overrides };
|
|
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__grid">
|
|
<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>
|
|
);
|
|
}
|
|
|
|
function CrossoverPanel({
|
|
crossover,
|
|
title,
|
|
setCrossover,
|
|
onLiveValue,
|
|
}: {
|
|
crossover: Crossover;
|
|
title: string;
|
|
setCrossover: (frequency: number, filter_type: CrossoverFilter) => Promise<boolean>;
|
|
onLiveValue: (value: number) => void;
|
|
}) {
|
|
const frequency = crossover.frequency;
|
|
const filter_type = crossover.slope;
|
|
const bypass = filter_type === CrossoverFilter.Bypass;
|
|
|
|
const lastFilterType = useRef<CrossoverFilter>(
|
|
filter_type !== CrossoverFilter.Bypass ? filter_type : CrossoverFilter.Lk48
|
|
);
|
|
|
|
const commit = async (overrides: {
|
|
frequency?: number;
|
|
filter_type?: CrossoverFilter;
|
|
bypass?: boolean;
|
|
}) => {
|
|
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 (
|
|
<div className="crossover-panel">
|
|
<div className="crossover-panel__title">{title}</div>
|
|
|
|
<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')}
|
|
value={frequency}
|
|
formatValue={(value) =>
|
|
value >= 1000 ? `${(value / 1000).toFixed(2)}` : `${value.toFixed(1)}`
|
|
}
|
|
onChange={(value) => commit({ frequency: value })}
|
|
onInput={(value) => onLiveValue(value)}
|
|
style={{ width: '100%', flex: '1 1 auto' }}
|
|
/>
|
|
|
|
<select
|
|
className="crossover-panel__select"
|
|
value={bypass ? lastFilterType.current : filter_type}
|
|
onChange={(e) => commit({ filter_type: e.target.value as CrossoverFilter })}
|
|
>
|
|
{CROSSOVER_FILTER_LABEL.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
<Button
|
|
variant="toggle"
|
|
className={bypass ? 'active-warning' : ''}
|
|
onClick={() => commit({ bypass: !bypass })}
|
|
style={{
|
|
width: '85%',
|
|
paddingLeft: 0,
|
|
paddingRight: 0,
|
|
fontSize: '4cqh',
|
|
paddingTop: '2cqh',
|
|
paddingBottom: '2cqh',
|
|
minHeight: 0,
|
|
}}
|
|
>
|
|
{bypass ? 'Bypassed' : 'Bypass'}
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PeqBandList({
|
|
bands,
|
|
liveBands,
|
|
activeBand,
|
|
setActiveBand,
|
|
setPEQ,
|
|
}: {
|
|
bands: Record<PEQBand, PEQ>;
|
|
liveBands: Partial<Record<PEQBand, PEQ>>;
|
|
activeBand: PEQBand;
|
|
setActiveBand: (band: PEQBand) => void;
|
|
setPEQ: (
|
|
band: PEQBand,
|
|
gainDb: number,
|
|
freqHz: number,
|
|
q: number,
|
|
filterType: PEQFilter,
|
|
bypass: boolean
|
|
) => Promise<boolean>;
|
|
}) {
|
|
const commit = (band: PEQBand, overrides: Partial<PEQ>) => {
|
|
const peq = liveBands[band] ?? bands[band];
|
|
const next = { ...peq, ...overrides };
|
|
return setPEQ(band, next.gain, next.frequency, next.q, next.filter_type, next.bypass);
|
|
};
|
|
|
|
return (
|
|
<div className="peq-band-list">
|
|
<div className="peq-band-list__row peq-band-list__row--header">
|
|
<div className="peq-band-list__cell">Band</div>
|
|
<div className="peq-band-list__cell">Freq</div>
|
|
<div className="peq-band-list__cell">Q</div>
|
|
<div className="peq-band-list__cell">Gain</div>
|
|
<div className="peq-band-list__cell">Type</div>
|
|
<div className="peq-band-list__cell">Bypass</div>
|
|
</div>
|
|
|
|
{PEQ_BANDS_ORDER.map((band, i) => {
|
|
const peq = liveBands[band] ?? bands[band];
|
|
const isActive = band === activeBand;
|
|
|
|
return (
|
|
<div
|
|
className={`peq-band-list__row${isActive ? ' peq-band-list__row--active' : ''}`}
|
|
key={band}
|
|
>
|
|
<Button
|
|
variant="toggle"
|
|
className={isActive ? 'active' : ''}
|
|
onClick={() => setActiveBand(band)}
|
|
style={{ minHeight: 0 }}
|
|
>
|
|
{`B${i + 1}`}
|
|
</Button>
|
|
|
|
{/* Frequency */}
|
|
<input
|
|
className="peq-band-list__input"
|
|
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
|
|
className="peq-band-list__input"
|
|
type="number"
|
|
step={1}
|
|
min={1}
|
|
max={999}
|
|
value={peq.q}
|
|
onChange={(e) => commit(band, { q: Number(e.target.value) })}
|
|
/>
|
|
|
|
<input
|
|
className="peq-band-list__input"
|
|
type="number"
|
|
step={0.1}
|
|
min={-12}
|
|
max={12}
|
|
value={peq.gain}
|
|
onChange={(e) => commit(band, { gain: Number(e.target.value) })}
|
|
/>
|
|
|
|
<select
|
|
className="peq-band-list__select"
|
|
value={peq.filter_type}
|
|
onChange={(e) => commit(band, { filter_type: e.target.value as PEQFilter, q: 3.0 })}
|
|
>
|
|
{PEQ_FILTERS_LABEL.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
|
|
<Button
|
|
variant="toggle"
|
|
className={peq.bypass ? 'active-warning' : ''}
|
|
onClick={() => commit(band, { bypass: !peq.bypass })}
|
|
style={{ minHeight: 0 }}
|
|
>
|
|
{peq.bypass ? 'Bypassed' : 'Bypass'}
|
|
</Button>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type SetFilterFn = (
|
|
channel: Channel,
|
|
freq: number,
|
|
filterType: CrossoverFilter
|
|
) => 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,
|
|
channel,
|
|
setHighPass,
|
|
setLowPass,
|
|
setPeq,
|
|
setPeqBypass,
|
|
}: {
|
|
dsp: DSP;
|
|
channel: InputChannel;
|
|
setHighPass: SetFilterFn;
|
|
setLowPass: SetFilterFn;
|
|
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, PEQ>>>({});
|
|
|
|
const magnitudeGraphRef = useRef<LinearGraphHandle>(null);
|
|
|
|
// DSP values
|
|
const activeInputState = dsp.state.current_config.input_states[channel];
|
|
const ActiveHighPassFilter = activeInputState.crossover.high_pass;
|
|
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);
|
|
};
|
|
|
|
const handleLiveLowPassValue = (value: number) => {
|
|
setLiveLpFreq(value);
|
|
};
|
|
|
|
const handleLivePEQValue = (band: PEQBand, value: PEQ) => {
|
|
setLivePeq((prev) => ({
|
|
...prev,
|
|
[band]: value,
|
|
}));
|
|
};
|
|
|
|
// Real dsp value handlers
|
|
const commitHighPass = async (freq: number, filterType: CrossoverFilter) => {
|
|
const success = await setHighPass({ Input: channel }, freq, filterType);
|
|
if (success) {
|
|
setLiveHpFreq(freq);
|
|
}
|
|
return success;
|
|
};
|
|
|
|
const commitLowPass = async (freq: number, filterType: CrossoverFilter) => {
|
|
const success = await setLowPass({ Input: channel }, freq, filterType);
|
|
if (success) {
|
|
setLiveLpFreq(freq);
|
|
}
|
|
return success;
|
|
};
|
|
|
|
const commitPEQ = async (
|
|
band: PEQBand,
|
|
gain: number,
|
|
frequency: number,
|
|
q: number,
|
|
filter_type: PEQFilter,
|
|
bypass: boolean
|
|
) => {
|
|
const success = await setPeq({ Input: channel }, band, gain, frequency, q, filter_type, bypass);
|
|
if (success) {
|
|
setLivePeq((prev) => {
|
|
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 };
|
|
|
|
const freqs = logSpace(GRAPH_X_MIN, GRAPH_X_MAX, 1000);
|
|
|
|
return (
|
|
<>
|
|
<div className="input-graph-section">
|
|
<div className="input-graph-panel">
|
|
<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={({ ctx, toPx }) => {
|
|
drawCurves({
|
|
ctx,
|
|
toPx,
|
|
|
|
xMin: GRAPH_X_MIN,
|
|
xMax: GRAPH_X_MAX,
|
|
yMin: GRAPH_Y_MIN,
|
|
yMax: GRAPH_Y_MAX,
|
|
curves: [
|
|
{
|
|
points: sumCurvePoints([
|
|
getPeqCurvePoints({ freqs, bands: displayPeq }),
|
|
getCrossoverCurvePoints({
|
|
freqs,
|
|
highPass: highPassSpec,
|
|
lowPass: lowPassSpec,
|
|
}),
|
|
]),
|
|
style: { strokeColor: '#e5e7eb', 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={({ ctx, toPx }) => {
|
|
drawCurves({
|
|
ctx,
|
|
toPx,
|
|
|
|
xMin: GRAPH_X_MIN,
|
|
xMax: GRAPH_X_MAX,
|
|
yMin: GRAPH_Y_MIN,
|
|
yMax: GRAPH_Y_MAX,
|
|
curves: [
|
|
{
|
|
points: sumPhaseCurvePoints([
|
|
getPeqPhasePoints({ freqs, bands: displayPeq }),
|
|
getCrossoverPhasePoints({
|
|
freqs,
|
|
highPass: highPassSpec,
|
|
lowPass: lowPassSpec,
|
|
}),
|
|
]),
|
|
style: { strokeColor: '#e5e7eb', dimmed: ActivePeq.bypass },
|
|
},
|
|
],
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="input-control-section">
|
|
<div className="input-control-panel">
|
|
<ChannelGroup title="PEQ">
|
|
<PeqBandList
|
|
bands={ActivePeq.bands}
|
|
liveBands={livePeq}
|
|
activeBand={activeBand}
|
|
setActiveBand={setActiveBand}
|
|
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)}
|
|
/>
|
|
</ChannelGroup>
|
|
|
|
<ChannelGroup title="CrossOver">
|
|
<CrossoverPanel
|
|
crossover={ActiveHighPassFilter}
|
|
title="HighPass"
|
|
setCrossover={commitHighPass}
|
|
onLiveValue={handleLiveHighPassValue}
|
|
/>
|
|
<CrossoverPanel
|
|
crossover={ActiveLowPassFilter}
|
|
title="LowPass"
|
|
setCrossover={commitLowPass}
|
|
onLiveValue={handleLiveLowPassValue}
|
|
/>
|
|
</ChannelGroup>
|
|
|
|
<ChannelGroup title="Actions">
|
|
<InputActions
|
|
bypass={ActivePeq.bypass}
|
|
resetting={resetting}
|
|
onBypass={handleBypassClick}
|
|
onReset={handleResetPeq}
|
|
/>
|
|
</ChannelGroup>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default InputSection;
|