Files
dsp-thomann-interface/src/features/dsp408/dsp408.ts
T

893 lines
23 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core';
import { DSP } from '../../types';
import {
Channel,
InputChannel,
OutputChannel,
Ratio,
DelayUnit,
DiscreteFrequency,
CrossoverFilter,
PEQFilter,
PEQBand,
InputSource,
DSP408Snapshot,
} from './types';
import { SetDsps, NotifyFn, updateDSP, setPollingPaused } from '../../dsp/dspShared';
import { registerPollStopper } from '../../dsp/pollRegistry';
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 [input_meters, output_meters] = await invoke<
[DSP408['input_meters'], DSP408['output_meters']]
>('get_meter_levels', { id: dsp.id });
updateDSP(setDsps, dsp.id, (d) =>
d.pollingPaused
? d
: ({
...d,
input_meters,
output_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,
demo: params.demo,
});
const snapshot = await invoke<DSP408Snapshot>('get_dsp_snapshot', { id });
return {
id,
type: 'DSP408',
name: params.name,
ip: params.ip,
port: params.port,
deviceId: params.deviceId,
mode: snapshot.mode,
status: 'disconnected',
pollingPaused: true,
state: snapshot.state,
input_meters: snapshot.input_meters,
output_meters: snapshot.output_meters,
};
}
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() {
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() {
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> {
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, ' ');
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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> {
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;
}
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> {
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> {
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> {
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> {
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;
}
});
},
async unlockDevice(password: string): Promise<boolean> {
return withPollingPaused(dsp, setDsps, async () => {
try {
const success = await invoke<boolean>('unlock_device', {
id: dsp.id,
password,
});
if (!success) {
return false;
}
await updateDSPState();
return true;
} catch (err) {
notify(`Unlock failed: ${String(err)}`, 'error', dsp.name);
return false;
}
});
},
async lockDevice(password: string): Promise<boolean> {
return withPollingPaused(dsp, setDsps, async () => {
try {
const success = await invoke<boolean>('lock_device', {
id: dsp.id,
password,
});
if (!success) {
return false;
}
await updateDSPState();
return true;
} catch (err) {
notify(`Lock failed: ${String(err)}`, 'error', dsp.name);
return false;
}
});
},
};
}