feat: add peq output pages
This commit is contained in:
@@ -47,6 +47,8 @@
|
||||
bottom: 0;
|
||||
|
||||
width: 4cqh;
|
||||
padding-top: 3%;
|
||||
padding-bottom: 3%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -11,7 +11,7 @@ import LoadingOverlay from '../../../../components/LoadingOverlay/LoadingOverlay
|
||||
import { InputChannel, OutputChannel } from '../../types';
|
||||
import MatrixSection from '../Matrixsection/Matrixsection';
|
||||
import GeqSection from '../GeqSection/GeqSection';
|
||||
import InputSection from '../InputSection/InputSection';
|
||||
import ChannelSection from '../InputSection/ChannelSection';
|
||||
import { DSP408Api } from '../../dsp408';
|
||||
import { DSP408 } from '../../../../types';
|
||||
import { SidebarId } from '../../constants';
|
||||
@@ -134,54 +134,6 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
||||
case 'delay':
|
||||
return <DelaySection dsp={dsp} setDelay={api.setDelay} setDelayUnit={api.setDelayUnit} />;
|
||||
|
||||
case 'ina':
|
||||
return (
|
||||
<InputSection
|
||||
dsp={dsp}
|
||||
channel={InputChannel.InA}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'inb':
|
||||
return (
|
||||
<InputSection
|
||||
dsp={dsp}
|
||||
channel={InputChannel.InB}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'inc':
|
||||
return (
|
||||
<InputSection
|
||||
dsp={dsp}
|
||||
channel={InputChannel.InC}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'ind':
|
||||
return (
|
||||
<InputSection
|
||||
dsp={dsp}
|
||||
channel={InputChannel.InD}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'matrix':
|
||||
return (
|
||||
<MatrixSection
|
||||
@@ -195,6 +147,66 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
||||
case 'geq':
|
||||
return <GeqSection dsp={dsp} setGeqBand={api.setGeqBand} setGeqBypass={api.setGeqBypass} />;
|
||||
|
||||
case 'ina':
|
||||
return (
|
||||
<ChannelSection
|
||||
dsp={dsp}
|
||||
channel={{Input: InputChannel.InA}}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'inb':
|
||||
return (
|
||||
<ChannelSection
|
||||
dsp={dsp}
|
||||
channel={{Input: InputChannel.InB}}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'inc':
|
||||
return (
|
||||
<ChannelSection
|
||||
dsp={dsp}
|
||||
channel={{Input: InputChannel.InC}}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'ind':
|
||||
return (
|
||||
<ChannelSection
|
||||
dsp={dsp}
|
||||
channel={{Input: InputChannel.InD}}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'out1':
|
||||
return (
|
||||
<ChannelSection
|
||||
dsp={dsp}
|
||||
channel={{Output: OutputChannel.Out1}}
|
||||
setLowPass={api.setLowPass}
|
||||
setHighPass={api.setHighPass}
|
||||
setPeq={api.setPeqBand}
|
||||
setPeqBypass={api.setPEQBypass}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
+19
-19
@@ -1,5 +1,5 @@
|
||||
/* InputSection.css */
|
||||
.input-graph-section {
|
||||
.channel-graph-section {
|
||||
flex: 0 0 calc(100% - var(--dsp-control-heigth));
|
||||
min-height: 0;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input-graph-panel {
|
||||
.channel-graph-panel {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
@@ -28,7 +28,7 @@
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.input-bypass-overlay {
|
||||
.channel-bypass-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
@@ -38,7 +38,7 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.input-bypass-overlay span {
|
||||
.channel-bypass-overlay span {
|
||||
font-size: 8cqh;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.15em;
|
||||
@@ -49,7 +49,7 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.input-graph__title {
|
||||
.channel-graph__title {
|
||||
flex: 0 0 auto;
|
||||
|
||||
text-align: center;
|
||||
@@ -60,7 +60,7 @@
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.input-graph-panel > .container {
|
||||
.channel-graph-panel > .container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
@@ -68,7 +68,7 @@
|
||||
/* Bottom control bar — same pattern as .compressor-section: absolute,
|
||||
bottom-anchored, panels flow in a row and scroll horizontally instead
|
||||
of squeezing into a fixed max-width. */
|
||||
.input-control-section {
|
||||
.channel-control-section {
|
||||
container-type: size;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -93,7 +93,7 @@
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input-control-panel {
|
||||
.channel-control-panel {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
height: 100%;
|
||||
@@ -106,7 +106,7 @@
|
||||
}
|
||||
|
||||
/* Peq Sliders */
|
||||
.input-panel {
|
||||
.channel-panel {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
background: var(--bg-void);
|
||||
}
|
||||
|
||||
.input-panel__title {
|
||||
.channel-panel__title {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@@ -140,7 +140,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.input-panel__grid {
|
||||
.channel-panel__grid {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-panel__cell {
|
||||
.channel-panel__cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -164,7 +164,7 @@
|
||||
gap: 1cqh;
|
||||
}
|
||||
|
||||
.input-panel__cell-label {
|
||||
.channel-panel__cell-label {
|
||||
flex: 0 0 auto;
|
||||
font-family: var(--font-ui);
|
||||
font-size: 3.2cqh;
|
||||
@@ -337,14 +337,14 @@
|
||||
font-size: 4cqh;
|
||||
}
|
||||
|
||||
.input-graph-panel__magnitude-wrap {
|
||||
.channel-graph-panel__magnitude-wrap {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-graph__title-row {
|
||||
.channel-graph__title-row {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -352,11 +352,11 @@
|
||||
padding: 1cqh 1.5cqh;
|
||||
}
|
||||
|
||||
.input-graph__title-row .input-graph__title {
|
||||
.channel-graph__title-row .channel-graph__title {
|
||||
padding: 0; /* was on the standalone title element; row now owns spacing */
|
||||
}
|
||||
|
||||
.input-graph-view-toggle {
|
||||
.channel-graph-view-toggle {
|
||||
display: flex;
|
||||
gap: 0.4cqh;
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
@@ -365,7 +365,7 @@
|
||||
background: var(--bg-void);
|
||||
}
|
||||
|
||||
.input-graph-view-toggle__btn {
|
||||
.channel-graph-view-toggle__btn {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2cqh;
|
||||
padding: 0.6cqh 1.2cqh;
|
||||
@@ -376,7 +376,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-graph-view-toggle__btn.active {
|
||||
.channel-graph-view-toggle__btn.active {
|
||||
background: var(--accent-brand);
|
||||
color: var(--bg-void);
|
||||
}
|
||||
+48
-37
@@ -1,4 +1,4 @@
|
||||
// InputSection.tsx
|
||||
// ChannelSection.tsx
|
||||
import VerticalSlider, {
|
||||
makeLogStep,
|
||||
makeLogQuantize,
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
PEQ_Q_RANGES,
|
||||
PEAK_Q_VALUES,
|
||||
} from '../../constants';
|
||||
import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Crossover } from '../../types';
|
||||
import { 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 './ChannelSection.css';
|
||||
import ChannelGroup from '../../../../components/ChannelGroup/ChannelGroup';
|
||||
import Button from '../../../../components/Button/Button';
|
||||
import {
|
||||
@@ -50,12 +50,12 @@ function GraphViewToggle({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="input-graph-view-toggle">
|
||||
<div className="channel-graph-view-toggle">
|
||||
{options.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
className={`input-graph-view-toggle__btn${view === opt.value ? ' active' : ''}`}
|
||||
className={`channel-graph-view-toggle__btn${view === opt.value ? ' active' : ''}`}
|
||||
onClick={() => onChange(opt.value)}
|
||||
>
|
||||
{opt.label}
|
||||
@@ -65,7 +65,7 @@ function GraphViewToggle({
|
||||
);
|
||||
}
|
||||
|
||||
function InputActions({
|
||||
function ChannelActions({
|
||||
bypass,
|
||||
resetting,
|
||||
onBypass,
|
||||
@@ -135,11 +135,11 @@ function PeqPanelSliders({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="input-panel">
|
||||
<div className="input-panel__title">PEQ Parameters</div>
|
||||
<div className="channel-panel">
|
||||
<div className="channel-panel__title">PEQ Parameters</div>
|
||||
|
||||
<div className="input-panel__grid">
|
||||
<div className="input-panel__cell input-panel__cell--frequency">
|
||||
<div className="channel-panel__grid">
|
||||
<div className="channel-panel__cell channel-panel__cell--frequency">
|
||||
<VerticalSlider
|
||||
min={FREQUENCY_MIN}
|
||||
max={FREQUENCY_MAX}
|
||||
@@ -156,10 +156,10 @@ function PeqPanelSliders({
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Frequency</div>
|
||||
<div className="channel-panel__cell-label">Frequency</div>
|
||||
</div>
|
||||
|
||||
<div className="input-panel__cell input-panel__cell--q">
|
||||
<div className="channel-panel__cell channel-panel__cell--q">
|
||||
<VerticalSlider
|
||||
min={qMin}
|
||||
max={qMax}
|
||||
@@ -171,10 +171,10 @@ function PeqPanelSliders({
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Q</div>
|
||||
<div className="channel-panel__cell-label">Q</div>
|
||||
</div>
|
||||
|
||||
<div className="input-panel__cell input-panel__cell--gain">
|
||||
<div className="channel-panel__cell channel-panel__cell--gain">
|
||||
<VerticalSlider
|
||||
min={-12}
|
||||
max={12}
|
||||
@@ -187,7 +187,7 @@ function PeqPanelSliders({
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<div className="input-panel__cell-label">Gain</div>
|
||||
<div className="channel-panel__cell-label">Gain</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -295,6 +295,7 @@ function PeqBandList({
|
||||
activeBand,
|
||||
setActiveBand,
|
||||
setPEQ,
|
||||
bandsOrder,
|
||||
}: {
|
||||
bands: Record<PEQBand, PEQ>;
|
||||
liveBands: Partial<Record<PEQBand, PEQ>>;
|
||||
@@ -308,6 +309,7 @@ function PeqBandList({
|
||||
filterType: PEQFilter,
|
||||
bypass: boolean
|
||||
) => Promise<boolean>;
|
||||
bandsOrder: PEQBand[];
|
||||
}) {
|
||||
const commit = (band: PEQBand, overrides: Partial<PEQ>) => {
|
||||
const peq = liveBands[band] ?? bands[band];
|
||||
@@ -326,7 +328,7 @@ function PeqBandList({
|
||||
<div className="peq-band-list__cell">Bypass</div>
|
||||
</div>
|
||||
|
||||
{PEQ_BANDS_ORDER.map((band, i) => {
|
||||
{bandsOrder.map((band, i) => {
|
||||
const peq = liveBands[band] ?? bands[band];
|
||||
const isActive = band === activeBand;
|
||||
|
||||
@@ -412,7 +414,7 @@ type SetFilterFn = (
|
||||
filterType: CrossoverFilter
|
||||
) => Promise<boolean>;
|
||||
|
||||
function InputSection({
|
||||
function ChannelSection({
|
||||
dsp,
|
||||
channel,
|
||||
setHighPass,
|
||||
@@ -421,7 +423,7 @@ function InputSection({
|
||||
setPeqBypass,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
channel: InputChannel;
|
||||
channel: Channel;
|
||||
setHighPass: SetFilterFn;
|
||||
setLowPass: SetFilterFn;
|
||||
setPeq: (
|
||||
@@ -448,16 +450,24 @@ function InputSection({
|
||||
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 isInput = 'Input' in channel;
|
||||
|
||||
const activeChannelState = isInput
|
||||
? dsp.state.current_config.input_states[channel.Input]
|
||||
: dsp.state.current_config.output_states[channel.Output];
|
||||
const ActiveHighPassFilter = activeChannelState.crossover.high_pass;
|
||||
const ActiveLowPassFilter = activeChannelState.crossover.low_pass;
|
||||
const ActivePeq = activeChannelState.peq_chain;
|
||||
|
||||
const bandsOrder = isInput
|
||||
? PEQ_BANDS_ORDER.slice(0, -1)
|
||||
: PEQ_BANDS_ORDER;
|
||||
|
||||
const handleResetPeq = async () => {
|
||||
if (resetting) return;
|
||||
setResetting(true);
|
||||
|
||||
for (const band of PEQ_BANDS_ORDER) {
|
||||
for (const band of bandsOrder) {
|
||||
const current = displayPeq[band];
|
||||
// Skip only if already flat — nothing to change.
|
||||
if (current.gain === 0 && current.filter_type === PEQFilter.Peak) continue;
|
||||
@@ -469,7 +479,7 @@ function InputSection({
|
||||
};
|
||||
|
||||
const handleBypassClick = () => {
|
||||
setPeqBypass({ Input: channel }, !ActivePeq.bypass);
|
||||
setPeqBypass(channel, !ActivePeq.bypass);
|
||||
};
|
||||
|
||||
// Live handlers
|
||||
@@ -490,7 +500,7 @@ function InputSection({
|
||||
|
||||
// Real dsp value handlers
|
||||
const commitHighPass = async (freq: number, filterType: CrossoverFilter) => {
|
||||
const success = await setHighPass({ Input: channel }, freq, filterType);
|
||||
const success = await setHighPass(channel, freq, filterType);
|
||||
if (success) {
|
||||
setLiveHpFreq(freq);
|
||||
}
|
||||
@@ -498,7 +508,7 @@ function InputSection({
|
||||
};
|
||||
|
||||
const commitLowPass = async (freq: number, filterType: CrossoverFilter) => {
|
||||
const success = await setLowPass({ Input: channel }, freq, filterType);
|
||||
const success = await setLowPass(channel, freq, filterType);
|
||||
if (success) {
|
||||
setLiveLpFreq(freq);
|
||||
}
|
||||
@@ -513,7 +523,7 @@ function InputSection({
|
||||
filter_type: PEQFilter,
|
||||
bypass: boolean
|
||||
) => {
|
||||
const success = await setPeq({ Input: channel }, band, gain, frequency, q, filter_type, bypass);
|
||||
const success = await setPeq(channel, band, gain, frequency, q, filter_type, bypass);
|
||||
if (success) {
|
||||
setLivePeq((prev) => {
|
||||
const next = { ...prev };
|
||||
@@ -537,15 +547,15 @@ function InputSection({
|
||||
|
||||
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>
|
||||
<div className="channel-graph-section">
|
||||
<div className="channel-graph-panel">
|
||||
<div className="channel-graph__title-row">
|
||||
<div className="channel-graph__title">{activeChannelState.name} - Filters</div>
|
||||
<GraphViewToggle view={graphView} onChange={setGraphView} />
|
||||
</div>
|
||||
|
||||
{(graphView === 'both' || graphView === 'magnitude') && (
|
||||
<div className="input-graph-panel__magnitude-wrap">
|
||||
<div className="channel-graph-panel__magnitude-wrap">
|
||||
<LinearGraph
|
||||
ref={magnitudeGraphRef}
|
||||
xMin={GRAPH_X_MIN}
|
||||
@@ -626,15 +636,15 @@ function InputSection({
|
||||
)}
|
||||
|
||||
{ActivePeq.bypass && (
|
||||
<div className="input-bypass-overlay">
|
||||
<div className="channel-bypass-overlay">
|
||||
<span>BYPASSED</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="input-control-section">
|
||||
<div className="input-control-panel">
|
||||
<div className="channel-control-section">
|
||||
<div className="channel-control-panel">
|
||||
<ChannelGroup title="PEQ">
|
||||
<PeqBandList
|
||||
bands={ActivePeq.bands}
|
||||
@@ -642,6 +652,7 @@ function InputSection({
|
||||
activeBand={activeBand}
|
||||
setActiveBand={setActiveBand}
|
||||
setPEQ={commitPEQ}
|
||||
bandsOrder={bandsOrder}
|
||||
/>
|
||||
<PeqPanelSliders
|
||||
peq={ActivePeq.bands[activeBand]}
|
||||
@@ -668,7 +679,7 @@ function InputSection({
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Actions">
|
||||
<InputActions
|
||||
<ChannelActions
|
||||
bypass={ActivePeq.bypass}
|
||||
resetting={resetting}
|
||||
onBypass={handleBypassClick}
|
||||
@@ -681,4 +692,4 @@ function InputSection({
|
||||
);
|
||||
}
|
||||
|
||||
export default InputSection;
|
||||
export default ChannelSection;
|
||||
Reference in New Issue
Block a user