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; export type DSP408Api = ReturnType; // --- selection + polling state, private to this module ------------------- const selectedIds = new Set(); const pollIntervals = new Map>(); 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( dsp: DSP408, setDsps: SetDsps, operation: () => Promise ): Promise { 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 { const id = await invoke('create_dsp408', { ip: params.ip, port: params.port, deviceId: params.deviceId, demo: params.demo, }); const snapshot = await invoke('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 { try { const state = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { const paddedName = name.slice(0, 14).padEnd(14, ' '); return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { if (!force) { return false; } return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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 { return withPollingPaused(dsp, setDsps, async () => { try { const success = await invoke('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; } }); }, }; }