From 1e00ca3a7c2123423b0242d0449f1ac18187f0e5 Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Sat, 12 Sep 2026 19:37:59 +0200 Subject: [PATCH] feat: remove state and let rust handle --- src-tauri/src/commands.rs | 30 +- src-tauri/src/lib.rs | 1 + src/assets/icons/Triangle.tsx | 16 +- src/components/ChoiceSelect/ChoiceSelect.tsx | 13 +- .../ChoiceStepper/ChoiceStepper.tsx | 41 +- .../SignalFlowMatrix/SignalFlowMatrix.tsx | 2 +- .../components/InputSection/InputSection.css | 155 +-- .../components/InputSection/InputSection.tsx | 171 +--- .../Matrixsection/Matrixsection.css | 2 +- src/features/dsp408/constants.ts | 1 + src/features/dsp408/defaults.ts | 196 ---- src/features/dsp408/dsp408.ts | 964 +----------------- src/features/dsp408/types.ts | 8 + src/pages/AddDspPage/AddDspPage.css | 2 +- src/pages/AddDspPage/AddDspPage.tsx | 39 +- src/pages/CreditsPage/CreditsPage.css | 2 +- src/types.ts | 4 +- 17 files changed, 221 insertions(+), 1426 deletions(-) delete mode 100644 src/features/dsp408/defaults.ts diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 3437679..7f26236 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -41,16 +41,28 @@ impl AppState { } } +#[derive(serde::Serialize)] +pub struct DSP408Snapshot { + pub state: types::DSPState, + pub meters: types::Meters, + pub mode: types::DSPMode, +} + #[tauri::command] pub fn create_dsp408( state: State, ip: String, port: u16, device_id: u8, + demo: bool, ) -> Result { let ip = IpAddr::from_str(&ip).map_err(|e| e.to_string())?; - let dsp = DSP408::new(ip, port, device_id).map_err(|e| e.to_string())?; + let dsp = if demo { + DSP408::new_demo() + } else { + DSP408::new(ip, port, device_id).map_err(|e| e.to_string())? + }; let mut next_id = state.next_id.lock().map_err(|e| e.to_string())?; @@ -81,6 +93,20 @@ pub async fn remove_dsp408(state: State<'_, AppState>, id: u64) -> Result<(), St Ok(()) } +#[tauri::command] +pub async fn get_dsp_snapshot( + state: State<'_, AppState>, + id: u64, +) -> Result { + state.with_device_mut(id, |device| { + Ok(DSP408Snapshot { + state: device.dsp.state().clone(), + meters: device.dsp.meters().clone(), + mode: device.dsp.mode(), + }) + }) +} + #[tauri::command] pub async fn connect_dsp408(state: State<'_, AppState>, id: u64) -> Result<(), String> { state.with_device_mut(id, |device| device.dsp.connect().map_err(|e| e.to_string())) @@ -99,7 +125,7 @@ pub async fn get_dsp_state( id: u64, ) -> Result { state.with_device_mut(id, |device| { - let dsp_state = device.dsp.state().map_err(|e| e.to_string())?; + let dsp_state = device.dsp.state(); Ok(dsp_state.clone()) }) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2488b1b..b37c4f0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,6 +11,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ commands::create_dsp408, commands::remove_dsp408, + commands::get_dsp_snapshot, commands::get_dsp_state, commands::connect_dsp408, // 0x10 commands::disconnect_dsp408, // 0x11 diff --git a/src/assets/icons/Triangle.tsx b/src/assets/icons/Triangle.tsx index 5a6fe99..03cb5ae 100644 --- a/src/assets/icons/Triangle.tsx +++ b/src/assets/icons/Triangle.tsx @@ -1,11 +1,6 @@ export function TriangleUpIcon(props: React.SVGProps) { return ( - + ) { export function TriangleDownIcon(props: React.SVGProps) { return ( - + ) { /> ); -} \ No newline at end of file +} diff --git a/src/components/ChoiceSelect/ChoiceSelect.tsx b/src/components/ChoiceSelect/ChoiceSelect.tsx index a005b53..4722442 100644 --- a/src/components/ChoiceSelect/ChoiceSelect.tsx +++ b/src/components/ChoiceSelect/ChoiceSelect.tsx @@ -9,6 +9,8 @@ type ChoiceSelectProps = { onChange: (value: T) => void; id?: string; disabled?: boolean; + className?: string; + style?: React.CSSProperties; }; function ChoiceSelect({ @@ -18,15 +20,21 @@ function ChoiceSelect({ onChange, id, disabled, + className = '', + style, }: ChoiceSelectProps) { return ( -
+
+ diff --git a/src/components/ChoiceStepper/ChoiceStepper.tsx b/src/components/ChoiceStepper/ChoiceStepper.tsx index 1cc69e9..fd44ca3 100644 --- a/src/components/ChoiceStepper/ChoiceStepper.tsx +++ b/src/components/ChoiceStepper/ChoiceStepper.tsx @@ -67,9 +67,15 @@ function ChoiceStepperList({ useEffect(() => setText(formatLabel(value)), [value, formatLabel]); const step = (direction: 1 | -1) => { - const currentIndex = index === -1 ? 0 : index; - const nextIndex = Math.min(values.length - 1, Math.max(0, currentIndex + direction)); - if (nextIndex !== currentIndex) onChange(values[nextIndex]); + const currentIndex = values.indexOf(value); + + if (currentIndex === -1) return; + + const nextIndex = currentIndex + direction; + + if (nextIndex >= 0 && nextIndex < values.length) { + onChange(values[nextIndex]); + } }; const commitText = () => { @@ -89,6 +95,15 @@ function ChoiceStepperList({ disabled={disabled} onChange={(e) => setText(e.target.value)} onBlur={commitText} + onWheel={(e) => { + e.preventDefault(); + + if (e.deltaY < 0 && canUp) { + step(1); + } else if (e.deltaY > 0 && canDown) { + step(-1); + } + }} onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur(); else if (e.key === 'ArrowUp') { @@ -164,14 +179,24 @@ function ChoiceStepperRange({ disabled={disabled} onChange={(e) => setText(e.target.value)} onBlur={commitText} - onKeyDown={(e) => { - if (e.key === 'Enter') e.currentTarget.blur(); - else if (e.key === 'ArrowUp') { - e.preventDefault(); + onWheel={(e) => { + e.preventDefault(); + + if (e.deltaY < 0 && canUp) { step(1); + } else if (e.deltaY > 0 && canDown) { + step(-1); + } + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.currentTarget.blur(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + if (canUp) step(1); } else if (e.key === 'ArrowDown') { e.preventDefault(); - step(-1); + if (canDown) step(-1); } }} /> diff --git a/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx b/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx index 29f918a..32cb191 100644 --- a/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx +++ b/src/components/SignalFlowMatrix/SignalFlowMatrix.tsx @@ -351,4 +351,4 @@ function SignalFlowMatrix({ ); } -export default SignalFlowMatrix; \ No newline at end of file +export default SignalFlowMatrix; diff --git a/src/features/dsp408/components/InputSection/InputSection.css b/src/features/dsp408/components/InputSection/InputSection.css index fdf85b6..5faaf4a 100644 --- a/src/features/dsp408/components/InputSection/InputSection.css +++ b/src/features/dsp408/components/InputSection/InputSection.css @@ -10,6 +10,9 @@ box-sizing: border-box; background: var(--bg-panel); + + user-select: none; + -webkit-user-select: none; } .input-graph-panel { @@ -40,6 +43,9 @@ min-height: 0; } +/* 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 { container-type: size; position: absolute; @@ -50,7 +56,7 @@ height: var(--dsp-control-heigth); display: flex; - align-items: center; + align-items: flex-end; justify-content: center; box-sizing: border-box; @@ -60,104 +66,23 @@ z-index: 20; border-top: var(--border-width) solid var(--border-hairline); + + user-select: none; + -webkit-user-select: none; } .input-control-panel { + display: grid; + grid-auto-flow: column; height: 100%; - width: 100%; - max-width: 60cqw; - - display: flex; - align-items: stretch; - justify-content: center; /* ← add this */ - gap: var(--space-4); + gap: var(--space-2); + overflow-x: auto; + justify-content: center; box-sizing: border-box; padding: 2cqh; } -.input-control-panel__sliders { - flex: 1 1 auto; - min-width: 0; - - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 4cqh; -} - -.input-control-panel__cell { - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - min-height: 0; - min-width: 0; - gap: 1cqh; -} - -.input-control-panel__cell-label { - flex: 0 0 auto; - font-family: var(--font-ui); - font-size: 2.5cqh; - color: var(--text-muted); - white-space: nowrap; -} - -.input-control-panel__side { - flex: 0 0 auto; - width: 16cqh; - - display: flex; - flex-direction: column; - justify-content: flex-end; - gap: 2cqh; -} - -.input-control-panel__select { - display: flex; - flex-direction: column; - gap: 0.5cqh; -} - -.input-control-panel__select-label { - font-family: var(--font-ui); - font-size: 2.2cqh; - color: var(--text-muted); -} - -.input-control-panel__select select { - font-family: var(--font-ui); - font-size: 2.5cqh; - padding: 0.8cqh; - background: var(--bg-void); - color: var(--text-primary); - border: var(--border-width) solid var(--border-hairline); - border-radius: var(--radius-md); -} - -.input-control-panel__select select:disabled { - opacity: 0.5; -} - -.input-control-panel__bypass-btn { - font-family: var(--font-ui); - font-size: 2.5cqh; - font-weight: var(--font-weight-semibold); - padding: 1cqh; - - background: var(--bg-void); - color: var(--text-primary); - border: var(--border-width) solid var(--border-hairline); - border-radius: var(--radius-md); - cursor: pointer; -} - -.input-control-panel__bypass-btn--active { - background: var(--accent, #f87171); - color: var(--bg-void); - border-color: var(--accent, #f87171); -} - /* Peq Sliders */ .input-panel { position: relative; @@ -260,16 +185,26 @@ text-overflow: ellipsis; } +/* Now the root of a ChoiceSelect rather than a native commit({ filter_type: e.target.value as CrossoverFilter })} - > - {CROSSOVER_FILTER_LABEL.map((opt) => ( - - ))} - + options={CROSSOVER_FILTER_LABEL.map((opt) => opt.value)} + formatLabel={(v) => + CROSSOVER_FILTER_LABEL.find((opt) => opt.value === v)?.label ?? String(v) + } + onChange={(value) => commit({ filter_type: value })} + /> {/* Frequency */} - = 1000 ? (peq.frequency / 1000).toFixed(2) : peq.frequency.toFixed(1) + + v >= 1000 ? `${(v / 1000).toFixed(2)} kHz` : `${v.toFixed(1)} Hz` } - onChange={(e) => { - const value = Number(e.target.value); - - if (!Number.isNaN(value)) { - commit(band, { - frequency: peq.frequency >= 1000 ? value * 1000 : value, - }); - } - }} + onChange={(value) => commit(band, { frequency: value })} /> - commit(band, { q: Number(e.target.value) })} + values={qValues} + formatLabel={(v) => v.toFixed(2)} + onChange={(value) => commit(band, { q: value })} /> - commit(band, { gain: Number(e.target.value) })} + step={0.1} + formatLabel={(v) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`} + onChange={(value) => commit(band, { gain: value })} /> - + options={PEQ_FILTERS_LABEL.map((opt) => opt.value)} + formatLabel={(v) => + PEQ_FILTERS_LABEL.find((opt) => opt.value === v)?.label ?? String(v) + } + onChange={(value) => commit(band, { filter_type: value, q: 3.0 })} + /> -// ); -// })} - -//
-// HP -//
-//
-// LP -//
-// -// ); -// } - function InputSection({ dsp, channel, @@ -626,8 +553,8 @@ function InputSection({ yMin={GRAPH_Y_MIN} yMax={GRAPH_Y_MAX} yStep={3} - xLabel="Hz" - yLabel="dB" + xLabel="Frequency - Hz" + yLabel="Magnituded - dB" showGrid={true} formatX={(v) => Math.round(v).toString()} formatY={(v) => Math.round(v).toString()} @@ -635,7 +562,6 @@ function InputSection({ drawCurves({ ctx, toPx, - xMin: GRAPH_X_MIN, xMax: GRAPH_X_MAX, yMin: GRAPH_Y_MIN, @@ -667,8 +593,8 @@ function InputSection({ yMin={-180} yMax={180} yStep={45} - xLabel="Hz" - yLabel="°" + xLabel="Frequency - Hz" + yLabel="Phase - Deg" showGrid={true} formatX={(v) => Math.round(v).toString()} formatY={(v) => Math.round(v).toString()} @@ -676,7 +602,6 @@ function InputSection({ drawCurves({ ctx, toPx, - xMin: GRAPH_X_MIN, xMax: GRAPH_X_MAX, yMin: GRAPH_Y_MIN, diff --git a/src/features/dsp408/components/Matrixsection/Matrixsection.css b/src/features/dsp408/components/Matrixsection/Matrixsection.css index 785f21e..2fe0d56 100644 --- a/src/features/dsp408/components/Matrixsection/Matrixsection.css +++ b/src/features/dsp408/components/Matrixsection/Matrixsection.css @@ -109,4 +109,4 @@ .matrix-gain-stepper .choice-stepper__input { font-size: 2.8cqh; -} \ No newline at end of file +} diff --git a/src/features/dsp408/constants.ts b/src/features/dsp408/constants.ts index 2af5fe1..5c67457 100644 --- a/src/features/dsp408/constants.ts +++ b/src/features/dsp408/constants.ts @@ -127,6 +127,7 @@ export const PEQ_BANDS_ORDER: PEQBand[] = [ PEQBand.B6, PEQBand.B7, PEQBand.B8, + PEQBand.B9, ]; export const PEQ_Q_RANGES: Record = { diff --git a/src/features/dsp408/defaults.ts b/src/features/dsp408/defaults.ts deleted file mode 100644 index 1cf934b..0000000 --- a/src/features/dsp408/defaults.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { - Gate, - GraphicEQ, - DiscreteFrequency, - PEQ, - PEQFilter, - PEQBand, - PEQChain, - Crossover, - CrossoverFilter, - CrossoverFilters, - InputChannelState, - OutputChannelState, - InputChannel, - DSPState, - Meters, - Meter, - Ratio, - OutputChannel, - DelayUnit, -} from './types'; -import { FREQUENCY_MIN, FREQUENCY_MAX } from './constants'; - -const createDefaultGate = (): Gate => ({ - attack: 10, - release: 10, - hold: 10, - threshold: 0, -}); - -const createDefaultGraphicEQ = (): GraphicEQ => ({ - gains: { - [DiscreteFrequency.F20]: 0, - [DiscreteFrequency.F25]: 0, - [DiscreteFrequency.F31_5]: 0, - [DiscreteFrequency.F40]: 0, - [DiscreteFrequency.F50]: 0, - [DiscreteFrequency.F63]: 0, - [DiscreteFrequency.F80]: 0, - [DiscreteFrequency.F100]: 0, - [DiscreteFrequency.F125]: 0, - [DiscreteFrequency.F160]: 0, - [DiscreteFrequency.F200]: 0, - [DiscreteFrequency.F250]: 0, - [DiscreteFrequency.F315]: 0, - [DiscreteFrequency.F400]: 0, - [DiscreteFrequency.F500]: 0, - [DiscreteFrequency.F630]: 0, - [DiscreteFrequency.F800]: 0, - [DiscreteFrequency.F1000]: 0, - [DiscreteFrequency.F1250]: 0, - [DiscreteFrequency.F1600]: 0, - [DiscreteFrequency.F2000]: 0, - [DiscreteFrequency.F2500]: 0, - [DiscreteFrequency.F3150]: 0, - [DiscreteFrequency.F4000]: 0, - [DiscreteFrequency.F5000]: 0, - [DiscreteFrequency.F6300]: 0, - [DiscreteFrequency.F8000]: 0, - [DiscreteFrequency.F10000]: 0, - [DiscreteFrequency.F12500]: 0, - [DiscreteFrequency.F16000]: 0, - [DiscreteFrequency.F20000]: 0, - }, - bypass: false, -}); - -const createDefaultPEQ = (): PEQ => ({ - gain: 0, - frequency: 1000, - q: 1, - filter_type: PEQFilter.Peak, - bypass: false, -}); - -const createDefaultPEQChain = (): PEQChain => ({ - bands: Object.fromEntries( - Object.values(PEQBand).map((band) => [band, createDefaultPEQ()]) - ) as Record, - bypass: false, -}); - -const createDefaultCrossover = (frequency: number): Crossover => ({ - frequency: frequency, - slope: CrossoverFilter.Bypass, -}); - -const createDefaultCrossoverFilters = (): CrossoverFilters => ({ - high_pass: createDefaultCrossover(FREQUENCY_MIN), - low_pass: createDefaultCrossover(FREQUENCY_MAX), -}); - -const createDefaultInputChannelState = (name: string): InputChannelState => ({ - name, - gain: 0, - phase_inverted: false, - delay: 0, - - gate: createDefaultGate(), - geq: createDefaultGraphicEQ(), - peq_chain: createDefaultPEQChain(), - crossover: createDefaultCrossoverFilters(), - - linked_channels: [], - - mute: false, -}); - -const createDefaultOutputChannelState = (name: string): OutputChannelState => ({ - name, - - matrix_routes: { - connected: [InputChannel.InA], - gains: { - [InputChannel.InA]: 0, - [InputChannel.InB]: 0, - [InputChannel.InC]: 0, - [InputChannel.InD]: 0, - }, - }, - - crossover: createDefaultCrossoverFilters(), - peq_chain: createDefaultPEQChain(), - - compressor: { - threshold: -90, - ratio: Ratio.Ratio1_1, - attack: 1, - release: 10, - knee: 0, - }, - - limiter: { - threshold: -90, - attack: 1, - release: 10, - }, - - gain: 0, - phase_inverted: false, - delay: 0, - - linked_channels: [], - - mute: false, -}); - -export const defaultDSPState: DSPState = { - name: 'Default', - flags: { - is_locked: false, - }, - - presets: { - current_index: 0, - names: Array(20).fill('Default'), - modified: Array(20).fill(false), - }, - - current_config: { - input_source: { - type: 'Analog', - frequency: '48kHz', - }, - - delay_unit: DelayUnit.Millisecond, - - input_states: { - [InputChannel.InA]: createDefaultInputChannelState('InInInIn'), - [InputChannel.InB]: createDefaultInputChannelState('In B'), - [InputChannel.InC]: createDefaultInputChannelState('In C'), - [InputChannel.InD]: createDefaultInputChannelState('In D'), - }, - - output_states: { - [OutputChannel.Out1]: createDefaultOutputChannelState('Out 1'), - [OutputChannel.Out2]: createDefaultOutputChannelState('Out 2'), - [OutputChannel.Out3]: createDefaultOutputChannelState('Out 3'), - [OutputChannel.Out4]: createDefaultOutputChannelState('Out 4'), - [OutputChannel.Out5]: createDefaultOutputChannelState('Out 5'), - [OutputChannel.Out6]: createDefaultOutputChannelState('Out 6'), - [OutputChannel.Out7]: createDefaultOutputChannelState('Out 7'), - [OutputChannel.Out8]: createDefaultOutputChannelState('Out 8'), - }, - }, -}; - -const createDefaultMeter = (): Meter => ({ - rms: -60, - peak: -60, - clip: false, -}); - -export const defaultMeters: Meters = { - channels: Array.from({ length: 12 }, createDefaultMeter), -}; diff --git a/src/features/dsp408/dsp408.ts b/src/features/dsp408/dsp408.ts index 58b93b8..51c7bf7 100644 --- a/src/features/dsp408/dsp408.ts +++ b/src/features/dsp408/dsp408.ts @@ -11,12 +11,10 @@ import { PEQFilter, PEQBand, InputSource, - Crossover, + DSP408Snapshot, } from './types'; import { SetDsps, NotifyFn, updateDSP, setPollingPaused } from '../../dsp/dspShared'; import { registerPollStopper } from '../../dsp/pollRegistry'; -import { defaultDSPState, defaultMeters } from './defaults'; -import { DELAY_RANGES, PEQ_Q_RANGES } from './constants'; type DSP408 = Extract; @@ -104,8 +102,11 @@ export async function create(params: { ip: params.ip, port: params.port, deviceId: params.deviceId, + demo: params.demo, }); + const snapshot = await invoke('get_dsp_snapshot', { id }); + return { id, type: 'DSP408', @@ -113,11 +114,11 @@ export async function create(params: { ip: params.ip, port: params.port, deviceId: params.deviceId, - demo: params.demo, + mode: snapshot.mode, status: 'disconnected', pollingPaused: true, - state: defaultDSPState, - meters: defaultMeters, + state: snapshot.state, + meters: snapshot.meters, }; } @@ -151,11 +152,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { updateDSPState, async connect() { - if (dsp.demo) { - updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'connected', pollingPaused: false })); - return; - } - updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'connecting' })); try { await invoke('connect_dsp408', { id: dsp.id }); @@ -174,16 +170,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async disconnect() { - if (dsp.demo) { - updateDSP(setDsps, dsp.id, (d) => ({ - ...d, - status: 'disconnected', - pollingPaused: true, - })); - - return; - } - stopPolling(dsp.id); try { @@ -204,25 +190,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async recallPreset(presetIndex: number): Promise { - if (dsp.demo) { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - return { - ...d, - state: { - ...d.state, - presets: { - ...d.state.presets, - current_index: presetIndex - 1, - }, - }, - }; - }); - - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('recall_preset', { @@ -249,34 +216,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { async setCurrentPresetName(name: string): Promise { const paddedName = name.slice(0, 14).padEnd(14, ' '); - const updatePresetName = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const presets = d.state.presets; - const index = presets.current_index; - - return { - ...d, - state: { - ...d.state, - presets: { - ...presets, - names: presets.names.map((presetName, i) => - i === index ? paddedName : presetName - ), - modified: presets.modified.map((modified, i) => (i === index ? true : modified)), - }, - }, - }; - }); - }; - - if (dsp.demo) { - updatePresetName(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_current_preset_name', { @@ -301,55 +240,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setGain(channel: Channel, gain: number): Promise { - const updateGain = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - gain, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - gain, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateGain(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_channel_gain', { @@ -375,59 +265,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setMute(channel: Channel, muted: boolean): Promise { - const updateMute = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - const inputChannel = channel.Input; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [inputChannel]: { - ...cfg.input_states[inputChannel], - mute: muted, - }, - }, - }, - }, - }; - } - - const outputChannel = channel.Output; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [outputChannel]: { - ...cfg.output_states[outputChannel], - mute: muted, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateMute(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_mute', { @@ -453,59 +290,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setInverse(channel: Channel, inverted: boolean): Promise { - const updatePhase = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - const inputChannel = channel.Input; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [inputChannel]: { - ...cfg.input_states[inputChannel], - phase_inverted: inverted, - }, - }, - }, - }, - }; - } - - const outputChannel = channel.Output; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [outputChannel]: { - ...cfg.output_states[outputChannel], - phase_inverted: inverted, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updatePhase(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_channel_inverse_gain', { @@ -537,41 +321,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { hold: number, release: number ): Promise { - const updateGate = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [inputChannel]: { - ...cfg.input_states[inputChannel], - gate: { - threshold, - attack, - hold, - release, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateGate(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_gate', { @@ -607,42 +356,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { knee: number, threshold: number ): Promise { - const updateCompressor = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [outputChannel]: { - ...cfg.output_states[outputChannel], - compressor: { - ratio, - attack, - release, - knee, - threshold, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateCompressor(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_compressor', { @@ -677,40 +390,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { attack: number, release: number ): Promise { - const updateLimiter = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [outputChannel]: { - ...cfg.output_states[outputChannel], - limiter: { - threshold, - attack, - release, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateLimiter(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_limiter', { @@ -738,54 +417,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setDelayUnit(unit: DelayUnit): Promise { - const updateDelayUnit = () => { - function convertDelayUnit(value: number, from: DelayUnit, to: DelayUnit): number { - if (from === to) return value; - - const [, fromMax] = DELAY_RANGES[from]; - const [, toMax] = DELAY_RANGES[to]; - - if (fromMax === 0) return 0; - - const ratio = value / fromMax; - - return ratio * toMax; - } - - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const currentConfig = d.state.current_config; - const oldUnit = currentConfig.delay_unit; - - const convertAll = >(states: T): T => - Object.fromEntries( - Object.entries(states).map(([channel, s]) => [ - channel, - { ...s, delay: convertDelayUnit(s.delay, oldUnit, unit) }, - ]) - ) as T; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...currentConfig, - delay_unit: unit, - input_states: convertAll(currentConfig.input_states), - output_states: convertAll(currentConfig.output_states), - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateDelayUnit(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_delay_unit', { @@ -810,55 +441,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setDelay(channel: Channel, delay: number): Promise { - const updateDelay = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - delay, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - delay, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateDelay(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_delay', { @@ -885,39 +467,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setMatrixRouting(output: OutputChannel, inputs: InputChannel[]): Promise { - const updateRouting = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [output]: { - ...cfg.output_states[output], - matrix_routes: { - ...cfg.output_states[output].matrix_routes, - connected: inputs, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateRouting(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_matrix_routing', { @@ -947,42 +496,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { outputChannel: OutputChannel, gain: number ): Promise { - const updateMatrixGain = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - const outputState = cfg.output_states[outputChannel]; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [outputChannel]: { - ...outputState, - matrix_routes: { - ...outputState.matrix_routes, - gains: { - ...outputState.matrix_routes.gains, - [inputChannel]: gain, - }, - }, - }, - }, - }, - }, - }; - }); - }; - if (dsp.demo) { - updateMatrixGain(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_matrix_gain', { @@ -1013,43 +526,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { frequencyBand: DiscreteFrequency, gain: number ): Promise { - const updateGeqBand = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - const inputState = cfg.input_states[inputChannel]; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [inputChannel]: { - ...inputState, - geq: { - ...inputState.geq, - gains: { - ...inputState.geq.gains, - [frequencyBand]: gain, - }, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateGeqBand(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_geq_band', { @@ -1076,40 +552,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setGeqBypass(inputChannel: InputChannel, bypass: boolean): Promise { - const updateGeqBypass = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - const inputState = cfg.input_states[inputChannel]; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [inputChannel]: { - ...inputState, - geq: { - ...inputState.geq, - bypass, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateGeqBypass(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_geq_bypass', { @@ -1140,66 +582,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { freq: number, filterType: CrossoverFilter ): Promise { - const updateLowPass = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - const lowPass: Crossover = { - frequency: freq, - slope: filterType, - }; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - crossover: { - ...cfg.input_states[channel.Input].crossover, - low_pass: lowPass, - }, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - crossover: { - ...cfg.output_states[channel.Output].crossover, - low_pass: lowPass, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateLowPass(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_low_pass', { @@ -1230,66 +612,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { freq: number, filterType: CrossoverFilter ): Promise { - const updateHighPass = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - const highPass: Crossover = { - frequency: freq, - slope: filterType, - }; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - crossover: { - ...cfg.input_states[channel.Input].crossover, - high_pass: highPass, - }, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - crossover: { - ...cfg.output_states[channel.Output].crossover, - high_pass: highPass, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateHighPass(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_high_pass', { @@ -1324,112 +646,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { filterType: PEQFilter, bypass: boolean ): Promise { - const updatePeqBand = () => { - function convertPeqQ(value: number, from: PEQFilter, to: PEQFilter): number { - if (from === to) return value; - - const [fromMin, fromMax] = PEQ_Q_RANGES[from]; - const [toMin, toMax] = PEQ_Q_RANGES[to]; - - if (fromMax === fromMin) return toMin; - - const ratio = (value - fromMin) / (fromMax - fromMin); - - return toMin + ratio * (toMax - toMin); - } - - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - const inputState = cfg.input_states[channel.Input]; - const existingBand = inputState.peq_chain.bands[band]; - - const nextQ = - existingBand.filter_type !== filterType - ? convertPeqQ(existingBand.q, existingBand.filter_type, filterType) - : q; - - const peqBand = { - gain: gainDb, - frequency: freqHz, - q: nextQ, - filter_type: filterType, - bypass, - }; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...inputState, - peq_chain: { - ...inputState.peq_chain, - bands: { - ...inputState.peq_chain.bands, - [band]: peqBand, - }, - }, - }, - }, - }, - }, - }; - } - - const outputState = cfg.output_states[channel.Output]; - const existingBand = outputState.peq_chain.bands[band]; - - const nextQ = - existingBand.filter_type !== filterType - ? convertPeqQ(existingBand.q, existingBand.filter_type, filterType) - : q; - - const peqBand = { - gain: gainDb, - frequency: freqHz, - q: nextQ, - filter_type: filterType, - bypass, - }; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...outputState, - peq_chain: { - ...outputState.peq_chain, - bands: { - ...outputState.peq_chain.bands, - [band]: peqBand, - }, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updatePeqBand(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_peq_band', { @@ -1460,61 +676,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setPEQBypass(channel: Channel, bypass: boolean): Promise { - const updateBypass = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - peq_chain: { - ...cfg.input_states[channel.Input].peq_chain, - bypass, - }, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - peq_chain: { - ...cfg.output_states[channel.Output].peq_chain, - bypass, - }, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateBypass(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_bypass_peq', { @@ -1541,33 +702,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async storeCurrentConfiguration(presetIndex: number, force: boolean): Promise { - if (dsp.demo) { - if (presetIndex === 0 && !force) { - return false; - } - - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const currentIndex = d.state.presets.current_index; - - return { - ...d, - state: { - ...d.state, - presets: { - ...d.state.presets, - modified: d.state.presets.modified.map((modified, index) => - index === currentIndex ? true : modified - ), - }, - }, - }; - }); - - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('store_current_configuration', { @@ -1595,10 +729,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { return false; } - if (dsp.demo) { - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('factory_reset', { @@ -1621,10 +751,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async copyChannel(channel1: Channel, channel2: Channel): Promise { - if (dsp.demo) { - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('copy_channel', { @@ -1648,28 +774,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setInputSource(inputSource: InputSource): Promise { - const updateInputSource = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - return { - ...d, - state: { - ...d.state, - current_config: { - ...d.state.current_config, - input_source: inputSource, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateInputSource(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_input_source', { @@ -1692,11 +796,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async linkChannels(source: Channel, destinations: Channel[]): Promise { - if (dsp.demo) { - // TODO: update local channel link state - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('link_channels', { @@ -1720,55 +819,6 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { }, async setChannelName(channel: Channel, name: string): Promise { - const updateChannelName = () => { - updateDSP(setDsps, dsp.id, (d) => { - if (!d.state) return d; - - const cfg = d.state.current_config; - - if ('Input' in channel) { - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - input_states: { - ...cfg.input_states, - [channel.Input]: { - ...cfg.input_states[channel.Input], - name, - }, - }, - }, - }, - }; - } - - return { - ...d, - state: { - ...d.state, - current_config: { - ...cfg, - output_states: { - ...cfg.output_states, - [channel.Output]: { - ...cfg.output_states[channel.Output], - name, - }, - }, - }, - }, - }; - }); - }; - - if (dsp.demo) { - updateChannelName(); - return true; - } - return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('set_channel_name', { diff --git a/src/features/dsp408/types.ts b/src/features/dsp408/types.ts index de81c46..4d23a6f 100644 --- a/src/features/dsp408/types.ts +++ b/src/features/dsp408/types.ts @@ -238,6 +238,14 @@ export type DSPState = { current_config: DSPConfigState; }; +export type DSPMode = 'demo' | 'disconnected' | 'connected'; + +export type DSP408Snapshot = { + state: DSPState; + meters: Meters; + mode: DSPMode; +}; + export interface Meter { rms: number; peak: number; diff --git a/src/pages/AddDspPage/AddDspPage.css b/src/pages/AddDspPage/AddDspPage.css index f7a93c6..0715d0f 100644 --- a/src/pages/AddDspPage/AddDspPage.css +++ b/src/pages/AddDspPage/AddDspPage.css @@ -172,4 +172,4 @@ .add-dsp-form select { user-select: text; -webkit-user-select: text; -} \ No newline at end of file +} diff --git a/src/pages/AddDspPage/AddDspPage.tsx b/src/pages/AddDspPage/AddDspPage.tsx index cd3fbae..fc9a943 100644 --- a/src/pages/AddDspPage/AddDspPage.tsx +++ b/src/pages/AddDspPage/AddDspPage.tsx @@ -1,5 +1,5 @@ import { useState, useRef } from 'react'; -import { DSP, DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types'; +import { DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types'; import Button from '../../components/Button/Button'; import Card from '../../components/Card/Card'; import ChoiceStepper from '../../components/ChoiceStepper/ChoiceStepper'; @@ -37,7 +37,7 @@ function AddDSPForm({ onAdd, }: { onCancel: () => void; - onAdd: (dsp: Omit) => void; + onAdd: (params: DSPForm) => void; }) { const [form, setForm] = useState({ name: '', @@ -60,39 +60,37 @@ function AddDSPForm({ form.deviceId <= 255; function validateIp() { - if (form.demo) return; + if (form.demo) return; - const ip = form.ip.trim(); + const ip = form.ip.trim(); - if (isValidIPv4(ip)) { - lastValidIp.current = ip; + if (isValidIPv4(ip)) { + lastValidIp.current = ip; - if (ip !== form.ip) { - setForm((prev) => ({ ...prev, ip })); + if (ip !== form.ip) { + setForm((prev) => ({ ...prev, ip })); + } + + return; } - return; + setForm((prev) => ({ + ...prev, + ip: lastValidIp.current, + })); } - setForm((prev) => ({ - ...prev, - ip: lastValidIp.current, - })); -} - function submit() { if (!canSubmit) return; - onAdd({ ...form }); + onAdd(form); } return (

Add DSP

-

- Register a new device to control from this app. -

+

Register a new device to control from this app.

@@ -109,6 +107,7 @@ function AddDSPForm({