From b572e1c7947bc02255a1e104bb117dbce40425fc Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Mon, 10 Aug 2026 17:58:55 +0200 Subject: [PATCH] feat: start in tab --- .../dsp408/InputSection/InputSection.css | 117 ++++++++ .../dsp408/InputSection/InputSection.tsx | 258 ++++++++++++++++-- 2 files changed, 352 insertions(+), 23 deletions(-) diff --git a/src/components/dsp408/InputSection/InputSection.css b/src/components/dsp408/InputSection/InputSection.css index c7eca5e..f5223ac 100644 --- a/src/components/dsp408/InputSection/InputSection.css +++ b/src/components/dsp408/InputSection/InputSection.css @@ -223,3 +223,120 @@ color: var(--text-muted); white-space: nowrap; } + +.crossover-panel { + position: relative; + height: 100%; + aspect-ratio: 0.28; + flex: 0 0 auto; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + + border: var(--border-width) solid var(--border-hairline); + border-radius: var(--radius-md); + box-sizing: border-box; + gap: 2cqh; + background: var(--bg-void); + padding-bottom: 2cqh; +} + +.crossover-panel__title { + flex: 0 0 auto; + width: 100%; + text-align: center; + font-family: var(--font-ui); + font-size: 4cqh; + font-weight: var(--font-weight-semibold); + padding-top: 2cqh; + padding-bottom: 2cqh; + border-bottom: var(--border-width) solid var(--border-hairline); + + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +.crossover-panel__select { + flex: 0 0 auto; + width: 85%; + font-family: var(--font-ui); + font-size: 3cqh; + padding: 1cqh; + background: var(--bg-panel); + color: inherit; + border: var(--border-width) solid var(--border-hairline); + border-radius: var(--radius-sm); +} + +.gain-meter-panel { + container-type: size; + + position: relative; + height: 100%; + aspect-ratio: 1 / 2; + flex: 0 0 auto; + + display: flex; + flex-direction: column; + align-items: center; + + border: var(--border-width) solid var(--border-hairline); + border-radius: var(--radius-md); + box-sizing: border-box; + background: var(--bg-void); +} + +.gain-meter-panel__title { + flex: 0 0 auto; + width: 100%; + text-align: center; + font-family: var(--font-ui); + font-size: 4cqh; + font-weight: var(--font-weight-semibold); + padding-top: 2cqh; + padding-bottom: 2cqh; + border-bottom: var(--border-width) solid var(--border-hairline); + + 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; +} + +.gain-meter-panel__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; +} diff --git a/src/components/dsp408/InputSection/InputSection.tsx b/src/components/dsp408/InputSection/InputSection.tsx index 73bdc3a..70177f7 100644 --- a/src/components/dsp408/InputSection/InputSection.tsx +++ b/src/components/dsp408/InputSection/InputSection.tsx @@ -3,37 +3,37 @@ import VerticalSlider from '../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 } from '../../../types/dsp408State'; +import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Meter } from '../../../types/dsp408State'; import { DSP } from '../../../types/types'; import { CrossoverFilter } from '../../../types/dsp408State'; import { LinearGraph } from '../LinearGraph/LinearGraph'; import { useRef, useState } from 'react'; import './InputSection.css'; import ChannelGroup from '../ChannelGroup/ChannelGroup'; +import Button from '../../Button/Button'; +import MeterBar from '../../MeterBar/MeterBar'; const FREQ_MIN = 20; const FREQ_MAX = 20000; const FREQ_STEP = 1; const FILTER_TYPE_OPTIONS: { value: CrossoverFilter; label: string }[] = [ - { value: CrossoverFilter.Bw6, label: 'BW 6 dB/oct' }, - { value: CrossoverFilter.Bw12, label: 'BW 12 dB/oct' }, - { value: CrossoverFilter.Bw18, label: 'BW 18 dB/oct' }, - { value: CrossoverFilter.Bw24, label: 'BW 24 dB/oct' }, - { value: CrossoverFilter.Bw30, label: 'BW 30 dB/oct' }, - { value: CrossoverFilter.Bw36, label: 'BW 36 dB/oct' }, - { value: CrossoverFilter.Bw42, label: 'BW 42 dB/oct' }, - { value: CrossoverFilter.Bw48, label: 'BW 48 dB/oct' }, - { value: CrossoverFilter.Lk12, label: 'LR 12 dB/oct' }, - { value: CrossoverFilter.Lk24, label: 'LR 24 dB/oct' }, - { value: CrossoverFilter.Lk36, label: 'LR 36 dB/oct' }, - { value: CrossoverFilter.Lk48, label: 'LR 48 dB/oct' }, + { 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' }, ]; -const DEFAULT_FILTER_TYPE = CrossoverFilter.Bw12; - const PEQ_SLIDER_PARAMS = [ - { key: 'frequency', label: 'Frequency', min: 19.7, max: 20.16, step: 0.5, unit: ' Hz' }, + { 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; @@ -112,6 +112,185 @@ function PeqPanelSliders({ ); } +function CrossoverPanel({ + channel, + dsp, + title, + setCrossover, +}: { + channel: Channel; + dsp: DSP; + title: string; + setCrossover: ( + channel: Channel, + frequency: number, + filter_type: CrossoverFilter, + bypass: boolean + ) => Promise; +}) { + const channelState = + 'Input' in channel + ? dsp.state?.current_config.input_states[channel.Input] + : dsp.state?.current_config.output_states[channel.Output]; + + const crossover = channelState?.crossover; + + const frequency = crossover.high_pass.frequency; + const filter_type = crossover.high_pass.slope; + const bypass = false; + + const commit = (overrides: { + frequency?: number; + filter_type?: CrossoverFilter; + bypass?: boolean; + }) => { + const next = { frequency, filter_type, bypass, ...overrides }; + return setCrossover(channel, next.frequency, next.filter_type, next.bypass); + }; + + return ( +
+
{title}
+ + 1} + unit=" Hz" + value={frequency} + onChange={(value) => commit({ frequency: value })} + style={{ width: '100%', flex: '1 1 auto' }} + /> + + + + +
+ ); +} + +function GainMeterPanel({ + dsp, + channel, + title, + meter, + setGain, + setMute, + setInverse, +}: { + dsp: DSP; + channel: Channel; + title: string; + meter: Meter; + setGain: (channel: Channel, gain: number) => Promise; + setMute: (channel: Channel, muted: boolean) => Promise; + setInverse: (channel: Channel, inverted: boolean) => Promise; +}) { + const channelState = + 'Input' in channel + ? dsp.state?.current_config.input_states[channel.Input] + : dsp.state?.current_config.output_states[channel.Output]; + + 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); + }; + + return ( +
+
{title}
+ +
+
+ (v >= -20 ? 0.1 : 0.5)} + unit=" dB" + value={gain} + onChange={(value) => setGain(channel, value)} + style={{ width: '100%', height: '100%' }} + /> +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ ); +} + type SetFilterFn = ( channel: Channel, freq: number, @@ -131,10 +310,13 @@ type setPeqBandFn = ( function InputSection({ dsp, channel, - set_high_pass, + set_high_pass: setHighPass, set_low_pass, setPeq, // setPeqBypass, + setGain, + setMute, + setInverse, }: { dsp: DSP; channel: InputChannel; @@ -142,6 +324,9 @@ function InputSection({ set_low_pass: SetFilterFn; setPeq: setPeqBandFn; // setPeqBypass; + setGain: (channel: Channel, gain: number) => Promise; + setMute: (channel: Channel, muted: boolean) => Promise; + setInverse: (channel: Channel, inverted: boolean) => Promise; }) { const [activeBand, setActiveBand] = useState(PEQBand.B1); @@ -159,12 +344,12 @@ function InputSection({ const activeFilterType = bypassed && lp.slope !== CrossoverFilter.Bypass ? lp.slope : hp.slope; // Remembers the last non-bypass slope so the Bypass button can restore it. - const lastNonBypassType = useRef( - activeFilterType === CrossoverFilter.Bypass ? DEFAULT_FILTER_TYPE : activeFilterType - ); - if (activeFilterType !== CrossoverFilter.Bypass) { - lastNonBypassType.current = activeFilterType; - } + // const lastNonBypassType = useRef( + // activeFilterType === CrossoverFilter.Bypass ? DEFAULT_FILTER_TYPE : activeFilterType + // ); + // if (activeFilterType !== CrossoverFilter.Bypass) { + // lastNonBypassType.current = activeFilterType; + // } const hpFreq = liveHpFreq ?? hp.frequency; const lpFreq = liveLpFreq ?? lp.frequency; @@ -225,6 +410,33 @@ function InputSection({ + + + + + + + + + {/*