1795 lines
45 KiB
TypeScript
1795 lines
45 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core';
|
|
import { DSP } from '../../types';
|
|
import {
|
|
Channel,
|
|
InputChannel,
|
|
OutputChannel,
|
|
Ratio,
|
|
DelayUnit,
|
|
DiscreteFrequency,
|
|
CrossoverFilter,
|
|
PEQFilter,
|
|
PEQBand,
|
|
InputSource,
|
|
Crossover,
|
|
} 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<DSP, { type: 'DSP408' }>;
|
|
|
|
export type DSP408Api = ReturnType<typeof forDSP408>;
|
|
|
|
// --- selection + polling state, private to this module -------------------
|
|
|
|
const selectedIds = new Set<number>();
|
|
const pollIntervals = new Map<number, ReturnType<typeof setInterval>>();
|
|
|
|
const ACTIVE_POLL_INTERVAL_MS = 1000;
|
|
const BACKGROUND_POLL_INTERVAL_MS = 5000;
|
|
|
|
function pollIntervalFor(id: number): number {
|
|
return selectedIds.has(id) ? ACTIVE_POLL_INTERVAL_MS : BACKGROUND_POLL_INTERVAL_MS;
|
|
}
|
|
|
|
async function poll(dsp: DSP408, setDsps: SetDsps) {
|
|
try {
|
|
const meters = await invoke<DSP408['meters']>('get_meter_levels', { id: dsp.id });
|
|
|
|
updateDSP(setDsps, dsp.id, (d) =>
|
|
d.pollingPaused
|
|
? d
|
|
: ({
|
|
...d,
|
|
meters,
|
|
} as DSP408)
|
|
);
|
|
} catch {
|
|
// transient poll failures — don't notify
|
|
}
|
|
}
|
|
|
|
function startPolling(dsp: DSP408, setDsps: SetDsps) {
|
|
stopPolling(dsp.id);
|
|
|
|
void poll(dsp, setDsps);
|
|
|
|
const interval = setInterval(() => poll(dsp, setDsps), pollIntervalFor(dsp.id));
|
|
|
|
pollIntervals.set(dsp.id, interval);
|
|
}
|
|
|
|
function restartPollingIfActive(dsp: DSP408, setDsps: SetDsps) {
|
|
// only reschedule if we're actually polling this dsp right now
|
|
if (pollIntervals.has(dsp.id)) startPolling(dsp, setDsps);
|
|
}
|
|
|
|
export function stopPolling(id: number) {
|
|
const interval = pollIntervals.get(id);
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
pollIntervals.delete(id);
|
|
}
|
|
}
|
|
|
|
registerPollStopper('DSP408', stopPolling);
|
|
|
|
// --- pause helper ----------------------------------------------------------
|
|
|
|
async function withPollingPaused<T>(
|
|
dsp: DSP408,
|
|
setDsps: SetDsps,
|
|
operation: () => Promise<T>
|
|
): Promise<T> {
|
|
setPollingPaused(setDsps, dsp.id, true);
|
|
try {
|
|
return await operation();
|
|
} finally {
|
|
setPollingPaused(setDsps, dsp.id, false);
|
|
}
|
|
}
|
|
|
|
// --- factory -----------------------------------------------------------
|
|
|
|
export async function create(params: {
|
|
name: string;
|
|
ip: string;
|
|
port: number;
|
|
deviceId: number;
|
|
demo: boolean;
|
|
}): Promise<DSP408> {
|
|
const id = await invoke<number>('create_dsp408', {
|
|
ip: params.ip,
|
|
port: params.port,
|
|
deviceId: params.deviceId,
|
|
});
|
|
|
|
return {
|
|
id,
|
|
type: 'DSP408',
|
|
name: params.name,
|
|
ip: params.ip,
|
|
port: params.port,
|
|
deviceId: params.deviceId,
|
|
demo: params.demo,
|
|
status: 'disconnected',
|
|
pollingPaused: true,
|
|
state: defaultDSPState,
|
|
meters: defaultMeters,
|
|
};
|
|
}
|
|
|
|
export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|
async function updateDSPState(): Promise<void> {
|
|
try {
|
|
const state = await invoke<DSP408['state']>('get_dsp_state', { id: dsp.id });
|
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, state }) as DSP408);
|
|
} catch (err) {
|
|
notify(`Failed to fetch state: ${String(err)}`, 'error', dsp.name);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'DSP408' as const,
|
|
|
|
setDSPState(updater: (dsp: DSP) => DSP) {
|
|
updateDSP(setDsps, dsp.id, updater);
|
|
},
|
|
|
|
setActive(active: boolean) {
|
|
if (active) {
|
|
selectedIds.add(dsp.id);
|
|
} else {
|
|
selectedIds.delete(dsp.id);
|
|
}
|
|
restartPollingIfActive(dsp, setDsps);
|
|
},
|
|
|
|
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 });
|
|
|
|
await updateDSPState();
|
|
|
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'connected', pollingPaused: false }));
|
|
|
|
notify('Connected', 'success', dsp.name);
|
|
|
|
startPolling(dsp, setDsps);
|
|
} catch (err) {
|
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'disconnected' }));
|
|
notify(`Connection failed: ${String(err)}`, 'error', dsp.name);
|
|
}
|
|
},
|
|
|
|
async disconnect() {
|
|
if (dsp.demo) {
|
|
updateDSP(setDsps, dsp.id, (d) => ({
|
|
...d,
|
|
status: 'disconnected',
|
|
pollingPaused: true,
|
|
}));
|
|
|
|
return;
|
|
}
|
|
|
|
stopPolling(dsp.id);
|
|
|
|
try {
|
|
await withPollingPaused(dsp, setDsps, async () => {
|
|
await invoke('disconnect_dsp408', { id: dsp.id });
|
|
});
|
|
|
|
updateDSP(setDsps, dsp.id, (d) => ({
|
|
...d,
|
|
status: 'disconnected',
|
|
pollingPaused: true,
|
|
}));
|
|
|
|
notify('Disconnected', 'success', dsp.name);
|
|
} catch (err) {
|
|
notify(`Disconnection failed: ${String(err)}`, 'error', dsp.name);
|
|
}
|
|
},
|
|
|
|
async recallPreset(presetIndex: number): Promise<boolean> {
|
|
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<boolean>('recall_preset', {
|
|
id: dsp.id,
|
|
presetIndex,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to recall preset', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify(`Preset ${presetIndex} recalled`, 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Preset recall failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setCurrentPresetName(name: string): Promise<boolean> {
|
|
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<boolean>('set_current_preset_name', {
|
|
id: dsp.id,
|
|
name: paddedName,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to rename preset', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Preset renamed', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Preset rename failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setGain(channel: Channel, gain: number): Promise<boolean> {
|
|
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<boolean>('set_channel_gain', {
|
|
id: dsp.id,
|
|
channel,
|
|
db: gain,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to set gain', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Gain updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Gain update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setMute(channel: Channel, muted: boolean): Promise<boolean> {
|
|
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<boolean>('set_mute', {
|
|
id: dsp.id,
|
|
channel,
|
|
muted,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change mute state', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify(muted ? 'Muted' : 'Unmuted', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Mute update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setInverse(channel: Channel, inverted: boolean): Promise<boolean> {
|
|
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<boolean>('set_channel_inverse_gain', {
|
|
id: dsp.id,
|
|
channel,
|
|
inverted,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change phase', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify(inverted ? 'Phase inverted' : 'Phase normal', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Phase update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setGate(
|
|
inputChannel: InputChannel,
|
|
threshold: number,
|
|
attack: number,
|
|
hold: number,
|
|
release: number
|
|
): Promise<boolean> {
|
|
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<boolean>('set_gate', {
|
|
id: dsp.id,
|
|
channel: inputChannel,
|
|
threshold,
|
|
attack,
|
|
hold,
|
|
release,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change gate settings', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Gate updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Gate update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setCompressor(
|
|
outputChannel: OutputChannel,
|
|
ratio: Ratio,
|
|
attack: number,
|
|
release: number,
|
|
knee: number,
|
|
threshold: number
|
|
): Promise<boolean> {
|
|
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<boolean>('set_compressor', {
|
|
id: dsp.id,
|
|
channel: outputChannel,
|
|
ratio,
|
|
attack,
|
|
release,
|
|
knee,
|
|
threshold,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change compressor settings', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Compressor updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Compressor update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setLimiter(
|
|
outputChannel: OutputChannel,
|
|
threshold: number,
|
|
attack: number,
|
|
release: number
|
|
): Promise<boolean> {
|
|
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<boolean>('set_limiter', {
|
|
id: dsp.id,
|
|
channel: outputChannel,
|
|
threshold,
|
|
attack,
|
|
release,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change limiter settings', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Limiter updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Limiter update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setDelayUnit(unit: DelayUnit): Promise<boolean> {
|
|
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 = <T extends Record<string, { delay: number }>>(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<boolean>('set_delay_unit', {
|
|
id: dsp.id,
|
|
unit,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change delay unit', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Delay unit updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Delay unit update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setDelay(channel: Channel, delay: number): Promise<boolean> {
|
|
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<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;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Channel delay updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Channel delay update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setMatrixRouting(output: OutputChannel, inputs: InputChannel[]): Promise<boolean> {
|
|
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<boolean>('set_matrix_routing', {
|
|
id: dsp.id,
|
|
output,
|
|
inputs,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change matrix routing', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Matrix routing updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Matrix routing update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setMatrixGain(
|
|
inputChannel: InputChannel,
|
|
outputChannel: OutputChannel,
|
|
gain: number
|
|
): Promise<boolean> {
|
|
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<boolean>('set_matrix_gain', {
|
|
id: dsp.id,
|
|
inputChannel,
|
|
outputChannel,
|
|
gain,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change matrix gain', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Matrix gain updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Matrix gain update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setGeqBand(
|
|
inputChannel: InputChannel,
|
|
frequencyBand: DiscreteFrequency,
|
|
gain: number
|
|
): Promise<boolean> {
|
|
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<boolean>('set_geq_band', {
|
|
id: dsp.id,
|
|
inputChannel,
|
|
frequencyBand,
|
|
gain,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change GEQ band', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('GEQ band updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`GEQ band update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setGeqBypass(inputChannel: InputChannel, bypass: boolean): Promise<boolean> {
|
|
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<boolean>('set_geq_bypass', {
|
|
id: dsp.id,
|
|
inputChannel,
|
|
bypass,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to change GEQ bypass', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify(bypass ? 'GEQ bypass enabled' : 'GEQ bypass disabled', 'success', dsp.name);
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`GEQ bypass update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setLowPass(
|
|
channel: Channel,
|
|
freq: number,
|
|
filterType: CrossoverFilter
|
|
): Promise<boolean> {
|
|
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<boolean>('set_low_pass', {
|
|
id: dsp.id,
|
|
channel,
|
|
freq,
|
|
filterType,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to set low-pass filter', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('Low-pass filter updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Low-pass update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setHighPass(
|
|
channel: Channel,
|
|
freq: number,
|
|
filterType: CrossoverFilter
|
|
): Promise<boolean> {
|
|
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<boolean>('set_high_pass', {
|
|
id: dsp.id,
|
|
channel,
|
|
freq,
|
|
filterType,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to set high-pass filter', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('High-pass filter updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`High-pass update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setPeqBand(
|
|
channel: Channel,
|
|
band: PEQBand,
|
|
gainDb: number,
|
|
freqHz: number,
|
|
q: number,
|
|
filterType: PEQFilter,
|
|
bypass: boolean
|
|
): Promise<boolean> {
|
|
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<boolean>('set_peq_band', {
|
|
id: dsp.id,
|
|
channel,
|
|
band,
|
|
gainDb,
|
|
freqHz,
|
|
q,
|
|
filterType,
|
|
bypass,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to set PEQ band', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify('PEQ band updated', 'success', dsp.name);
|
|
return true;
|
|
} catch (err) {
|
|
notify(`PEQ update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setPEQBypass(channel: Channel, bypass: boolean): Promise<boolean> {
|
|
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<boolean>('set_bypass_peq', {
|
|
id: dsp.id,
|
|
channel,
|
|
bypass,
|
|
});
|
|
|
|
if (!success) {
|
|
notify('Failed to set PEQ bypass', 'error', dsp.name);
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
notify(bypass ? 'PEQ bypass enabled' : 'PEQ bypass disabled', 'success', dsp.name);
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`PEQ bypass failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async storeCurrentConfiguration(presetIndex: number, force: boolean): Promise<boolean> {
|
|
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<boolean>('store_current_configuration', {
|
|
id: dsp.id,
|
|
presetIndex,
|
|
force,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Store configuration failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async factoryReset(force: boolean): Promise<boolean> {
|
|
if (!force) {
|
|
return false;
|
|
}
|
|
|
|
if (dsp.demo) {
|
|
return true;
|
|
}
|
|
|
|
return withPollingPaused(dsp, setDsps, async () => {
|
|
try {
|
|
const success = await invoke<boolean>('factory_reset', {
|
|
id: dsp.id,
|
|
force,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Factory reset failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async copyChannel(channel1: Channel, channel2: Channel): Promise<boolean> {
|
|
if (dsp.demo) {
|
|
return true;
|
|
}
|
|
|
|
return withPollingPaused(dsp, setDsps, async () => {
|
|
try {
|
|
const success = await invoke<boolean>('copy_channel', {
|
|
id: dsp.id,
|
|
channel1,
|
|
channel2,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Channel copy failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setInputSource(inputSource: InputSource): Promise<boolean> {
|
|
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<boolean>('set_input_source', {
|
|
id: dsp.id,
|
|
inputSource,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Input source update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async linkChannels(source: Channel, destinations: Channel[]): Promise<boolean> {
|
|
if (dsp.demo) {
|
|
// TODO: update local channel link state
|
|
return true;
|
|
}
|
|
|
|
return withPollingPaused(dsp, setDsps, async () => {
|
|
try {
|
|
const success = await invoke<boolean>('link_channels', {
|
|
id: dsp.id,
|
|
source,
|
|
destinations,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Channel linking failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
|
|
async setChannelName(channel: Channel, name: string): Promise<boolean> {
|
|
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<boolean>('set_channel_name', {
|
|
id: dsp.id,
|
|
channel,
|
|
name,
|
|
});
|
|
|
|
if (!success) {
|
|
return false;
|
|
}
|
|
|
|
await updateDSPState();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
notify(`Channel name update failed: ${String(err)}`, 'error', dsp.name);
|
|
return false;
|
|
}
|
|
});
|
|
},
|
|
};
|
|
}
|