feat: dont moove slider cursor if tcp command fails
This commit is contained in:
14
src/App.tsx
14
src/App.tsx
@ -18,14 +18,7 @@ function App() {
|
||||
const activeDsp = dsps.find((d) => d.id === selected) ?? null;
|
||||
const connectedDspCount = dsps.filter((dsp) => dsp.status === 'connected').length;
|
||||
|
||||
const dsp408 = useDSP408(
|
||||
dsps,
|
||||
setDsps,
|
||||
selected,
|
||||
setSelected,
|
||||
setPage,
|
||||
notify
|
||||
);
|
||||
const dsp408 = useDSP408(dsps, setDsps, selected, setSelected, setPage, notify);
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
@ -60,10 +53,7 @@ function App() {
|
||||
) : page === 'add-dsp' ? (
|
||||
<AddDSPForm onCancel={() => setPage('home')} onAdd={dsp408.addDSP} />
|
||||
) : activeDsp ? (
|
||||
<DSPPage
|
||||
dsp={activeDsp}
|
||||
dsp408={dsp408.forDSP(activeDsp)}
|
||||
/>
|
||||
<DSPPage dsp={activeDsp} dsp408={dsp408.forDSP(activeDsp)} />
|
||||
) : (
|
||||
<HomePage
|
||||
onAddDSP={() => setPage('add-dsp')}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import VerticalSlider from '../Slider/VerticalSlider';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
import { DSP } from '../../../types/types';
|
||||
import { NotificationType } from '../../../types/types';
|
||||
import Button from '../../Button/Button';
|
||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
||||
@ -12,109 +10,62 @@ import './GainSection.css';
|
||||
const PANEL_DESIGN_WIDTH = 90;
|
||||
const PANEL_DESIGN_HEIGHT = 400;
|
||||
|
||||
function ChannelGroupScaled({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
// function ChannelGroupScaled({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
// const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
// const [scale, setScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const el = wrapperRef.current;
|
||||
if (!el) return;
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const { height } = entries[0].contentRect;
|
||||
if (height === 0) return;
|
||||
setScale(height / PANEL_DESIGN_HEIGHT); // height is the only free axis here
|
||||
});
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// const el = wrapperRef.current;
|
||||
// if (!el) return;
|
||||
// const observer = new ResizeObserver((entries) => {
|
||||
// const { height } = entries[0].contentRect;
|
||||
// if (height === 0) return;
|
||||
// setScale(height / PANEL_DESIGN_HEIGHT); // height is the only free axis here
|
||||
// });
|
||||
// observer.observe(el);
|
||||
// return () => observer.disconnect();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} style={{ height: '100%', '--panel-scale': scale } as React.CSSProperties}>
|
||||
<ChannelGroup title={title}>{children}</ChannelGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div ref={wrapperRef} style={{ height: '100%', '--panel-scale': scale } as React.CSSProperties}>
|
||||
// <ChannelGroup title={title}>{children}</ChannelGroup>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
function GainPanel({
|
||||
dsp,
|
||||
notify,
|
||||
channel,
|
||||
title,
|
||||
setGain,
|
||||
setMute,
|
||||
setInverse,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
channel: Channel;
|
||||
title: string;
|
||||
setGain: (channel: Channel, gain: number) => Promise<boolean>;
|
||||
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
|
||||
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
|
||||
}) {
|
||||
const setGain = async (value: number) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_channel_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
db: value,
|
||||
});
|
||||
const channelState =
|
||||
'Input' in channel
|
||||
? dsp.state?.current_config.input_states[channel.Input]
|
||||
: dsp.state?.current_config.output_states[channel.Output];
|
||||
|
||||
if (success) {
|
||||
notify(`Input A gain set to ${value} dB`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to set input gain', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Gain update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
const muted = channelState?.mute ?? false;
|
||||
const inverted = channelState?.phase_inverted ?? false;
|
||||
const gain = channelState?.gain ?? 0;
|
||||
|
||||
const setMute = async (muted: boolean) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_mute', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
muted,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
notify(`Input A ${muted ? 'muted' : 'unmuted'}`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to change mute state', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Mute update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
|
||||
const setInverse = async (inverted: boolean) => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_channel_inverse_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
inverted,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
notify(`Input A phase ${inverted ? 'inverted' : 'normal'}`, 'success', dsp.name);
|
||||
} else {
|
||||
notify('Failed to change phase', 'error', dsp.name);
|
||||
}
|
||||
} catch (err) {
|
||||
notify(`Phase update failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
};
|
||||
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [inverted, setInverted] = useState(false);
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
|
||||
const toggleMute = async () => {
|
||||
const next = !muted;
|
||||
setMuted(next);
|
||||
await setMute(next);
|
||||
await setMute(channel, !muted);
|
||||
};
|
||||
|
||||
const toggleInverse = async () => {
|
||||
const next = !inverted;
|
||||
setInverted(next);
|
||||
await setInverse(next);
|
||||
await setInverse(channel, !inverted);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -148,7 +99,8 @@ function GainPanel({
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={setGain}
|
||||
value={gain}
|
||||
onChange={(value) => setGain(channel, value)}
|
||||
style={{ width: 70, height: 260, flex: '0 0 auto' }} // pin to design size, disable its own auto-shrink
|
||||
/>
|
||||
|
||||
@ -169,10 +121,14 @@ function GainPanel({
|
||||
|
||||
function GainSection({
|
||||
dsp,
|
||||
notify,
|
||||
setGain,
|
||||
setMute,
|
||||
setInverse,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
setGain: (channel: Channel, gain: number) => Promise<boolean>;
|
||||
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
|
||||
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
|
||||
}) {
|
||||
const inputs: { channel: Channel; title: string }[] = [
|
||||
{ channel: { Input: InputChannel.InA }, title: 'In A' },
|
||||
@ -223,13 +179,29 @@ function GainSection({
|
||||
<div className="gain-panels">
|
||||
<ChannelGroup title="Input">
|
||||
{inputs.map(({ channel, title }) => (
|
||||
<GainPanel key={title} dsp={dsp} notify={notify} channel={channel} title={title} />
|
||||
<GainPanel
|
||||
key={title}
|
||||
dsp={dsp}
|
||||
channel={channel}
|
||||
title={title}
|
||||
setGain={setGain}
|
||||
setMute={setMute}
|
||||
setInverse={setInverse}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
|
||||
<ChannelGroup title="Output">
|
||||
{outputs.map(({ channel, title }) => (
|
||||
<GainPanel key={title} dsp={dsp} notify={notify} channel={channel} title={title} />
|
||||
<GainPanel
|
||||
key={title}
|
||||
dsp={dsp}
|
||||
channel={channel}
|
||||
title={title}
|
||||
setGain={setGain}
|
||||
setMute={setMute}
|
||||
setInverse={setInverse}
|
||||
/>
|
||||
))}
|
||||
</ChannelGroup>
|
||||
</div>
|
||||
|
||||
@ -1,35 +1,35 @@
|
||||
import VerticalSlider from '../Slider/VerticalSlider';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
import { DSP } from '../../../types/types';
|
||||
import { NotificationType } from '../../../types/types';
|
||||
// import VerticalSlider from '../Slider/VerticalSlider';
|
||||
// import { invoke } from '@tauri-apps/api/core';
|
||||
// import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||
// import { DSP } from '../../../types/types';
|
||||
// import { NotificationType } from '../../../types/types';
|
||||
|
||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
import './GateSection.css';
|
||||
// import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||
// import './GateSection.css';
|
||||
|
||||
function GateSection({
|
||||
dsp,
|
||||
notify,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
}) {
|
||||
console.log(dsp, notify);
|
||||
return (
|
||||
<div className="rerere">
|
||||
<VerticalSlider
|
||||
min={-60}
|
||||
max={12}
|
||||
step={0.5}
|
||||
switchStepValue={-10}
|
||||
switchStep={0.1}
|
||||
unit=" dB"
|
||||
onChange={(_f: number) => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// function GateSection({
|
||||
// dsp,
|
||||
// notify,
|
||||
// }: {
|
||||
// dsp: DSP;
|
||||
// notify: (message: string, type?: NotificationType, dspName?: string) => void;
|
||||
// }) {
|
||||
// console.log(dsp, notify);
|
||||
// return (
|
||||
// <div className="rerere">
|
||||
// <VerticalSlider
|
||||
// min={-60}
|
||||
// max={12}
|
||||
// step={0.5}
|
||||
// switchStepValue={-10}
|
||||
// switchStep={0.1}
|
||||
// unit=" dB"
|
||||
// onChange={(_f: number) => {
|
||||
// return;
|
||||
// }}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
export default GateSection;
|
||||
// export default GateSection;
|
||||
|
||||
@ -13,8 +13,8 @@ type VerticalSliderProps = {
|
||||
switchStepValue?: number;
|
||||
switchStep?: number;
|
||||
|
||||
value?: number;
|
||||
onChange?: (value: number) => void;
|
||||
value: number;
|
||||
onChange?: (value: number) => Promise<boolean>;
|
||||
|
||||
unit?: string;
|
||||
|
||||
@ -34,23 +34,18 @@ export default function VerticalSlider({
|
||||
step,
|
||||
switchStepValue,
|
||||
switchStep,
|
||||
value: controlledValue,
|
||||
value,
|
||||
onChange,
|
||||
unit = '',
|
||||
className,
|
||||
style,
|
||||
}: VerticalSliderProps) {
|
||||
const [internalValue, setInternalValue] = useState(min);
|
||||
const value = controlledValue ?? internalValue;
|
||||
|
||||
const [editingText, setEditingText] = useState<string | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const lastValue = useRef(value);
|
||||
const keyboardValue = useRef<number | null>(null);
|
||||
const [displayValue, setDisplayValue] = useState(value);
|
||||
const dragValue = useRef(value);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
const dragging = useRef(false);
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
const keyboardEditing = useRef(false);
|
||||
@ -74,16 +69,20 @@ export default function VerticalSlider({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dragging.current) return;
|
||||
if (keyboardEditing.current) return;
|
||||
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}, [value]);
|
||||
|
||||
const currentStep =
|
||||
switchStepValue !== undefined && switchStep !== undefined && value >= switchStepValue
|
||||
? switchStep
|
||||
: step;
|
||||
function getStep(value: number) {
|
||||
if (switchStepValue !== undefined && switchStep !== undefined && value >= switchStepValue) {
|
||||
return switchStep;
|
||||
}
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
const decimals = useMemo(
|
||||
() => Math.max(getDecimals(step), switchStep !== undefined ? getDecimals(switchStep) : 0),
|
||||
@ -92,30 +91,25 @@ export default function VerticalSlider({
|
||||
|
||||
function normalizeValue(input: number) {
|
||||
const clamped = Math.min(max, Math.max(min, input));
|
||||
const snapped = Math.round(clamped / currentStep) * currentStep;
|
||||
return Number(snapped.toFixed(6));
|
||||
|
||||
const currentStep = getStep(clamped);
|
||||
|
||||
const snapped = min + Math.round((clamped - min) / currentStep) * currentStep;
|
||||
|
||||
return Number(Math.min(max, Math.max(min, snapped)).toFixed(6));
|
||||
}
|
||||
|
||||
function updateValue(v: number) {
|
||||
const next = normalizeValue(v);
|
||||
function updateValue(input: number) {
|
||||
const next = normalizeValue(input);
|
||||
|
||||
dragValue.current = next;
|
||||
lastValue.current = next;
|
||||
|
||||
if (thumbRef.current) {
|
||||
const percent = (next - min) / (max - min);
|
||||
thumbRef.current.style.bottom = `${percent * 100}%`;
|
||||
}
|
||||
|
||||
if (rafRef.current === null) {
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
setDisplayValue(dragValue.current);
|
||||
rafRef.current = null;
|
||||
});
|
||||
}
|
||||
setDisplayValue(next);
|
||||
}
|
||||
|
||||
function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return;
|
||||
if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboardEditing.current = true;
|
||||
|
||||
@ -123,33 +117,39 @@ export default function VerticalSlider({
|
||||
|
||||
const base = keyboardValue.current ?? value;
|
||||
|
||||
const currentStep = getStep(base);
|
||||
|
||||
let next = base;
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') next += currentStep;
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
|
||||
next += currentStep;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') next -= currentStep;
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
|
||||
next -= currentStep;
|
||||
}
|
||||
|
||||
next = normalizeValue(next);
|
||||
|
||||
keyboardValue.current = next;
|
||||
|
||||
updateValue(next); // updates displayValue only
|
||||
updateValue(next);
|
||||
}
|
||||
|
||||
function handleKeyUp() {
|
||||
async function handleKeyUp() {
|
||||
if (keyboardValue.current == null) return;
|
||||
|
||||
keyboardEditing.current = false;
|
||||
|
||||
const next = keyboardValue.current;
|
||||
|
||||
if (controlledValue === undefined) {
|
||||
setInternalValue(next);
|
||||
}
|
||||
|
||||
onChange?.(next);
|
||||
const success = await onChange?.(next);
|
||||
|
||||
keyboardEditing.current = false;
|
||||
keyboardValue.current = null;
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
}
|
||||
|
||||
function valueFromPointer(clientY: number) {
|
||||
@ -172,28 +172,47 @@ export default function VerticalSlider({
|
||||
valueFromPointer(e.clientY);
|
||||
}
|
||||
|
||||
function endDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||
dragging.current = false;
|
||||
async function endDrag(e: React.PointerEvent<HTMLDivElement>) {
|
||||
if (!dragging.current) return;
|
||||
|
||||
const finalValue = dragValue.current;
|
||||
|
||||
if (controlledValue === undefined) setInternalValue(finalValue);
|
||||
setDisplayValue(finalValue);
|
||||
onChange?.(finalValue);
|
||||
const success = await onChange?.(finalValue);
|
||||
|
||||
dragging.current = false;
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
}
|
||||
}
|
||||
|
||||
function commitEditing() {
|
||||
async function commitEditing() {
|
||||
if (editingText !== null && editingText !== '' && editingText !== '-') {
|
||||
const parsed = Number(editingText);
|
||||
if (!Number.isNaN(parsed)) updateValue(parsed);
|
||||
|
||||
if (!Number.isNaN(parsed)) {
|
||||
const next = normalizeValue(parsed);
|
||||
|
||||
setDisplayValue(next);
|
||||
|
||||
const success = await onChange?.(next);
|
||||
|
||||
if (success === false) {
|
||||
setDisplayValue(value);
|
||||
dragValue.current = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setEditingText(null);
|
||||
}
|
||||
|
||||
const percent = (value - min) / (max - min);
|
||||
const percent = (displayValue - min) / (max - min);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
import { DSP, DSPStatus, AppPage } from '../types/types';
|
||||
import { DSP, AppPage } from '../types/types';
|
||||
import { Channel } from '../types/dsp408State';
|
||||
|
||||
import {
|
||||
createDSP408,
|
||||
@ -16,11 +17,7 @@ import {
|
||||
getDSP408MeterLevels,
|
||||
} from '../api/dsp408';
|
||||
|
||||
type NotifyFunction = (
|
||||
message: string,
|
||||
type: 'success' | 'error',
|
||||
dspName?: string
|
||||
) => void;
|
||||
type NotifyFunction = (message: string, type: 'success' | 'error', dspName?: string) => void;
|
||||
|
||||
type SetDsps = React.Dispatch<React.SetStateAction<DSP[]>>;
|
||||
|
||||
@ -28,14 +25,8 @@ 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))
|
||||
);
|
||||
function updateDSP(setDsps: SetDsps, dspId: number, updater: (dsp: DSP) => DSP) {
|
||||
setDsps((prev) => prev.map((dsp) => (dsp.id === dspId ? updater(dsp) : dsp)));
|
||||
}
|
||||
|
||||
export type DSP408Device = {
|
||||
@ -43,6 +34,9 @@ export type DSP408Device = {
|
||||
disconnect: () => Promise<void>;
|
||||
recallPreset: (index: number) => Promise<boolean>;
|
||||
setCurrentPresetName: (name: string) => Promise<boolean>;
|
||||
setGain: (channel: Channel, gain: number) => Promise<boolean>;
|
||||
setMute: (channel: Channel, muted: boolean) => Promise<boolean>;
|
||||
setInverse: (channel: Channel, inverted: boolean) => Promise<boolean>;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
@ -173,10 +167,7 @@ export function useDSP408(
|
||||
// Generic operation wrapper
|
||||
// ----------------------------------------------------------
|
||||
|
||||
async function withPollingPaused<T>(
|
||||
dsp: DSP,
|
||||
operation: () => Promise<T>
|
||||
): Promise<T> {
|
||||
async function withPollingPaused<T>(dsp: DSP, operation: () => Promise<T>): Promise<T> {
|
||||
setPollingPaused(dsp.id, true);
|
||||
|
||||
try {
|
||||
@ -216,11 +207,7 @@ export function useDSP408(
|
||||
status: 'disconnected',
|
||||
}));
|
||||
|
||||
notify(
|
||||
`Connection failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Connection failed: ${String(err)}`, 'error', dsp.name);
|
||||
} finally {
|
||||
setPollingPaused(dsp.id, false);
|
||||
}
|
||||
@ -242,11 +229,7 @@ export function useDSP408(
|
||||
// If disconnect failed, it is probably still connected.
|
||||
setPollingPaused(dsp.id, false);
|
||||
|
||||
notify(
|
||||
`Disconnect failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Disconnect failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,18 +237,9 @@ export function useDSP408(
|
||||
// Add / Remove
|
||||
// ==========================================================
|
||||
|
||||
async function addDSP(
|
||||
dsp: Omit<
|
||||
DSP,
|
||||
'id' | 'status' | 'state' | 'meters' | 'pollingPaused'
|
||||
>
|
||||
) {
|
||||
async function addDSP(dsp: Omit<DSP, 'id' | 'status' | 'state' | 'meters' | 'pollingPaused'>) {
|
||||
try {
|
||||
const id = await createDSP408(
|
||||
dsp.ip,
|
||||
dsp.port,
|
||||
dsp.deviceId
|
||||
);
|
||||
const id = await createDSP408(dsp.ip, dsp.port, dsp.deviceId);
|
||||
|
||||
const newDsp: DSP = {
|
||||
id,
|
||||
@ -282,11 +256,7 @@ export function useDSP408(
|
||||
|
||||
notify('Added successfully', 'success', dsp.name);
|
||||
} catch (err) {
|
||||
notify(
|
||||
`Connection failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Connection failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,32 +267,20 @@ export function useDSP408(
|
||||
await removeDSP408(dsp.id);
|
||||
|
||||
setDsps((prev) => {
|
||||
const next = prev.filter(
|
||||
(d) => d.id !== dsp.id
|
||||
);
|
||||
const next = prev.filter((d) => d.id !== dsp.id);
|
||||
|
||||
if (selected === dsp.id) {
|
||||
setSelected(
|
||||
next.length ? next[0].id : null
|
||||
);
|
||||
setSelected(next.length ? next[0].id : null);
|
||||
}
|
||||
|
||||
return next;
|
||||
});
|
||||
|
||||
notify(
|
||||
'Removed successfully',
|
||||
'success',
|
||||
dsp.name
|
||||
);
|
||||
notify('Removed successfully', 'success', dsp.name);
|
||||
} catch (err) {
|
||||
setPollingPaused(dsp.id, false);
|
||||
|
||||
notify(
|
||||
`Remove failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Remove failed: ${String(err)}`, 'error', dsp.name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,23 +288,13 @@ export function useDSP408(
|
||||
// Presets
|
||||
// ==========================================================
|
||||
|
||||
async function recallPreset(
|
||||
dsp: DSP,
|
||||
presetIndex: number
|
||||
) {
|
||||
async function recallPreset(dsp: DSP, presetIndex: number) {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await recallPresetDSP408(
|
||||
dsp.id,
|
||||
presetIndex
|
||||
);
|
||||
const success = await recallPresetDSP408(dsp.id, presetIndex);
|
||||
|
||||
if (!success) {
|
||||
notify(
|
||||
'Failed to recall preset',
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify('Failed to recall preset', 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -366,47 +314,26 @@ export function useDSP408(
|
||||
};
|
||||
});
|
||||
|
||||
notify(
|
||||
`Preset ${presetIndex} recalled`,
|
||||
'success',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Preset ${presetIndex} recalled`, 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(
|
||||
`Preset recall failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Preset recall failed: ${String(err)}`, 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function setCurrentPresetName(
|
||||
dsp: DSP,
|
||||
name: string
|
||||
) {
|
||||
async function setCurrentPresetName(dsp: DSP, name: string) {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const paddedName = name
|
||||
.slice(0, 14)
|
||||
.padEnd(14, ' ');
|
||||
const paddedName = name.slice(0, 14).padEnd(14, ' ');
|
||||
|
||||
const success =
|
||||
await DSP408SetCurrentPresetName(
|
||||
dsp.id,
|
||||
paddedName
|
||||
);
|
||||
const success = await DSP408SetCurrentPresetName(dsp.id, paddedName);
|
||||
|
||||
if (!success) {
|
||||
notify(
|
||||
'Failed to rename preset',
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify('Failed to rename preset', 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -423,36 +350,20 @@ export function useDSP408(
|
||||
...d.state,
|
||||
presets: {
|
||||
...presets,
|
||||
names: presets.names.map(
|
||||
(presetName, i) =>
|
||||
i === index
|
||||
? paddedName
|
||||
: presetName
|
||||
),
|
||||
modified: presets.modified.map(
|
||||
(modified, i) =>
|
||||
i === index
|
||||
? true
|
||||
: modified
|
||||
names: presets.names.map((presetName, i) =>
|
||||
i === index ? paddedName : presetName
|
||||
),
|
||||
modified: presets.modified.map((modified, i) => (i === index ? true : modified)),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
notify(
|
||||
'Preset renamed',
|
||||
'success',
|
||||
dsp.name
|
||||
);
|
||||
notify('Preset renamed', 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(
|
||||
`Preset rename failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Preset rename failed: ${String(err)}`, 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -463,28 +374,54 @@ export function useDSP408(
|
||||
// Gain
|
||||
// ==========================================================
|
||||
|
||||
async function setGain(
|
||||
dsp: DSP,
|
||||
channel: number,
|
||||
gain: number
|
||||
) {
|
||||
async function setGain(dsp: DSP, channel: Channel, gain: number): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await invoke<boolean>(
|
||||
'set_gain',
|
||||
{
|
||||
const success = await invoke<boolean>('set_channel_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
gain,
|
||||
}
|
||||
);
|
||||
db: gain,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
notify(
|
||||
'Failed to set gain',
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify('Failed to set gain', 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const state = await getDSP408State(dsp.id);
|
||||
|
||||
updateDSP(setDsps, dsp.id, (d) => ({
|
||||
...d,
|
||||
state,
|
||||
}));
|
||||
|
||||
notify('Gain updated', 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(`Gain update failed: ${String(err)}`, 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Mute
|
||||
// ==========================================================
|
||||
|
||||
async function setMute(dsp: DSP, channel: Channel, muted: boolean): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_mute', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
muted,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
notify('Failed to change mute state', 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -492,28 +429,130 @@ export function useDSP408(
|
||||
updateDSP(setDsps, dsp.id, (d) => {
|
||||
if (!d.state) return d;
|
||||
|
||||
const currentConfig = d.state.current_config;
|
||||
|
||||
if ('Input' in channel) {
|
||||
const inputChannel = channel.Input;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
// Update gain state here
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
input_states: {
|
||||
...currentConfig.input_states,
|
||||
[inputChannel]: {
|
||||
...currentConfig.input_states[inputChannel],
|
||||
mute: muted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const outputChannel = channel.Output;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
output_states: {
|
||||
...currentConfig.output_states,
|
||||
[outputChannel]: {
|
||||
...currentConfig.output_states[outputChannel],
|
||||
mute: muted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
notify(
|
||||
'Gain updated',
|
||||
'success',
|
||||
dsp.name
|
||||
);
|
||||
notify(muted ? 'Muted' : 'Unmuted', 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(
|
||||
`Gain update failed: ${String(err)}`,
|
||||
'error',
|
||||
dsp.name
|
||||
);
|
||||
notify(`Mute update failed: ${String(err)}`, 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// Inverse
|
||||
// ==========================================================
|
||||
|
||||
async function setInverse(dsp: DSP, channel: Channel, inverted: boolean): Promise<boolean> {
|
||||
return withPollingPaused(dsp, async () => {
|
||||
try {
|
||||
const success = await invoke<boolean>('set_channel_inverse_gain', {
|
||||
id: dsp.id,
|
||||
channel,
|
||||
inverted,
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
notify('Failed to change phase', '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) {
|
||||
const inputChannel = channel.Input;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
input_states: {
|
||||
...currentConfig.input_states,
|
||||
[inputChannel]: {
|
||||
...currentConfig.input_states[inputChannel],
|
||||
phase_inverted: inverted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const outputChannel = channel.Output;
|
||||
|
||||
return {
|
||||
...d,
|
||||
state: {
|
||||
...d.state,
|
||||
current_config: {
|
||||
...currentConfig,
|
||||
output_states: {
|
||||
...currentConfig.output_states,
|
||||
[outputChannel]: {
|
||||
...currentConfig.output_states[outputChannel],
|
||||
phase_inverted: inverted,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
notify(inverted ? 'Phase inverted' : 'Phase normal', 'success', dsp.name);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
notify(`Phase update failed: ${String(err)}`, 'error', dsp.name);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -529,10 +568,10 @@ export function useDSP408(
|
||||
connect: () => connectDSP(dsp),
|
||||
disconnect: () => disconnectDSP(dsp),
|
||||
recallPreset: (index: number) => recallPreset(dsp, index),
|
||||
setCurrentPresetName: (name: string) =>
|
||||
setCurrentPresetName(dsp, name),
|
||||
// setGain: (channel, gain) =>
|
||||
// setGain(dsp, channel, gain),
|
||||
setCurrentPresetName: (name: string) => setCurrentPresetName(dsp, name),
|
||||
setGain: (channel: Channel, gain: number) => setGain(dsp, channel, gain),
|
||||
setMute: (channel: Channel, muted: boolean) => setMute(dsp, channel, muted),
|
||||
setInverse: (channel: Channel, inverted: boolean) => setInverse(dsp, channel, inverted),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -6,17 +6,11 @@ import ConnectionPanel from '../../components/dsp408/ConnectionSection/Connectio
|
||||
import GainSection from '../../components/dsp408/GainSection/GainSection';
|
||||
import { DSP408Device } from '../../hooks/useDSP408';
|
||||
import './DspPage.css';
|
||||
import GateSection from '../../components/dsp408/GateSection/GateSection';
|
||||
// import GateSection from '../../components/dsp408/GateSection/GateSection';
|
||||
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
||||
import { InputChannel, OutputChannel } from '../../types/dsp408State';
|
||||
|
||||
function DSPPage({
|
||||
dsp,
|
||||
dsp408,
|
||||
}: {
|
||||
dsp: DSP;
|
||||
dsp408: DSP408Device;
|
||||
}) {
|
||||
function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
||||
const [activeSidebar, setActiveSidebar] = useState('overview');
|
||||
const [sidebarExpanded, setSidebarExpanded] = useState(false);
|
||||
const [selectedPreset, setSelectedPreset] = useState(dsp.state?.presets.current_index ?? 0);
|
||||
@ -170,10 +164,19 @@ function DSPPage({
|
||||
const renderTab = () => {
|
||||
switch (activeSidebar) {
|
||||
case 'overview':
|
||||
return <ConnectionPanel dsp={dsp} onConnect={dsp408.connect} onDisconnect={dsp408.disconnect} />;
|
||||
return (
|
||||
<ConnectionPanel dsp={dsp} onConnect={dsp408.connect} onDisconnect={dsp408.disconnect} />
|
||||
);
|
||||
|
||||
// case 'gain':
|
||||
// return <GainSection dsp={dsp} notify={notify} />;
|
||||
case 'gain':
|
||||
return (
|
||||
<GainSection
|
||||
dsp={dsp}
|
||||
setGain={dsp408.setGain}
|
||||
setMute={dsp408.setMute}
|
||||
setInverse={dsp408.setInverse}
|
||||
/>
|
||||
);
|
||||
|
||||
// case 'gate':
|
||||
// return <GateSection dsp={dsp} notify={notify} />;
|
||||
|
||||
Reference in New Issue
Block a user