feat: start input section

This commit is contained in:
2026-08-12 22:19:09 +02:00
parent 00adc4df53
commit a28dd890dd
6 changed files with 395 additions and 183 deletions
@@ -139,12 +139,10 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
<InputSection
dsp={dsp}
channel={InputChannel.InA}
set_low_pass={api.setLowPass}
set_high_pass={api.setHighPass}
setLowPass={api.setLowPass}
setHighPass={api.setHighPass}
setPeq={api.setPeqBand}
setGain={api.setGain}
setMute={api.setMute}
setInverse={api.setInverse}
setPeqBypass={api.setPEQBypass}
/>
);
@@ -69,6 +69,7 @@
display: flex;
align-items: stretch;
justify-content: center; /* ← add this */
gap: var(--space-4);
box-sizing: border-box;
@@ -219,7 +220,7 @@
.input-panel__cell-label {
flex: 0 0 auto;
font-family: var(--font-ui);
font-size: 2.5cqh;
font-size: 3.2cqh;
color: var(--text-muted);
white-space: nowrap;
}
@@ -271,72 +272,92 @@
border-radius: var(--radius-sm);
}
.gain-meter-panel {
container-type: size;
position: relative;
height: 100%;
aspect-ratio: 1 / 2;
flex: 0 0 auto;
.peq-band-list {
flex: 1 1 auto;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5cqh;
border: var(--border-width) solid var(--border-hairline);
border-radius: var(--radius-md);
box-sizing: border-box;
background: var(--bg-void);
padding: 1cqh;
overflow: hidden;
aspect-ratio: 2;
}
.gain-meter-panel__title {
.peq-band-list__row {
flex: 1 1 0;
min-height: 0;
min-width: 0;
display: grid;
grid-template-columns: 1fr 1.2fr 1fr 1fr 1.4fr 1.2fr;
align-items: stretch;
gap: 1cqh;
box-sizing: border-box;
}
.peq-band-list__row--header {
flex: 0 0 auto;
width: 100%;
text-align: center;
font-family: var(--font-ui);
font-size: 4cqh;
color: var(--text-primary);
text-align: center;
font-weight: var(--font-weight-semibold);
padding-top: 2cqh;
padding-bottom: 2cqh;
border-bottom: var(--border-width) solid var(--border-hairline);
padding-top: 1cqh;
padding-bottom: 1cqh;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.gain-meter-panel__grid {
flex: 1 1 auto;
min-height: 0;
width: 100%;
padding: 2cqh;
box-sizing: border-box;
display: grid;
grid-template-columns: 1fr 1fr;
/* slider/meter row gets the bulk of the height, button row stays compact */
grid-template-rows: 1fr auto;
gap: 1.5cqh;
.peq-band-list__row--active {
background: var(--bg-panel);
border-radius: var(--radius-sm);
}
.gain-meter-panel__cell {
.peq-band-list__cell {
min-width: 0;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
}
.gain-meter-panel__cell--slider,
.gain-meter-panel__cell--meter {
height: 100%;
}
.gain-meter-panel__cell--btn {
flex: 0 0 auto;
}
.gain-meter-panel__cell--meter {
align-items: stretch;
justify-content: stretch;
justify-content: center;
text-align: center;
}
.peq-band-list__input,
.peq-band-list__select {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
font-family: var(--font-ui);
font-size: 2.2cqh;
padding: 0.5cqh;
text-align: center;
background: var(--bg-panel);
color: var(--text-primary);
border: var(--border-width) solid var(--border-hairline);
border-radius: var(--radius-sm);
box-sizing: border-box;
}
@@ -1,5 +1,8 @@
// InputSection.tsx
import VerticalSlider from '../../../../components/VerticalSlider/VerticalSlider';
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.
@@ -13,9 +16,23 @@ import ChannelGroup from '../../../../components/ChannelGroup/ChannelGroup';
import Button from '../../../../components/Button/Button';
import MeterBar from '../../../../components/MeterBar/MeterBar';
const FREQ_MIN = 20;
const FREQ_MAX = 20000;
const FREQ_STEP = 1;
const PEQ_BANDS: PEQBand[] = [
PEQBand.B1,
PEQBand.B2,
PEQBand.B3,
PEQBand.B4,
PEQBand.B5,
PEQBand.B6,
PEQBand.B7,
PEQBand.B8,
];
// 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' },
];
const FILTER_TYPE_OPTIONS: { value: CrossoverFilter; label: string }[] = [
{ value: CrossoverFilter.Bw6, label: 'BW-6' },
@@ -112,6 +129,12 @@ 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,
@@ -124,8 +147,7 @@ function CrossoverPanel({
setCrossover: (
channel: Channel,
frequency: number,
filter_type: CrossoverFilter,
bypass: boolean
filter_type: CrossoverFilter
) => Promise<boolean>;
}) {
const channelState =
@@ -145,7 +167,7 @@ function CrossoverPanel({
bypass?: boolean;
}) => {
const next = { frequency, filter_type, bypass, ...overrides };
return setCrossover(channel, next.frequency, next.filter_type, next.bypass);
return setCrossover(channel, next.frequency, next.filter_type);
};
return (
@@ -153,9 +175,11 @@ function CrossoverPanel({
<div className="crossover-panel__title">{title}</div>
<VerticalSlider
min={20}
max={20000}
step={() => 1}
min={FREQ_MIN}
max={FREQ_MAX}
scale="log"
step={FREQ_STEP}
quantize={FREQ_QUANTIZE}
unit=" Hz"
value={frequency}
onChange={(value) => commit({ frequency: value })}
@@ -194,99 +218,117 @@ function CrossoverPanel({
);
}
function GainMeterPanel({
dsp,
function PeqBandList({
channel,
title,
meter,
setGain,
setMute,
setInverse,
dsp,
activeBand,
setActiveBand,
setPEQ,
}: {
channel: InputChannel;
dsp: DSP;
channel: Channel;
title: string;
meter: Meter;
setGain: (channel: Channel, gain: number) => Promise<boolean>;
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
activeBand: PEQBand;
setActiveBand: (band: PEQBand) => void;
setPEQ: SetPEQFn;
}) {
const channelState =
'Input' in channel
? dsp.state?.current_config.input_states[channel.Input]
: dsp.state?.current_config.output_states[channel.Output];
const inputState = dsp.state.current_config.input_states[channel];
const muted = channelState?.mute ?? false;
const inverted = channelState?.phase_inverted ?? false;
const gain = channelState?.gain ?? 0;
const toggleMute = async () => {
await setMute(channel, !muted);
};
const toggleInverse = async () => {
await setInverse(channel, !inverted);
const commit = (band: PEQBand, overrides: Partial<PEQValues>) => {
const peq = inputState.peq_chain.bands[band] as PEQ;
const next = { ...peq, ...overrides };
return setPEQ(
{ Input: channel },
band,
next.gain,
next.frequency,
next.q,
next.filter_type,
next.bypass
);
};
return (
<div className="gain-meter-panel">
<div className="gain-meter-panel__title">{title}</div>
<div className="gain-meter-panel__grid">
<div className="gain-meter-panel__cell gain-meter-panel__cell--slider">
<VerticalSlider
min={-60}
max={12}
step={(v) => (v >= -20 ? 0.1 : 0.5)}
unit=" dB"
value={gain}
onChange={(value) => setGain(channel, value)}
style={{ width: '100%', height: '100%' }}
/>
</div>
<div className="gain-meter-panel__cell gain-meter-panel__cell--meter">
<MeterBar label="" meter={meter} />
</div>
<div className="gain-meter-panel__cell gain-meter-panel__cell--btn">
<Button
variant="toggle"
className={inverted ? 'active-warning' : ''}
onClick={toggleInverse}
style={{
width: '100%',
paddingLeft: 0,
paddingRight: 0,
fontSize: '3.5cqh',
paddingTop: '1cqh',
paddingBottom: '1cqh',
minHeight: 0,
}}
>
{inverted ? 'Inverted' : 'Invert'}
</Button>
</div>
<div className="gain-meter-panel__cell gain-meter-panel__cell--btn">
<Button
variant="toggle"
className={muted ? 'active-danger' : ''}
onClick={toggleMute}
style={{
width: '100%',
paddingLeft: 0,
paddingRight: 0,
fontSize: '3.5cqh',
paddingTop: '1cqh',
paddingBottom: '1cqh',
minHeight: 0,
}}
>
{muted ? 'Muted' : 'Mute'}
</Button>
</div>
<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.map((band, i) => {
const peq = inputState.peq_chain.bands[band] as PEQ;
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>
<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) })}
/>
<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 })}
>
{PEQ_FILTER_TYPE_OPTIONS.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>
);
}
@@ -310,23 +352,17 @@ type setPeqBandFn = (
function InputSection({
dsp,
channel,
set_high_pass: setHighPass,
set_low_pass,
setHighPass,
setLowPass,
setPeq,
// setPeqBypass,
setGain,
setMute,
setInverse,
setPeqBypass,
}: {
dsp: DSP;
channel: InputChannel;
set_high_pass: SetFilterFn;
set_low_pass: SetFilterFn;
setHighPass: SetFilterFn;
setLowPass: SetFilterFn;
setPeq: setPeqBandFn;
// setPeqBypass;
setGain: (channel: Channel, gain: number) => Promise<boolean>;
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
setPeqBypass: (channel: Channel, bypass: boolean) => Promise<boolean>;
}) {
const [activeBand, setActiveBand] = useState<PEQBand>(PEQBand.B1);
@@ -408,7 +444,14 @@ function InputSection({
<div className="input-control-section">
<div className="input-control-panel">
<ChannelGroup title="PEQ">
<PeqPanelSliders channel={channel} dsp={dsp} band={PEQBand.B1} setPEQ={setPeq} />
<PeqBandList
channel={channel}
dsp={dsp}
activeBand={activeBand}
setActiveBand={setActiveBand}
setPEQ={setPeq}
/>
<PeqPanelSliders channel={channel} dsp={dsp} band={activeBand} setPEQ={setPeq} />
</ChannelGroup>
<ChannelGroup title="CrossOver">
@@ -425,18 +468,6 @@ function InputSection({
setCrossover={setHighPass}
/>
</ChannelGroup>
<ChannelGroup title="Gain">
<GainMeterPanel
dsp={dsp}
channel={{ Input: channel }}
title={dsp.state.current_config.input_states.InA.name}
meter={dsp.meters.channels[0]}
setGain={setGain}
setMute={setMute}
setInverse={setInverse}
/>
</ChannelGroup>
{/* <div className="input-control-panel__sliders">
<div className="input-control-panel__cell input-control-panel__cell--high-pass">
<VerticalSlider
+10 -12
View File
@@ -106,15 +106,15 @@ export enum DiscreteFrequency {
}
export enum PEQBand {
B1 = 1,
B2 = 2,
B3 = 3,
B4 = 4,
B5 = 5,
B6 = 6,
B7 = 7,
B8 = 8,
B9 = 9,
B1 = 'B1',
B2 = 'B2',
B3 = 'B3',
B4 = 'B4',
B5 = 'B5',
B6 = 'B6',
B7 = 'B7',
B8 = 'B8',
B9 = 'B9',
}
// ---------------- STATE ----------------
@@ -304,9 +304,7 @@ const createDefaultPEQ = (): PEQ => ({
const createDefaultPEQChain = (): PEQChain => ({
bands: Object.fromEntries(
Object.values(PEQBand)
.filter((band): band is PEQBand => typeof band === 'number')
.map((band) => [band, createDefaultPEQ()])
Object.values(PEQBand).map((band) => [band, createDefaultPEQ()])
) as Record<PEQBand, PEQ>,
bypass: false,
});