feat: add delay tab
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use dsp_thomann::dsp408::types::DelayUnit;
|
||||
use tauri::State;
|
||||
use crate::state::{AppState, DSPDevice};
|
||||
use dsp_thomann::dsp408::{DSP408, types};
|
||||
@@ -308,3 +309,33 @@ pub async fn set_limiter(
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_delay_unit(
|
||||
state: State<'_, AppState>,
|
||||
id: u64,
|
||||
unit: types::DelayUnit,
|
||||
) -> Result<bool, String> {
|
||||
state.with_device_mut(id, |device| {
|
||||
device
|
||||
.dsp
|
||||
.set_delay_unit(unit)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_delay(
|
||||
state: State<'_, AppState>,
|
||||
id: u64,
|
||||
channel: types::Channel,
|
||||
delay: f32,
|
||||
unit: DelayUnit
|
||||
) -> Result<bool, String> {
|
||||
state.with_device_mut(id, |device| {
|
||||
device
|
||||
.dsp
|
||||
.set_delay(channel, delay, unit)
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
}
|
||||
@@ -23,6 +23,8 @@ pub fn run() {
|
||||
commands::set_gate,
|
||||
commands::set_compressor,
|
||||
commands::set_limiter,
|
||||
commands::set_delay_unit,
|
||||
commands::set_delay,
|
||||
]
|
||||
)
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -45,6 +45,12 @@
|
||||
}
|
||||
|
||||
/* Active toggle states */
|
||||
.btn-toggle.active-info {
|
||||
background: var(--accent-brand);
|
||||
border-color: var(--accent-brand);
|
||||
color: var(--bg-void);
|
||||
}
|
||||
|
||||
.btn-toggle.active-danger {
|
||||
background: var(--accent-down);
|
||||
border-color: var(--accent-down);
|
||||
@@ -58,6 +64,11 @@
|
||||
}
|
||||
|
||||
/* Optional: active state hover */
|
||||
.btn-toggle.active-info:hover:not(:disabled) {
|
||||
background: var(--accent-brand);
|
||||
border-color: var(--accent-brand);
|
||||
}
|
||||
|
||||
.btn-toggle.active-danger:hover:not(:disabled) {
|
||||
background: var(--accent-down);
|
||||
border-color: var(--accent-down);
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
.compressor-panel {
|
||||
position: relative;
|
||||
height: 10;
|
||||
|
||||
aspect-ratio: 0.5;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -200,7 +200,6 @@ function CompressorPanel({
|
||||
onLiveValue: (channel: OutputChannel, key: CompressorParamKey, value: number | Ratio) => void;
|
||||
}) {
|
||||
const outputState = dsp.state.current_config.output_states[channel];
|
||||
// ASSUMPTION: compressor settings live at output_states[channel].compressor
|
||||
const comp = outputState.compressor as CompressorValues;
|
||||
|
||||
const commit = (overrides: Partial<CompressorValues>) => {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
.delay-section {
|
||||
container-type: size;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
height: var(--dsp-control-heigth);
|
||||
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
background: var(--bg-panel);
|
||||
|
||||
z-index: 20;
|
||||
|
||||
border-top: var(--border-width) solid var(--border-hairline);
|
||||
}
|
||||
|
||||
.delay-panels {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.delay-panel {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 4;
|
||||
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;
|
||||
}
|
||||
|
||||
.delay-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;
|
||||
}
|
||||
|
||||
.delay-unit-selector {
|
||||
height: 100%;
|
||||
width: 20cqh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5cqh;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.delay-linear-graph-section {
|
||||
flex: 0 0 calc(100% - var(--dsp-control-heigth));
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1cqh;
|
||||
padding: 0.5cqh;
|
||||
|
||||
box-sizing: border-box;
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
|
||||
.delay-graph-panel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.delay-graph-title {
|
||||
flex: 0 0 auto;
|
||||
|
||||
text-align: center;
|
||||
padding: 1cqh;
|
||||
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2.5cqh;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.delay-graph-panel > .container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
import VerticalSlider from '../Slider/VerticalSlider';
|
||||
import { InputChannel, Channel, OutputChannel, DelayUnit } from '../../../types/dsp408State';
|
||||
import { DSP } from '../../../types/types';
|
||||
import Button from '../../Button/Button';
|
||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
||||
import { useMemo, useState } from 'react';
|
||||
import './DelaySection.css';
|
||||
|
||||
const DELAY_RANGES: Record<DelayUnit, [number, number]> = {
|
||||
[DelayUnit.Millisecond]: [0, 680.0],
|
||||
[DelayUnit.Meter]: [0, 233.58],
|
||||
[DelayUnit.Feet]: [0, 766.329],
|
||||
};
|
||||
|
||||
const UNIT_LABEL: Record<DelayUnit, string> = {
|
||||
[DelayUnit.Millisecond]: 'ms',
|
||||
[DelayUnit.Meter]: 'm',
|
||||
[DelayUnit.Feet]: 'ft',
|
||||
};
|
||||
|
||||
const UNIT_ORDER: DelayUnit[] = [DelayUnit.Millisecond, DelayUnit.Meter, DelayUnit.Feet];
|
||||
|
||||
const DELAY_STEPS = 255;
|
||||
const DELAY_STEP: Record<DelayUnit, number> = {
|
||||
[DelayUnit.Millisecond]: (680.0 - 0) / (DELAY_STEPS - 1),
|
||||
[DelayUnit.Meter]: (233.58 - 0) / (DELAY_STEPS - 1),
|
||||
[DelayUnit.Feet]: (766.329 - 0) / (DELAY_STEPS - 1),
|
||||
};
|
||||
|
||||
type SetDelayFn = (channel: Channel, delay: number) => Promise<boolean>;
|
||||
|
||||
function channelKey(channel: Channel): string {
|
||||
return 'Input' in channel ? `Input:${channel.Input}` : `Output:${channel.Output}`;
|
||||
}
|
||||
|
||||
function DelayPanel({
|
||||
channel,
|
||||
dsp,
|
||||
setDelay,
|
||||
onLiveValue,
|
||||
}: {
|
||||
channel: Channel;
|
||||
dsp: DSP;
|
||||
setDelay: SetDelayFn;
|
||||
onLiveValue: (channel: Channel, value: number) => void;
|
||||
}) {
|
||||
function getChannelState(channel: Channel) {
|
||||
if ('Input' in channel) {
|
||||
return dsp.state.current_config.input_states[channel.Input];
|
||||
} else {
|
||||
return dsp.state.current_config.output_states[channel.Output];
|
||||
}
|
||||
}
|
||||
|
||||
const state = getChannelState(channel);
|
||||
const unit = dsp.state.current_config.delay_unit;
|
||||
const [min, max] = DELAY_RANGES[unit];
|
||||
|
||||
return (
|
||||
<div className="delay-panel">
|
||||
<div className="delay-panel__title">{state.name}</div>
|
||||
|
||||
<VerticalSlider
|
||||
min={min}
|
||||
max={max}
|
||||
step={DELAY_STEP[unit]}
|
||||
unit={' ' + UNIT_LABEL[unit]}
|
||||
value={state.delay}
|
||||
onInput={(value) => onLiveValue(channel, value)}
|
||||
onChange={(value) => setDelay(channel, value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DelayGraph({
|
||||
title,
|
||||
rows,
|
||||
unit,
|
||||
}: {
|
||||
title: string;
|
||||
rows: { label: string; delay: number }[];
|
||||
unit: DelayUnit;
|
||||
}) {
|
||||
const [min, max] = DELAY_RANGES[unit];
|
||||
const labelByX = useMemo(() => {
|
||||
const map = new Map<number, string>();
|
||||
rows.forEach((row, i) => map.set(i + 1, row.label));
|
||||
return map;
|
||||
}, [rows]);
|
||||
|
||||
return (
|
||||
<div className="delay-graph-panel">
|
||||
<div className="delay-graph-title">{title}</div>
|
||||
<LinearGraph
|
||||
xMin={0.5}
|
||||
xMax={rows.length + 0.5}
|
||||
xStep={1}
|
||||
yMin={min}
|
||||
yMax={max}
|
||||
xLabel="Channel"
|
||||
yLabel={UNIT_LABEL[unit]}
|
||||
showGrid={true}
|
||||
formatX={(v) => labelByX.get(Math.round(v)) ?? ''}
|
||||
formatY={(v) => v.toFixed(0)}
|
||||
draw={({ ctx, toPx, plot }) => {
|
||||
const barWidth = (plot.width / rows.length) * 0.55;
|
||||
|
||||
rows.forEach((row, i) => {
|
||||
const x = i + 1;
|
||||
const [xPx, baseY] = toPx(x, min);
|
||||
const [, topY] = toPx(x, row.delay);
|
||||
|
||||
ctx.fillStyle = '#4ade80';
|
||||
ctx.fillRect(xPx - barWidth / 2, topY, barWidth, baseY - topY);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DelayUnitSelector({
|
||||
unit,
|
||||
onSelect,
|
||||
}: {
|
||||
unit: DelayUnit;
|
||||
onSelect: (unit: DelayUnit) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="delay-unit-selector">
|
||||
{UNIT_ORDER.map((u) => (
|
||||
<Button
|
||||
key={u}
|
||||
variant="toggle"
|
||||
className={u === unit ? 'active-info' : ''}
|
||||
onClick={() => onSelect(u)}
|
||||
style={{
|
||||
width: '100%',
|
||||
fontSize: '3.5cqh',
|
||||
paddingTop: '2cqh',
|
||||
paddingBottom: '2cqh',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{UNIT_LABEL[u]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DelaySection({
|
||||
dsp,
|
||||
setDelay,
|
||||
setDelayUnit,
|
||||
setDSPState,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
setDelay: SetDelayFn;
|
||||
setDelayUnit: (unit: DelayUnit) => Promise<boolean>;
|
||||
setDSPState: (updater: (dsp: DSP) => DSP) => void;
|
||||
}) {
|
||||
const unit = dsp.state?.current_config.delay_unit ?? DelayUnit.Millisecond;
|
||||
|
||||
const inputs: InputChannel[] = [
|
||||
InputChannel.InA,
|
||||
InputChannel.InB,
|
||||
InputChannel.InC,
|
||||
InputChannel.InD,
|
||||
];
|
||||
|
||||
const outputs: { channel: Channel; title: string }[] = [
|
||||
{ channel: { Output: OutputChannel.Out1 }, title: 'Out 1' },
|
||||
{ channel: { Output: OutputChannel.Out2 }, title: 'Out 2' },
|
||||
{ channel: { Output: OutputChannel.Out3 }, title: 'Out 3' },
|
||||
{ channel: { Output: OutputChannel.Out4 }, title: 'Out 4' },
|
||||
{ channel: { Output: OutputChannel.Out5 }, title: 'Out 5' },
|
||||
{ channel: { Output: OutputChannel.Out6 }, title: 'Out 6' },
|
||||
{ channel: { Output: OutputChannel.Out7 }, title: 'Out 7' },
|
||||
{ channel: { Output: OutputChannel.Out8 }, title: 'Out 8' },
|
||||
];
|
||||
|
||||
const [liveDelay, setLiveDelay] = useState<Partial<Record<string, number>>>({});
|
||||
|
||||
const handleLiveValue = (channel: Channel, value: number) => {
|
||||
setLiveDelay((prev) => ({
|
||||
...prev,
|
||||
[channelKey(channel)]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSetDelay: SetDelayFn = async (channel, delay) => {
|
||||
const success = await setDelay(channel, delay);
|
||||
|
||||
if (success) {
|
||||
const key = channelKey(channel);
|
||||
|
||||
setLiveDelay((prev) => {
|
||||
if (!(key in prev)) return prev;
|
||||
|
||||
const next = { ...prev };
|
||||
delete next[key];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
const committedDelay = (channel: Channel): number =>
|
||||
'Input' in channel
|
||||
? dsp.state.current_config.input_states[channel.Input].delay
|
||||
: dsp.state.current_config.output_states[channel.Output].delay;
|
||||
|
||||
const getDelay = (channel: Channel): number => {
|
||||
const key = channelKey(channel);
|
||||
|
||||
if (key in liveDelay) {
|
||||
return liveDelay[key]!;
|
||||
}
|
||||
|
||||
return committedDelay(channel);
|
||||
};
|
||||
|
||||
function convertDelayUnit(value: number, from: DelayUnit, to: DelayUnit): number {
|
||||
const [, fromMax] = DELAY_RANGES[from];
|
||||
const [, toMax] = DELAY_RANGES[to];
|
||||
|
||||
if (fromMax === 0) return 0;
|
||||
|
||||
const ratio = value / fromMax;
|
||||
|
||||
return ratio * toMax;
|
||||
}
|
||||
|
||||
const handleUnitChange = async (newUnit: DelayUnit) => {
|
||||
if (newUnit === unit) return;
|
||||
|
||||
const success = await setDelayUnit(newUnit);
|
||||
|
||||
if (!success) return;
|
||||
|
||||
setDSPState((d) => {
|
||||
if (!d.state) return d;
|
||||
|
||||
const currentConfig = d.state.current_config;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
delay_unit: newUnit,
|
||||
|
||||
input_states: {
|
||||
...currentConfig.input_states,
|
||||
[InputChannel.InA]: {
|
||||
...currentConfig.input_states[InputChannel.InA],
|
||||
delay: convertDelayUnit(
|
||||
currentConfig.input_states[InputChannel.InA].delay,
|
||||
unit,
|
||||
newUnit
|
||||
),
|
||||
},
|
||||
[InputChannel.InB]: {
|
||||
...currentConfig.input_states[InputChannel.InB],
|
||||
delay: convertDelayUnit(
|
||||
currentConfig.input_states[InputChannel.InB].delay,
|
||||
unit,
|
||||
newUnit
|
||||
),
|
||||
},
|
||||
[InputChannel.InC]: {
|
||||
...currentConfig.input_states[InputChannel.InC],
|
||||
delay: convertDelayUnit(
|
||||
currentConfig.input_states[InputChannel.InC].delay,
|
||||
unit,
|
||||
newUnit
|
||||
),
|
||||
},
|
||||
[InputChannel.InD]: {
|
||||
...currentConfig.input_states[InputChannel.InD],
|
||||
delay: convertDelayUnit(
|
||||
currentConfig.input_states[InputChannel.InD].delay,
|
||||
unit,
|
||||
newUnit
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
output_states: {
|
||||
...currentConfig.output_states,
|
||||
...Object.fromEntries(
|
||||
Object.values(OutputChannel).map((channel) => [
|
||||
channel,
|
||||
{
|
||||
...currentConfig.output_states[channel],
|
||||
delay: convertDelayUnit(
|
||||
currentConfig.output_states[channel].delay,
|
||||
unit,
|
||||
newUnit
|
||||
),
|
||||
},
|
||||
])
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const inputRows = inputs.map((channel) => ({
|
||||
label: dsp.state.current_config.input_states[channel].name,
|
||||
delay: getDelay({ Input: channel }),
|
||||
}));
|
||||
|
||||
const outputRows = outputs.map(({ channel, title }) => ({
|
||||
label: title,
|
||||
delay: getDelay(channel),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="delay-linear-graph-section">
|
||||
<DelayGraph title="Input Delay" rows={inputRows} unit={unit} />
|
||||
<DelayGraph title="Output Delay" rows={outputRows} unit={unit} />
|
||||
</div>
|
||||
|
||||
<div className="delay-section">
|
||||
<div className="delay-panels">
|
||||
<ChannelGroup title="Input">
|
||||
{inputs.map((channel) => (
|
||||
<DelayPanel
|
||||
key={channel}
|
||||
channel={{ Input: channel }}
|
||||
dsp={dsp}
|
||||
setDelay={handleSetDelay}
|
||||
onLiveValue={handleLiveValue}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Output">
|
||||
{outputs.map(({ channel, title }) => (
|
||||
<DelayPanel
|
||||
key={title}
|
||||
channel={channel}
|
||||
dsp={dsp}
|
||||
setDelay={handleSetDelay}
|
||||
onLiveValue={handleLiveValue}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Unit">
|
||||
<DelayUnitSelector unit={unit} onSelect={handleUnitChange} />
|
||||
</ChannelGroup>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DelaySection;
|
||||
@@ -91,7 +91,11 @@ export default function VerticalSlider({
|
||||
}
|
||||
|
||||
const decimals = useMemo(
|
||||
() => Math.max(getDecimals(step), switchStep !== undefined ? getDecimals(switchStep) : 0),
|
||||
() =>
|
||||
Math.min(
|
||||
3,
|
||||
Math.max(getDecimals(step), switchStep !== undefined ? getDecimals(switchStep) : 0)
|
||||
),
|
||||
[step, switchStep]
|
||||
);
|
||||
|
||||
|
||||
+117
-44
@@ -11,6 +11,7 @@ import {
|
||||
defaultMeters,
|
||||
OutputChannel,
|
||||
Ratio,
|
||||
DelayUnit,
|
||||
} from '../types/dsp408State';
|
||||
|
||||
import {
|
||||
@@ -28,15 +29,13 @@ type NotifyFunction = (message: string, type: 'success' | 'error', dspName?: str
|
||||
|
||||
type SetDsps = React.Dispatch<React.SetStateAction<DSP[]>>;
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
function updateDSP(setDsps: SetDsps, dspId: number, updater: (dsp: DSP) => DSP) {
|
||||
setDsps((prev) => prev.map((dsp) => (dsp.id === dspId ? updater(dsp) : dsp)));
|
||||
}
|
||||
|
||||
export type DSP408Device = {
|
||||
setDSPState: (updater: (dsp: DSP) => DSP) => void;
|
||||
connect: () => Promise<void>;
|
||||
disconnect: () => Promise<void>;
|
||||
recallPreset: (index: number) => Promise<boolean>;
|
||||
@@ -65,12 +64,11 @@ export type DSP408Device = {
|
||||
attack: number,
|
||||
release: number
|
||||
) => Promise<boolean>;
|
||||
setDelayUnit: (unit: DelayUnit) => Promise<boolean>;
|
||||
setDelay: (channel: Channel, delay: number) => Promise<boolean>;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Polling
|
||||
// ============================================================
|
||||
|
||||
function useDSPPolling(
|
||||
dsps: DSP[],
|
||||
selected: number | null,
|
||||
@@ -165,10 +163,6 @@ function useDSPPolling(
|
||||
}, [setDsps]);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Main hook
|
||||
// ============================================================
|
||||
|
||||
export function useDSP408(
|
||||
dsps: DSP[],
|
||||
setDsps: SetDsps,
|
||||
@@ -177,10 +171,7 @@ export function useDSP408(
|
||||
setPage: React.Dispatch<React.SetStateAction<AppPage>>,
|
||||
notify: NotifyFunction
|
||||
) {
|
||||
// ----------------------------------------------------------
|
||||
// Polling control
|
||||
// ----------------------------------------------------------
|
||||
|
||||
const setPollingPaused = (id: number, paused: boolean) => {
|
||||
updateDSP(setDsps, id, (dsp) => ({
|
||||
...dsp,
|
||||
@@ -188,13 +179,14 @@ export function useDSP408(
|
||||
}));
|
||||
};
|
||||
|
||||
function setDSPState(dsp: DSP, updater: (dsp: DSP) => DSP) {
|
||||
updateDSP(setDsps, dsp.id, updater);
|
||||
}
|
||||
|
||||
// Polling is part of the hook.
|
||||
useDSPPolling(dsps, selected, setDsps);
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Generic operation wrapper
|
||||
// ----------------------------------------------------------
|
||||
|
||||
async function withPollingPaused<T>(dsp: DSP, operation: () => Promise<T>): Promise<T> {
|
||||
setPollingPaused(dsp.id, true);
|
||||
|
||||
@@ -205,10 +197,7 @@ export function useDSP408(
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Connection
|
||||
// ==========================================================
|
||||
|
||||
async function connectDSP(dsp: DSP) {
|
||||
setPollingPaused(dsp.id, true);
|
||||
|
||||
@@ -261,10 +250,7 @@ export function useDSP408(
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Add / Remove
|
||||
// ==========================================================
|
||||
|
||||
async function addDSP(dsp: Omit<DSP, 'id' | 'status' | 'state' | 'meters' | 'pollingPaused'>) {
|
||||
try {
|
||||
const id = await createDSP408(dsp.ip, dsp.port, dsp.deviceId);
|
||||
@@ -312,10 +298,7 @@ export function useDSP408(
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Presets
|
||||
// ==========================================================
|
||||
|
||||
async function recallPreset(dsp: DSP, presetIndex: number) {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
@@ -398,10 +381,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Gain
|
||||
// ==========================================================
|
||||
|
||||
async function setGain(dsp: DSP, channel: Channel, gain: number): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
@@ -435,10 +415,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Mute
|
||||
// ==========================================================
|
||||
|
||||
async function setMute(dsp: DSP, channel: Channel, muted: boolean): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
@@ -511,10 +488,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Inverse
|
||||
// ==========================================================
|
||||
|
||||
async function setInverse(dsp: DSP, channel: Channel, inverted: boolean): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
@@ -587,9 +561,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Gate
|
||||
// ==========================================================
|
||||
async function setGate(
|
||||
dsp: DSP,
|
||||
inputChannel: InputChannel,
|
||||
@@ -641,9 +613,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Compressor
|
||||
// ==========================================================
|
||||
async function setCompressor(
|
||||
dsp: DSP,
|
||||
outputChannel: OutputChannel,
|
||||
@@ -708,9 +678,7 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Limiter
|
||||
// ==========================================================
|
||||
async function setLimiter(
|
||||
dsp: DSP,
|
||||
outputChannel: OutputChannel,
|
||||
@@ -769,12 +737,115 @@ export function useDSP408(
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Return API
|
||||
// ==========================================================
|
||||
// Delay Unit
|
||||
async function setDelayUnit(dsp: DSP, unit: DelayUnit): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_delay_unit', {
|
||||
id: dsp.id,
|
||||
unit,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
notify('Failed to change delay unit', 'error', dsp.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateDSP(setDsps, dsp.id, (d) => {
|
||||
if (!d.state) return d;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...d.state.current_config,
|
||||
delay_unit: unit,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
notify('Delay unit updated', 'success', dsp.name);
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(`Delay unit update failed: ${String(err)}`, 'error', dsp.name);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Channel Delay
|
||||
async function setDelay(dsp: DSP, channel: Channel, delay: number): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_delay', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
delay,
|
||||
unit: dsp.state.current_config.delay_unit,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
notify('Failed to change channel delay', 'error', dsp.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateDSP(setDsps, dsp.id, (d) => {
|
||||
if (!d.state) return d;
|
||||
|
||||
const currentConfig = d.state.current_config;
|
||||
|
||||
if ('Input' in channel) {
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
input_states: {
|
||||
...currentConfig.input_states,
|
||||
[channel.Input]: {
|
||||
...currentConfig.input_states[channel.Input],
|
||||
delay,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
output_states: {
|
||||
...currentConfig.output_states,
|
||||
[channel.Output]: {
|
||||
...currentConfig.output_states[channel.Output],
|
||||
delay,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
notify('Channel delay updated', 'success', dsp.name);
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(`Channel delay update failed: ${String(err)}`, 'error', dsp.name);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Return API
|
||||
function forDSP(dsp: DSP): DSP408Device {
|
||||
return {
|
||||
setDSPState: (updater: (dsp: DSP) => DSP) => setDSPState(dsp, updater),
|
||||
connect: () => connectDSP(dsp),
|
||||
disconnect: () => disconnectDSP(dsp),
|
||||
recallPreset: (index: number) => recallPreset(dsp, index),
|
||||
@@ -803,6 +874,8 @@ export function useDSP408(
|
||||
attack: number,
|
||||
release: number
|
||||
) => setLimiter(dsp, outputChannel, threshold, attack, release),
|
||||
setDelayUnit: (unit: DelayUnit) => setDelayUnit(dsp, unit),
|
||||
setDelay: (channel: Channel, delay: number) => setDelay(dsp, channel, delay),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -818,6 +891,6 @@ export function useDSP408(
|
||||
recallPreset,
|
||||
setCurrentPresetName,
|
||||
|
||||
setGain,
|
||||
setDSPState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { DSP408Device } from '../../hooks/useDSP408';
|
||||
import './DspPage.css';
|
||||
import GateSection from '../../components/dsp408/GateSection/GateSection';
|
||||
import LimiterSection from '../../components/dsp408/LimiterSection/LimiterSection';
|
||||
import DelaySection from '../../components/dsp408/DelaySection/DelaySection';
|
||||
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
||||
import { InputChannel, OutputChannel } from '../../types/dsp408State';
|
||||
|
||||
@@ -187,6 +188,16 @@ function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
||||
case 'limiter':
|
||||
return <LimiterSection dsp={dsp} set_limiter={dsp408.setLimiter} />;
|
||||
|
||||
case 'delay':
|
||||
return (
|
||||
<DelaySection
|
||||
dsp={dsp}
|
||||
setDelay={dsp408.setDelay}
|
||||
setDelayUnit={dsp408.setDelayUnit}
|
||||
setDSPState={dsp408.setDSPState}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user