feat: add demo mode
This commit is contained in:
@@ -461,3 +461,106 @@ pub async fn set_peq_band(
|
|||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn store_current_configuration(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
preset_index: usize,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.store_current_configuration(preset_index, force)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn factory_reset(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
force: bool,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.factory_reset(force)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn copy_channel(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
channel_1: types::Channel,
|
||||||
|
channel_2: types::Channel,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.copy_channel(channel_1, channel_2)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_input_source(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
input_source: types::InputSource,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_input_source(input_source)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn link_channels(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
source: types::Channel,
|
||||||
|
destinations: Vec<types::Channel>,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.link_channels(source, destinations)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_bypass_peq(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
channel: types::Channel,
|
||||||
|
bypass: bool,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_bypass_peq(channel, bypass)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_channel_name(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
channel: types::Channel,
|
||||||
|
name: String,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_channel_name(channel, &name)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
+31
-20
@@ -10,28 +10,39 @@ pub fn run() {
|
|||||||
.manage(AppState::new())
|
.manage(AppState::new())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::create_dsp408,
|
commands::create_dsp408,
|
||||||
commands::connect_dsp408,
|
|
||||||
commands::remove_dsp408,
|
commands::remove_dsp408,
|
||||||
commands::disconnect_dsp408,
|
|
||||||
commands::get_dsp_state,
|
commands::get_dsp_state,
|
||||||
commands::set_channel_gain,
|
|
||||||
commands::set_mute,
|
commands::connect_dsp408, // 0x10
|
||||||
commands::set_channel_inverse_gain,
|
commands::disconnect_dsp408, // 0x11
|
||||||
commands::recall_preset,
|
commands::set_delay_unit, // 0x15
|
||||||
commands::get_meter_levels,
|
|
||||||
commands::set_current_preset_name,
|
commands::recall_preset, // 0x20
|
||||||
commands::set_gate,
|
commands::store_current_configuration, // 0x21
|
||||||
commands::set_compressor,
|
commands::factory_reset, // 0x23
|
||||||
commands::set_limiter,
|
commands::set_current_preset_name, // 0x26
|
||||||
commands::set_delay_unit,
|
commands::copy_channel, // 0x2A
|
||||||
commands::set_delay,
|
|
||||||
commands::set_matrix_routing,
|
commands::set_compressor, // 0x30
|
||||||
commands::set_matrix_gain,
|
commands::set_low_pass, // 0x31
|
||||||
commands::set_geq_band,
|
commands::set_high_pass, // 0x32
|
||||||
commands::set_geq_bypass,
|
commands::set_peq_band, // 0x33
|
||||||
commands::set_low_pass,
|
commands::set_channel_gain, // 0x34
|
||||||
commands::set_high_pass,
|
commands::set_mute, // 0x35
|
||||||
commands::set_peq_band,
|
commands::set_channel_inverse_gain, // 0x36
|
||||||
|
commands::set_delay, // 0x38
|
||||||
|
commands::set_input_source, // 0x39
|
||||||
|
commands::set_matrix_routing, // 0x3A
|
||||||
|
commands::link_channels, // 0x3B
|
||||||
|
commands::set_bypass_peq, // 0x3C
|
||||||
|
commands::set_channel_name, // 0x3D
|
||||||
|
commands::set_gate, // 0x3E
|
||||||
|
commands::set_limiter, // 0x3F
|
||||||
|
|
||||||
|
commands::get_meter_levels, // 0x40
|
||||||
|
commands::set_matrix_gain, // 0x41
|
||||||
|
commands::set_geq_band, // 0x48
|
||||||
|
commands::set_geq_bypass, // 0x49
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
+30
-17
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { DSP, AppPage } from './types/types';
|
import { DSP, AppPage } from './types/types';
|
||||||
import TopBar from './components/Topbar/Topbar';
|
import TopBar from './components/Topbar/Topbar';
|
||||||
@@ -8,17 +8,33 @@ import { useNotifications } from './hooks/useNotifications';
|
|||||||
import Notifications from './components/Notifications/Notifications';
|
import Notifications from './components/Notifications/Notifications';
|
||||||
import CreditsPage from './pages/CreditsPage/CreditsPage';
|
import CreditsPage from './pages/CreditsPage/CreditsPage';
|
||||||
import HomePage from './pages/HomePage/HomePage';
|
import HomePage from './pages/HomePage/HomePage';
|
||||||
import { useDSP408 } from './hooks/useDSP408';
|
import { addDSP, createDSPInstance } from './dsp/dspApi';
|
||||||
|
import { removeDSP } from './dsp/dspShared';
|
||||||
|
import { stopAllPolling } from './dsp/pollRegistry';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [dsps, setDsps] = useState<DSP[]>([]);
|
const [dsps, setDsps] = useState<DSP[]>([]);
|
||||||
const [selected, setSelected] = useState<number | null>(null);
|
const [selected, setSelected] = useState<number | null>(null);
|
||||||
const [page, setPage] = useState<AppPage>('home');
|
const [page, setPage] = useState<AppPage>('home');
|
||||||
const { notifications, notify, removeNotification, hoverNotification } = useNotifications();
|
const { notifications, notify, removeNotification, hoverNotification } = useNotifications();
|
||||||
const activeDsp = dsps.find((d) => d.id === selected) ?? null;
|
|
||||||
const connectedDspCount = dsps.filter((dsp) => dsp.status === 'connected').length;
|
const connectedDspCount = dsps.filter((dsp) => dsp.status === 'connected').length;
|
||||||
|
|
||||||
const dsp408 = useDSP408(dsps, setDsps, selected, setSelected, setPage, notify);
|
const activeDsp = dsps.find((d) => d.id === selected) ?? null;
|
||||||
|
const activeInstance = activeDsp ? createDSPInstance(activeDsp, setDsps, notify) : null;
|
||||||
|
|
||||||
|
const dspsRef = useRef(dsps);
|
||||||
|
dspsRef.current = dsps;
|
||||||
|
useEffect(() => {
|
||||||
|
return () => stopAllPolling(dspsRef.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeDsp == null) return;
|
||||||
|
const instance = createDSPInstance(activeDsp, setDsps, notify);
|
||||||
|
instance.api.setActive(true);
|
||||||
|
return () => instance.api.setActive(false);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [activeDsp?.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
@@ -34,29 +50,26 @@ function App() {
|
|||||||
page={page}
|
page={page}
|
||||||
setPage={setPage}
|
setPage={setPage}
|
||||||
setSelect={setSelected}
|
setSelect={setSelected}
|
||||||
|
|
||||||
onSelect={(id) => {
|
onSelect={(id) => {
|
||||||
setSelected(id);
|
setSelected(id);
|
||||||
setPage('view-dsp');
|
setPage('view-dsp');
|
||||||
}}
|
}}
|
||||||
|
onRemove={(dsp) => removeDSP(setDsps, selected, setSelected, dsp.id, dsp.type)}
|
||||||
onRemove={dsp408.removeDSP}
|
onAddClick={() => setPage('add-dsp')}
|
||||||
|
onConnect={(dsp) => createDSPInstance(dsp, setDsps, notify).api.connect()}
|
||||||
onAddClick={() => {
|
onDisconnect={(dsp) => createDSPInstance(dsp, setDsps, notify).api.disconnect()}
|
||||||
setPage('add-dsp');
|
|
||||||
}}
|
|
||||||
|
|
||||||
onConnect={dsp408.connectDSP}
|
|
||||||
onDisconnect={dsp408.disconnectDSP}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main className="stage">
|
<main className="stage">
|
||||||
{page === 'credits' ? (
|
{page === 'credits' ? (
|
||||||
<CreditsPage />
|
<CreditsPage />
|
||||||
) : page === 'add-dsp' ? (
|
) : page === 'add-dsp' ? (
|
||||||
<AddDSPForm onCancel={() => setPage('home')} onAdd={dsp408.addDSP} />
|
<AddDSPForm
|
||||||
) : activeDsp ? (
|
onCancel={() => setPage('home')}
|
||||||
<DSPPage dsp={activeDsp} dsp408={dsp408.forDSP(activeDsp)} />
|
onAdd={(params) => addDSP(setDsps, setSelected, setPage, notify, params)}
|
||||||
|
/>
|
||||||
|
) : activeInstance ? (
|
||||||
|
<DSPPage instance={activeInstance} />
|
||||||
) : (
|
) : (
|
||||||
<HomePage
|
<HomePage
|
||||||
onAddDSP={() => setPage('add-dsp')}
|
onAddDSP={() => setPage('add-dsp')}
|
||||||
|
|||||||
+127
-22
@@ -24,6 +24,8 @@ export interface LinearGraphHandle {
|
|||||||
getCanvas: () => HTMLCanvasElement | null;
|
getCanvas: () => HTMLCanvasElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AxisScale = 'linear' | 'log';
|
||||||
|
|
||||||
export interface LinearGraphProps {
|
export interface LinearGraphProps {
|
||||||
xMin: number;
|
xMin: number;
|
||||||
xMax: number;
|
xMax: number;
|
||||||
@@ -31,6 +33,10 @@ export interface LinearGraphProps {
|
|||||||
yMin: number;
|
yMin: number;
|
||||||
yMax: number;
|
yMax: number;
|
||||||
yStep?: number;
|
yStep?: number;
|
||||||
|
/** 'log' requires xMin > 0. Ticks are drawn at 1/2/5-per-decade mantissas, with decade (power-of-10) lines highlighted. */
|
||||||
|
xScale?: AxisScale;
|
||||||
|
/** 'log' requires yMin > 0. */
|
||||||
|
yScale?: AxisScale;
|
||||||
draw?: DrawFn;
|
draw?: DrawFn;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
@@ -72,6 +78,37 @@ function range(min: number, max: number, step: number): number[] {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Tick {
|
||||||
|
value: number;
|
||||||
|
/** Decade lines (powers of 10) get major=true for a bolder/brighter grid line. */
|
||||||
|
major: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear ticks: every step is equally weighted, so mark every Nth (default 5th) as "major"
|
||||||
|
// purely for label emphasis — grid highlighting logic only really matters for log mode.
|
||||||
|
function linearTicks(min: number, max: number, step: number): Tick[] {
|
||||||
|
return range(min, max, step).map((value) => ({ value, major: false }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log ticks: standard 1/2/3/4/5/6/7/8/9 mantissas within each decade between min and max.
|
||||||
|
// Mantissa === 1 (i.e. the decade itself: ...0.1, 1, 10, 100, 1000...) is flagged major
|
||||||
|
// so it can be drawn as a highlighted line distinct from the minor in-decade ticks.
|
||||||
|
function logTicks(min: number, max: number): Tick[] {
|
||||||
|
if (min <= 0 || max <= 0 || max <= min) return [];
|
||||||
|
const startDecade = Math.floor(Math.log10(min));
|
||||||
|
const endDecade = Math.ceil(Math.log10(max));
|
||||||
|
const out: Tick[] = [];
|
||||||
|
for (let d = startDecade; d <= endDecade; d++) {
|
||||||
|
const decadeValue = Math.pow(10, d);
|
||||||
|
for (const mantissa of [1, 2, 3, 4, 5, 6, 7, 8, 9]) {
|
||||||
|
const value = decadeValue * mantissa;
|
||||||
|
if (value < min - 1e-9 || value > max + 1e-9) continue;
|
||||||
|
out.push({ value, major: mantissa === 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(function LinearGraph(
|
export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(function LinearGraph(
|
||||||
{
|
{
|
||||||
xMin,
|
xMin,
|
||||||
@@ -80,6 +117,8 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
yMin,
|
yMin,
|
||||||
yMax,
|
yMax,
|
||||||
yStep,
|
yStep,
|
||||||
|
xScale = 'linear',
|
||||||
|
yScale = 'linear',
|
||||||
draw,
|
draw,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -138,6 +177,18 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
const effXStep = xStep ?? niceStep(xMin, xMax);
|
const effXStep = xStep ?? niceStep(xMin, xMax);
|
||||||
const effYStep = yStep ?? niceStep(yMin, yMax);
|
const effYStep = yStep ?? niceStep(yMin, yMax);
|
||||||
|
|
||||||
|
const xLogValid = xScale === 'log' && xMin > 0 && xMax > xMin;
|
||||||
|
const yLogValid = yScale === 'log' && yMin > 0 && yMax > yMin;
|
||||||
|
|
||||||
|
const xTicks = useMemo(
|
||||||
|
() => (xLogValid ? logTicks(xMin, xMax) : linearTicks(xMin, xMax, effXStep)),
|
||||||
|
[xLogValid, xMin, xMax, effXStep]
|
||||||
|
);
|
||||||
|
const yTicks = useMemo(
|
||||||
|
() => (yLogValid ? logTicks(yMin, yMax) : linearTicks(yMin, yMax, effYStep)),
|
||||||
|
[yLogValid, yMin, yMax, effYStep]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (width && height) return;
|
if (width && height) return;
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
@@ -176,17 +227,41 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
height: Math.max(1, cssH - pad.top - pad.bottom),
|
height: Math.max(1, cssH - pad.top - pad.bottom),
|
||||||
};
|
};
|
||||||
|
|
||||||
const toPxX = (x: number) => plot.x + ((x - xMin) / (xMax - xMin || 1)) * plot.width;
|
const logXMin = xLogValid ? Math.log10(xMin) : 0;
|
||||||
const toPxY = (y: number) =>
|
const logXMax = xLogValid ? Math.log10(xMax) : 0;
|
||||||
plot.y + plot.height - ((y - yMin) / (yMax - yMin || 1)) * plot.height;
|
const logYMin = yLogValid ? Math.log10(yMin) : 0;
|
||||||
|
const logYMax = yLogValid ? Math.log10(yMax) : 0;
|
||||||
|
|
||||||
|
const toPxX = (x: number) => {
|
||||||
|
if (xLogValid) {
|
||||||
|
const lx = Math.log10(Math.max(x, 1e-9));
|
||||||
|
return plot.x + ((lx - logXMin) / (logXMax - logXMin || 1)) * plot.width;
|
||||||
|
}
|
||||||
|
return plot.x + ((x - xMin) / (xMax - xMin || 1)) * plot.width;
|
||||||
|
};
|
||||||
|
const toPxY = (y: number) => {
|
||||||
|
if (yLogValid) {
|
||||||
|
const ly = Math.log10(Math.max(y, 1e-9));
|
||||||
|
return plot.y + plot.height - ((ly - logYMin) / (logYMax - logYMin || 1)) * plot.height;
|
||||||
|
}
|
||||||
|
return plot.y + plot.height - ((y - yMin) / (yMax - yMin || 1)) * plot.height;
|
||||||
|
};
|
||||||
const toPx = (x: number, y: number): [number, number] => [toPxX(x), toPxY(y)];
|
const toPx = (x: number, y: number): [number, number] => [toPxX(x), toPxY(y)];
|
||||||
const toData = (px: number, py: number): [number, number] => [
|
const toData = (px: number, py: number): [number, number] => {
|
||||||
xMin + ((px - plot.x) / plot.width) * (xMax - xMin),
|
const xRatio = (px - plot.x) / plot.width;
|
||||||
yMin + (1 - (py - plot.y) / plot.height) * (yMax - yMin),
|
const yRatio = 1 - (py - plot.y) / plot.height;
|
||||||
];
|
const x = xLogValid
|
||||||
|
? Math.pow(10, logXMin + xRatio * (logXMax - logXMin))
|
||||||
|
: xMin + xRatio * (xMax - xMin);
|
||||||
|
const y = yLogValid
|
||||||
|
? Math.pow(10, logYMin + yRatio * (logYMax - logYMin))
|
||||||
|
: yMin + yRatio * (yMax - yMin);
|
||||||
|
return [x, y];
|
||||||
|
};
|
||||||
|
|
||||||
const css = getComputedStyle(canvas);
|
const css = getComputedStyle(canvas);
|
||||||
const gridColor = css.getPropertyValue('--graph-grid').trim() || '#2a2f3a';
|
const gridColor = css.getPropertyValue('--graph-grid').trim() || '#2a2f3a';
|
||||||
|
const gridMajorColor = css.getPropertyValue('--graph-grid-major').trim() || '#4b5563';
|
||||||
const axisColor = css.getPropertyValue('--graph-axis').trim() || '#6b7280';
|
const axisColor = css.getPropertyValue('--graph-axis').trim() || '#6b7280';
|
||||||
const textColor = css.getPropertyValue('--graph-text').trim() || '#9ca3af';
|
const textColor = css.getPropertyValue('--graph-text').trim() || '#9ca3af';
|
||||||
const bgColor = css.getPropertyValue('--graph-bg').trim();
|
const bgColor = css.getPropertyValue('--graph-bg').trim();
|
||||||
@@ -217,32 +292,55 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (showGrid) {
|
if (showGrid) {
|
||||||
|
// minor grid lines first (thin), then major/decade lines drawn on top so
|
||||||
|
// they aren't obscured and read clearly as the "multiples of 10" markers.
|
||||||
ctx.strokeStyle = gridColor;
|
ctx.strokeStyle = gridColor;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
for (const gx of range(xMin, xMax, effXStep)) {
|
for (const t of xTicks) {
|
||||||
const px = Math.round(toPxX(gx)) + 0.5;
|
if (t.major) continue;
|
||||||
|
const px = Math.round(toPxX(t.value)) + 0.5;
|
||||||
ctx.moveTo(px, plot.y);
|
ctx.moveTo(px, plot.y);
|
||||||
ctx.lineTo(px, plot.y + plot.height);
|
ctx.lineTo(px, plot.y + plot.height);
|
||||||
}
|
}
|
||||||
for (const gy of range(yMin, yMax, effYStep)) {
|
for (const t of yTicks) {
|
||||||
const py = Math.round(toPxY(gy)) + 0.5;
|
if (t.major) continue;
|
||||||
|
const py = Math.round(toPxY(t.value)) + 0.5;
|
||||||
ctx.moveTo(plot.x, py);
|
ctx.moveTo(plot.x, py);
|
||||||
ctx.lineTo(plot.x + plot.width, py);
|
ctx.lineTo(plot.x + plot.width, py);
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
const majorX = xTicks.filter((t) => t.major);
|
||||||
|
const majorY = yTicks.filter((t) => t.major);
|
||||||
|
if (majorX.length || majorY.length) {
|
||||||
|
ctx.strokeStyle = gridMajorColor;
|
||||||
|
ctx.lineWidth = Math.max(1, metrics.axisLineWidth * 0.6);
|
||||||
|
ctx.beginPath();
|
||||||
|
for (const t of majorX) {
|
||||||
|
const px = Math.round(toPxX(t.value)) + 0.5;
|
||||||
|
ctx.moveTo(px, plot.y);
|
||||||
|
ctx.lineTo(px, plot.y + plot.height);
|
||||||
|
}
|
||||||
|
for (const t of majorY) {
|
||||||
|
const py = Math.round(toPxY(t.value)) + 0.5;
|
||||||
|
ctx.moveTo(plot.x, py);
|
||||||
|
ctx.lineTo(plot.x + plot.width, py);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAxes) {
|
if (showAxes) {
|
||||||
ctx.strokeStyle = axisColor;
|
ctx.strokeStyle = axisColor;
|
||||||
ctx.lineWidth = metrics.axisLineWidth;
|
ctx.lineWidth = metrics.axisLineWidth;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
if (yMin <= 0 && yMax >= 0) {
|
if (!yLogValid && yMin <= 0 && yMax >= 0) {
|
||||||
const py = Math.round(toPxY(0)) + 0.5;
|
const py = Math.round(toPxY(0)) + 0.5;
|
||||||
ctx.moveTo(plot.x, py);
|
ctx.moveTo(plot.x, py);
|
||||||
ctx.lineTo(plot.x + plot.width, py);
|
ctx.lineTo(plot.x + plot.width, py);
|
||||||
}
|
}
|
||||||
if (xMin <= 0 && xMax >= 0) {
|
if (!xLogValid && xMin <= 0 && xMax >= 0) {
|
||||||
const px = Math.round(toPxX(0)) + 0.5;
|
const px = Math.round(toPxX(0)) + 0.5;
|
||||||
ctx.moveTo(px, plot.y);
|
ctx.moveTo(px, plot.y);
|
||||||
ctx.lineTo(px, plot.y + plot.height);
|
ctx.lineTo(px, plot.y + plot.height);
|
||||||
@@ -264,29 +362,34 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (showTicks) {
|
if (showTicks) {
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.font = `${metrics.tickFont}px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`;
|
ctx.font = `${metrics.tickFont}px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`;
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = 'top';
|
||||||
for (const gx of range(xMin, xMax, effXStep)) {
|
for (const t of xTicks) {
|
||||||
const px = toPxX(gx);
|
// In log mode, only label the decade (major) ticks — labeling every
|
||||||
|
// 2,3,4...9 mantissa gets unreadably dense.
|
||||||
|
if (xLogValid && !t.major) continue;
|
||||||
|
const px = toPxX(t.value);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(px, plot.y + plot.height);
|
ctx.moveTo(px, plot.y + plot.height);
|
||||||
ctx.lineTo(px, plot.y + plot.height + metrics.tickLen);
|
ctx.lineTo(px, plot.y + plot.height + metrics.tickLen);
|
||||||
ctx.strokeStyle = axisColor;
|
ctx.strokeStyle = axisColor;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.fillText(formatX(gx), px, plot.y + plot.height + metrics.tickLen + 2);
|
ctx.fillStyle = t.major && xLogValid ? textColor : textColor;
|
||||||
|
ctx.fillText(formatX(t.value), px, plot.y + plot.height + metrics.tickLen + 2);
|
||||||
}
|
}
|
||||||
ctx.textAlign = 'right';
|
ctx.textAlign = 'right';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
for (const gy of range(yMin, yMax, effYStep)) {
|
for (const t of yTicks) {
|
||||||
const py = toPxY(gy);
|
if (yLogValid && !t.major) continue;
|
||||||
|
const py = toPxY(t.value);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(plot.x - metrics.tickLen, py);
|
ctx.moveTo(plot.x - metrics.tickLen, py);
|
||||||
ctx.lineTo(plot.x, py);
|
ctx.lineTo(plot.x, py);
|
||||||
ctx.strokeStyle = axisColor;
|
ctx.strokeStyle = axisColor;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.fillText(formatY(gy), plot.x - metrics.tickLen - 2, py);
|
ctx.fillStyle = textColor;
|
||||||
|
ctx.fillText(formatY(t.value), plot.x - metrics.tickLen - 2, py);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,10 +417,12 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
metrics,
|
metrics,
|
||||||
xMin,
|
xMin,
|
||||||
xMax,
|
xMax,
|
||||||
effXStep,
|
xLogValid,
|
||||||
yMin,
|
yMin,
|
||||||
yMax,
|
yMax,
|
||||||
effYStep,
|
yLogValid,
|
||||||
|
xTicks,
|
||||||
|
yTicks,
|
||||||
showGrid,
|
showGrid,
|
||||||
showAxes,
|
showAxes,
|
||||||
showTicks,
|
showTicks,
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// CompressorSection.tsx
|
// CompressorSection.tsx
|
||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { OutputChannel, Ratio } from '../../../types/dsp408State';
|
import { OutputChannel, Ratio } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import MeterBar from '../../MeterBar/MeterBar';
|
import MeterBar from '../../MeterBar/MeterBar';
|
||||||
import { useMemo, useState, useEffect } from 'react';
|
import { useMemo, useState, useEffect } from 'react';
|
||||||
import { TriangleUpIcon, TriangleDownIcon } from '../../../assets/icons/Triangle';
|
import { TriangleUpIcon, TriangleDownIcon } from '../../../assets/icons/Triangle';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel, Channel, OutputChannel, DelayUnit } from '../../../types/dsp408State';
|
import { InputChannel, Channel, OutputChannel, DelayUnit } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import './DelaySection.css';
|
import './DelaySection.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import './GainSection.css';
|
import './GainSection.css';
|
||||||
|
|
||||||
function GainPanel({
|
function GainPanel({
|
||||||
@@ -26,9 +26,9 @@ function GainPanel({
|
|||||||
? dsp.state?.current_config.input_states[channel.Input]
|
? dsp.state?.current_config.input_states[channel.Input]
|
||||||
: dsp.state?.current_config.output_states[channel.Output];
|
: dsp.state?.current_config.output_states[channel.Output];
|
||||||
|
|
||||||
const muted = channelState?.mute ?? false;
|
const muted = channelState.mute;
|
||||||
const inverted = channelState?.phase_inverted ?? false;
|
const inverted = channelState.phase_inverted;
|
||||||
const gain = channelState?.gain ?? 0;
|
const gain = channelState.gain;
|
||||||
|
|
||||||
const toggleMute = async () => {
|
const toggleMute = async () => {
|
||||||
await setMute(channel, !muted);
|
await setMute(channel, !muted);
|
||||||
@@ -122,11 +122,12 @@ function GainSection({
|
|||||||
<div className="gain-graph-panel">
|
<div className="gain-graph-panel">
|
||||||
<div className="gain-graph-title">Frequency Response</div>
|
<div className="gain-graph-title">Frequency Response</div>
|
||||||
<LinearGraph
|
<LinearGraph
|
||||||
xMin={20}
|
xMin={10}
|
||||||
xMax={20000}
|
xMax={30000}
|
||||||
yMin={-24}
|
xScale="log"
|
||||||
yMax={24}
|
yMin={-18}
|
||||||
yStep={6}
|
yMax={18}
|
||||||
|
yStep={3}
|
||||||
xLabel="Hz"
|
xLabel="Hz"
|
||||||
yLabel="dB"
|
yLabel="dB"
|
||||||
draw={({ ctx, toPx }) => {
|
draw={({ ctx, toPx }) => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// GateSection.tsx
|
// GateSection.tsx
|
||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel } from '../../../types/dsp408State';
|
import { InputChannel } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import MeterBar from '../../MeterBar/MeterBar';
|
import MeterBar from '../../MeterBar/MeterBar';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import './GateSection.css';
|
import './GateSection.css';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel, DiscreteFrequency } from '../../../types/dsp408State';
|
import { InputChannel, DiscreteFrequency } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import './GeqSection.css';
|
import './GeqSection.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// InputSection.tsx
|
// InputSection.tsx
|
||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
// ASSUMPTION: an InputChannel enum exists alongside OutputChannel, mapping
|
// ASSUMPTION: an InputChannel enum exists alongside OutputChannel, mapping
|
||||||
// input pages 1-8 similarly (In1 -> 0, In2 -> 1, ...). Adjust import/values
|
// input pages 1-8 similarly (In1 -> 0, In2 -> 1, ...). Adjust import/values
|
||||||
// if the actual enum differs.
|
// if the actual enum differs.
|
||||||
import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Meter } from '../../../types/dsp408State';
|
import { InputChannel, Channel, PEQBand, PEQFilter, PEQ, Meter } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import { CrossoverFilter } from '../../../types/dsp408State';
|
import { CrossoverFilter } from '../../../types/dsp408';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import './InputSection.css';
|
import './InputSection.css';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
import MeterBar from '../../MeterBar/MeterBar';
|
import MeterBar from '../../MeterBar/MeterBar';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// LimiterSection.tsx
|
// LimiterSection.tsx
|
||||||
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
import VerticalSlider from '../../VerticalSlider/VerticalSlider';
|
||||||
import { OutputChannel } from '../../../types/dsp408State';
|
import { OutputChannel } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../../ChannelGroup/ChannelGroup';
|
||||||
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
import { LinearGraph } from '../../LinearGraph/LinearGraph';
|
||||||
import MeterBar from '../../MeterBar/MeterBar';
|
import MeterBar from '../../MeterBar/MeterBar';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import './LimiterSection.css';
|
import './LimiterSection.css';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// MatrixSection.tsx
|
// MatrixSection.tsx
|
||||||
import { Fragment, useRef, useState } from 'react';
|
import { Fragment, useRef, useState } from 'react';
|
||||||
import { InputChannel, OutputChannel } from '../../../types/dsp408State';
|
import { InputChannel, OutputChannel } from '../../../types/dsp408';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP408 as DSP } from '../../../types/types';
|
||||||
import './MatrixSection.css';
|
import './MatrixSection.css';
|
||||||
|
|
||||||
// ASSUMPTION: an InputChannel enum exists alongside OutputChannel in
|
// ASSUMPTION: an InputChannel enum exists alongside OutputChannel in
|
||||||
|
|||||||
+1719
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
|||||||
|
import { DSP, DSPTypes, AppPage } from '../types/types';
|
||||||
|
import { SetDsps, NotifyFn } from './dspShared';
|
||||||
|
import { forDSP408, create as createDSP408 } from './dsp408';
|
||||||
|
import { DSP408Api } from './dsp408';
|
||||||
|
// import { forDSP204 } from './dsp204';
|
||||||
|
|
||||||
|
export type DSPInstance = { dsp: Extract<DSP, { type: 'DSP408' }>; api: DSP408Api };
|
||||||
|
// | { dsp: Extract<DSP, { type: 'DSP204' }>; api: DSP408Api };
|
||||||
|
|
||||||
|
export function createDSPInstance(dsp: DSP, setDsps: SetDsps, notify: NotifyFn): DSPInstance {
|
||||||
|
switch (dsp.type) {
|
||||||
|
case 'DSP408':
|
||||||
|
return { dsp, api: forDSP408(dsp, setDsps, notify) };
|
||||||
|
case 'DSP204':
|
||||||
|
throw new Error('DSP204 not yet implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addDSP(
|
||||||
|
setDsps: SetDsps,
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<number | null>>,
|
||||||
|
setPage: React.Dispatch<React.SetStateAction<AppPage>>,
|
||||||
|
notify: NotifyFn,
|
||||||
|
params: {
|
||||||
|
name: string;
|
||||||
|
type: DSPTypes;
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
deviceId: number;
|
||||||
|
demo: boolean;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const newDsp: DSP =
|
||||||
|
params.type === 'DSP408'
|
||||||
|
? await createDSP408(params)
|
||||||
|
: await Promise.reject('DSP204 not implemented');
|
||||||
|
|
||||||
|
setDsps((prev) => [...prev, newDsp]);
|
||||||
|
setSelected(newDsp.id);
|
||||||
|
setPage('view-dsp');
|
||||||
|
} catch (err) {
|
||||||
|
notify(`Failed to add DSP: ${String(err)}`, 'error', params.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { DSP, DSPTypes } from '../types/types';
|
||||||
|
import { stopPollingFor } from './pollRegistry';
|
||||||
|
|
||||||
|
export type SetDsps = React.Dispatch<React.SetStateAction<DSP[]>>;
|
||||||
|
export type NotifyFn = (message: string, type: 'success' | 'error', dspName?: string) => void;
|
||||||
|
|
||||||
|
export function updateDSP(setDsps: SetDsps, id: number, updater: (dsp: DSP) => DSP) {
|
||||||
|
setDsps((prev) => prev.map((d) => (d.id === id ? updater(d) : d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setPollingPaused(setDsps: SetDsps, id: number, paused: boolean) {
|
||||||
|
updateDSP(setDsps, id, (d) => ({ ...d, pollingPaused: paused }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeDSP(
|
||||||
|
setDsps: SetDsps,
|
||||||
|
selected: number | null,
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<number | null>>,
|
||||||
|
id: number,
|
||||||
|
type: DSPTypes
|
||||||
|
) {
|
||||||
|
stopPollingFor(type, id);
|
||||||
|
setDsps((prev) => prev.filter((d) => d.id !== id));
|
||||||
|
if (selected === id) setSelected(null);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { DSPTypes } from '../types/types';
|
||||||
|
|
||||||
|
const stoppers = new Map<DSPTypes, (id: number) => void>();
|
||||||
|
|
||||||
|
export function registerPollStopper(type: DSPTypes, stop: (id: number) => void) {
|
||||||
|
stoppers.set(type, stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopPollingFor(type: DSPTypes, id: number) {
|
||||||
|
stoppers.get(type)?.(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopAllPolling(dsps: { id: number; type: DSPTypes }[]) {
|
||||||
|
for (const dsp of dsps) stopPollingFor(dsp.type, dsp.id);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -67,3 +67,8 @@
|
|||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-dsp-form__checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { DSP, DSP_TYPES } from '../../types/types';
|
import { DSP, DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types/types';
|
||||||
import Button from '../../components/Button/Button';
|
import Button from '../../components/Button/Button';
|
||||||
import Card from '../../components/Card/Card';
|
import Card from '../../components/Card/Card';
|
||||||
|
|
||||||
import './AddDspPage.css';
|
import './AddDspPage.css';
|
||||||
|
|
||||||
|
type DSPForm = {
|
||||||
|
name: string;
|
||||||
|
type: DSPTypes;
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
deviceId: number;
|
||||||
|
demo: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function AddDSPForm({
|
function AddDSPForm({
|
||||||
onCancel,
|
onCancel,
|
||||||
onAdd,
|
onAdd,
|
||||||
@@ -12,17 +21,18 @@ function AddDSPForm({
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onAdd: (d: Omit<DSP, 'status' | 'id' | 'state' | 'meters' | 'pollingPaused'>) => void;
|
onAdd: (d: Omit<DSP, 'status' | 'id' | 'state' | 'meters' | 'pollingPaused'>) => void;
|
||||||
}) {
|
}) {
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState<DSPForm>({
|
||||||
name: '',
|
name: '',
|
||||||
type: DSP_TYPES[0].name,
|
type: 'DSP408',
|
||||||
ip: '192.168.49.30',
|
ip: DSP_DEFAULT_PARAMETERS['DSP408'].ip,
|
||||||
port: DSP_TYPES[0].defaultPort,
|
port: DSP_DEFAULT_PARAMETERS['DSP408'].defaultPort,
|
||||||
deviceId: 1,
|
deviceId: DSP_DEFAULT_PARAMETERS['DSP408'].device_id,
|
||||||
|
demo: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const canSubmit =
|
const canSubmit =
|
||||||
form.name.trim().length > 0 &&
|
form.name.trim().length > 0 &&
|
||||||
form.ip.trim().length > 0 &&
|
(form.demo || form.ip.trim().length > 0) &&
|
||||||
Number.isInteger(form.deviceId) &&
|
Number.isInteger(form.deviceId) &&
|
||||||
form.deviceId >= 1 &&
|
form.deviceId >= 1 &&
|
||||||
form.deviceId <= 255;
|
form.deviceId <= 255;
|
||||||
@@ -58,18 +68,17 @@ function AddDSPForm({
|
|||||||
id="f-type"
|
id="f-type"
|
||||||
value={form.type}
|
value={form.type}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selected = DSP_TYPES.find((d) => d.name === e.target.value);
|
const type = e.target.value as DSPTypes;
|
||||||
|
|
||||||
setForm({
|
setForm({
|
||||||
...form,
|
...form,
|
||||||
type: e.target.value,
|
type,
|
||||||
port: selected?.defaultPort ?? form.port,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{DSP_TYPES.map((dsp) => (
|
{Object.keys(DSP_DEFAULT_PARAMETERS).map((type) => (
|
||||||
<option key={dsp.name} value={dsp.name}>
|
<option key={type} value={type}>
|
||||||
{dsp.name}
|
{type}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -98,6 +107,7 @@ function AddDSPForm({
|
|||||||
id="f-ip"
|
id="f-ip"
|
||||||
className="mono"
|
className="mono"
|
||||||
value={form.ip}
|
value={form.ip}
|
||||||
|
disabled={form.demo}
|
||||||
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,11 +119,24 @@ function AddDSPForm({
|
|||||||
className="mono"
|
className="mono"
|
||||||
type="number"
|
type="number"
|
||||||
value={form.port}
|
value={form.port}
|
||||||
|
disabled={form.demo}
|
||||||
onChange={(e) => setForm({ ...form, port: Number(e.target.value) })}
|
onChange={(e) => setForm({ ...form, port: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="add-dsp-form__field add-dsp-form__field--checkbox">
|
||||||
|
<label htmlFor="f-demo" className="add-dsp-form__checkbox-label">
|
||||||
|
<input
|
||||||
|
id="f-demo"
|
||||||
|
type="checkbox"
|
||||||
|
checked={form.demo}
|
||||||
|
onChange={(e) => setForm({ ...form, demo: e.target.checked })}
|
||||||
|
/>
|
||||||
|
Demo mode (simulate device, no network connection)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="add-dsp-form__actions">
|
<div className="add-dsp-form__actions">
|
||||||
<Button variant="ghost" onClick={onCancel}>
|
<Button variant="ghost" onClick={onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { DSP } from '../../types/types';
|
||||||
|
import Sidebar from '../../components/Sidebar/Sidebar';
|
||||||
|
import { InfoIcon, KnobGainIcon, TextIcon } from '../../assets/icons/AudioIcons';
|
||||||
|
import ConnectionPanel from '../../components/dsp408/ConnectionSection/ConnectionSection';
|
||||||
|
import GainSection from '../../components/dsp408/GainSection/GainSection';
|
||||||
|
import CompressorSection from '../../components/dsp408/CompressorSection/CompressorSection';
|
||||||
|
import GateSection from '../../components/dsp408/GateSection/GateSection';
|
||||||
|
import LimiterSection from '../../components/dsp408/LimiterSection/LimiterSection';
|
||||||
|
import DelaySection from '../../components/dsp408/DelaySection/DelaySection';
|
||||||
|
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
||||||
|
import { InputChannel, OutputChannel } from '../../types/dsp408';
|
||||||
|
import MatrixSection from '../../components/dsp408/Matrixsection/Matrixsection';
|
||||||
|
import GeqSection from '../../components/dsp408/GeqSection/GeqSection';
|
||||||
|
import InputSection from '../../components/dsp408/InputSection/InputSection';
|
||||||
|
import { DSP408Api } from '../../dsp/dsp408';
|
||||||
|
import '../DspPage/DspPage.css';
|
||||||
|
|
||||||
|
type DSP408 = Extract<DSP, { type: 'DSP408' }>;
|
||||||
|
|
||||||
|
function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
||||||
|
const [activeSidebar, setActiveSidebar] = useState('overview');
|
||||||
|
const [sidebarExpanded, setSidebarExpanded] = useState(false);
|
||||||
|
const [selectedPreset, setSelectedPreset] = useState(dsp.state?.presets.current_index ?? 0);
|
||||||
|
const [presetLoading, setPresetLoading] = useState(false);
|
||||||
|
const inputStates = dsp.state?.current_config.input_states;
|
||||||
|
const outputStates = dsp.state?.current_config.output_states;
|
||||||
|
|
||||||
|
const sidebarItems = [
|
||||||
|
{ id: 'overview', label: 'Overview', icon: <InfoIcon /> },
|
||||||
|
{ id: 'gain', label: 'Gain', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'gate', label: 'Gate', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'compressor', label: 'Compressor', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'limiter', label: 'Limiter', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'delay', label: 'Delay', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'matrix', label: 'Matrix', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'geq', label: 'Graphic Equalizer', icon: <KnobGainIcon /> },
|
||||||
|
{ id: 'ina', label: `${inputStates[InputChannel.InA].name}`, icon: <TextIcon>In A</TextIcon> },
|
||||||
|
{ id: 'inb', label: `${inputStates[InputChannel.InB].name}`, icon: <TextIcon>In B</TextIcon> },
|
||||||
|
{ id: 'inc', label: `${inputStates[InputChannel.InC].name}`, icon: <TextIcon>In C</TextIcon> },
|
||||||
|
{ id: 'ind', label: `${inputStates[InputChannel.InD].name}`, icon: <TextIcon>In D</TextIcon> },
|
||||||
|
{
|
||||||
|
id: 'out1',
|
||||||
|
label: `${outputStates[OutputChannel.Out1].name}`,
|
||||||
|
icon: <TextIcon>Out 1</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out2',
|
||||||
|
label: `${outputStates[OutputChannel.Out2].name}`,
|
||||||
|
icon: <TextIcon>Out 2</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out3',
|
||||||
|
label: `${outputStates[OutputChannel.Out3].name}`,
|
||||||
|
icon: <TextIcon>Out 3</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out4',
|
||||||
|
label: `${outputStates[OutputChannel.Out4].name}`,
|
||||||
|
icon: <TextIcon>Out 4</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out5',
|
||||||
|
label: `${outputStates[OutputChannel.Out5].name}`,
|
||||||
|
icon: <TextIcon>Out 5</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out6',
|
||||||
|
label: `${outputStates[OutputChannel.Out6].name}`,
|
||||||
|
icon: <TextIcon>Out 6</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out7',
|
||||||
|
label: `${outputStates[OutputChannel.Out7].name}`,
|
||||||
|
icon: <TextIcon>Out 7</TextIcon>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'out8',
|
||||||
|
label: `${outputStates[OutputChannel.Out8].name}`,
|
||||||
|
icon: <TextIcon>Out 8</TextIcon>,
|
||||||
|
},
|
||||||
|
{ id: 'miscellaneous', label: 'Miscellaneous', icon: <TextIcon>Out 1</TextIcon> },
|
||||||
|
];
|
||||||
|
|
||||||
|
const presets = dsp.state?.presets.names
|
||||||
|
? [
|
||||||
|
{ id: 'f00', name: 'Factory Preset' },
|
||||||
|
...dsp.state.presets.names.map((name, index) => ({
|
||||||
|
id: `u${String(index + 1).padStart(2, '0')}`,
|
||||||
|
name,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ id: 'f00', name: 'Factory Preset' },
|
||||||
|
...Array.from({ length: 20 }, (_, index) => ({
|
||||||
|
id: `u${String(index + 1).padStart(2, '0')}`,
|
||||||
|
name: 'Default Preset',
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const onAppChange = async (id: string) => {
|
||||||
|
const presetIndex = presets.findIndex((preset) => preset.id === id);
|
||||||
|
if (presetIndex === -1) return;
|
||||||
|
|
||||||
|
setPresetLoading(true);
|
||||||
|
const success = await api.recallPreset(presetIndex);
|
||||||
|
if (success) setSelectedPreset(presetIndex);
|
||||||
|
setPresetLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTab = () => {
|
||||||
|
switch (activeSidebar) {
|
||||||
|
case 'overview':
|
||||||
|
return <ConnectionPanel dsp={dsp} onConnect={api.connect} onDisconnect={api.disconnect} />;
|
||||||
|
|
||||||
|
case 'gain':
|
||||||
|
return (
|
||||||
|
<GainSection
|
||||||
|
dsp={dsp}
|
||||||
|
setGain={api.setGain}
|
||||||
|
setMute={api.setMute}
|
||||||
|
setInverse={api.setInverse}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'gate':
|
||||||
|
return <GateSection dsp={dsp} set_gate={api.setGate} />;
|
||||||
|
|
||||||
|
case 'compressor':
|
||||||
|
return <CompressorSection dsp={dsp} set_compressor={api.setCompressor} />;
|
||||||
|
|
||||||
|
case 'limiter':
|
||||||
|
return <LimiterSection dsp={dsp} set_limiter={api.setLimiter} />;
|
||||||
|
|
||||||
|
// case 'delay':
|
||||||
|
// return (
|
||||||
|
// <DelaySection
|
||||||
|
// dsp={dsp}
|
||||||
|
// setDelay={api.setDelay}
|
||||||
|
// setDelayUnit={api.setDelayUnit}
|
||||||
|
// setDSPState={api.setDSPState}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
|
||||||
|
case 'ina':
|
||||||
|
return (
|
||||||
|
<InputSection
|
||||||
|
dsp={dsp}
|
||||||
|
channel={InputChannel.InA}
|
||||||
|
set_low_pass={api.setLowPass}
|
||||||
|
set_high_pass={api.setHighPass}
|
||||||
|
setPeq={api.setPeqBand}
|
||||||
|
setGain={api.setGain}
|
||||||
|
setMute={api.setMute}
|
||||||
|
setInverse={api.setInverse}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'matrix':
|
||||||
|
return (
|
||||||
|
<MatrixSection
|
||||||
|
dsp={dsp}
|
||||||
|
setMatrixRouting={api.setMatrixRouting}
|
||||||
|
setMatrixGain={api.setMatrixGain}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'geq':
|
||||||
|
return <GeqSection dsp={dsp} setGeqBand={api.setGeqBand} setGeqBypass={api.setGeqBypass} />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`app-root ${sidebarExpanded ? 'app-root--sidebar-expanded' : ''}`}>
|
||||||
|
<Sidebar
|
||||||
|
items={sidebarItems}
|
||||||
|
activeId={activeSidebar}
|
||||||
|
onSelect={setActiveSidebar}
|
||||||
|
appName={dsp.name}
|
||||||
|
appOptions={presets}
|
||||||
|
selectedAppId={presets[selectedPreset]?.id}
|
||||||
|
onAppChange={onAppChange}
|
||||||
|
onAppRename={api.setCurrentPresetName}
|
||||||
|
expanded={sidebarExpanded}
|
||||||
|
onToggle={() => setSidebarExpanded((v) => !v)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LoadingOverlay
|
||||||
|
active={presetLoading}
|
||||||
|
label="Recalling preset…"
|
||||||
|
overlayClassName="dsp-loading-overlay"
|
||||||
|
>
|
||||||
|
<div className="dsp-content">{renderTab()}</div>
|
||||||
|
</LoadingOverlay>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DSP408Panel;
|
||||||
@@ -1,260 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { DSPInstance } from '../../dsp/dspApi';
|
||||||
import { DSP } from '../../types/types';
|
import DSP408Panel from '../DSP408Panel/DSP408Panel';
|
||||||
import Sidebar from '../../components/Sidebar/Sidebar';
|
|
||||||
import { InfoIcon, KnobGainIcon, TextIcon } from '../../assets/icons/AudioIcons';
|
|
||||||
import ConnectionPanel from '../../components/dsp408/ConnectionSection/ConnectionSection';
|
|
||||||
import GainSection from '../../components/dsp408/GainSection/GainSection';
|
|
||||||
import CompressorSection from '../../components/dsp408/CompressorSection/CompressorSection';
|
|
||||||
import { DSP408Device } from '../../hooks/useDSP408';
|
|
||||||
import './DspPage.css';
|
|
||||||
import GateSection from '../../components/dsp408/GateSection/GateSection';
|
|
||||||
import LimiterSection from '../../components/dsp408/LimiterSection/LimiterSection';
|
|
||||||
import DelaySection from '../../components/dsp408/DelaySection/DelaySection';
|
|
||||||
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
|
||||||
import { InputChannel, OutputChannel } from '../../types/dsp408State';
|
|
||||||
import MatrixSection from '../../components/dsp408/Matrixsection/Matrixsection';
|
|
||||||
import GeqSection from '../../components/dsp408/GeqSection/GeqSection';
|
|
||||||
import InputSection from '../../components/dsp408/InputSection/InputSection';
|
|
||||||
|
|
||||||
function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
function DSPPage({ instance }: { instance: DSPInstance }) {
|
||||||
const [activeSidebar, setActiveSidebar] = useState('overview');
|
if (instance.dsp.type === 'DSP408') {
|
||||||
const [sidebarExpanded, setSidebarExpanded] = useState(false);
|
return <DSP408Panel dsp={instance.dsp} api={instance.api} />;
|
||||||
const [selectedPreset, setSelectedPreset] = useState(dsp.state?.presets.current_index ?? 0);
|
}
|
||||||
const [presetLoading, setPresetLoading] = useState(false);
|
return null;
|
||||||
const inputStates = dsp.state?.current_config.input_states;
|
|
||||||
const outputStates = dsp.state?.current_config.output_states;
|
|
||||||
const sidebarItems = [
|
|
||||||
{
|
|
||||||
id: 'overview',
|
|
||||||
label: 'Overview',
|
|
||||||
icon: <InfoIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gain',
|
|
||||||
label: 'Gain',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gate',
|
|
||||||
label: 'Gate',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'compressor',
|
|
||||||
label: 'Compressor',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'limiter',
|
|
||||||
label: 'Limiter',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'delay',
|
|
||||||
label: 'Delay',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'matrix',
|
|
||||||
label: 'Matrix',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'geq',
|
|
||||||
label: 'Graphic Equalizer',
|
|
||||||
icon: <KnobGainIcon />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ina',
|
|
||||||
label: `${inputStates?.[InputChannel.InA]?.name}`,
|
|
||||||
icon: <TextIcon>In A</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'inb',
|
|
||||||
label: `${inputStates?.[InputChannel.InB]?.name}`,
|
|
||||||
icon: <TextIcon>In B</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'inc',
|
|
||||||
label: `${inputStates?.[InputChannel.InC]?.name}`,
|
|
||||||
icon: <TextIcon>In C</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ind',
|
|
||||||
label: `${inputStates?.[InputChannel.InD]?.name}`,
|
|
||||||
icon: <TextIcon>In D</TextIcon>,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: 'out1',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out1]?.name}`,
|
|
||||||
icon: <TextIcon>Out 1</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out2',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out2]?.name}`,
|
|
||||||
icon: <TextIcon>Out 2</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out3',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out3]?.name}`,
|
|
||||||
icon: <TextIcon>Out 3</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out4',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out4]?.name}`,
|
|
||||||
icon: <TextIcon>Out 4</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out5',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out5]?.name}`,
|
|
||||||
icon: <TextIcon>Out 5</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out6',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out6]?.name}`,
|
|
||||||
icon: <TextIcon>Out 6</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out7',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out7]?.name}`,
|
|
||||||
icon: <TextIcon>Out 7</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'out8',
|
|
||||||
label: `${outputStates?.[OutputChannel.Out8]?.name}`,
|
|
||||||
icon: <TextIcon>Out 8</TextIcon>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'miscellaneous',
|
|
||||||
label: 'Miscellaneous',
|
|
||||||
icon: <TextIcon>Out 1</TextIcon>,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const presets = dsp.state?.presets.names
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: 'f00',
|
|
||||||
name: 'Factory Preset',
|
|
||||||
},
|
|
||||||
...dsp.state.presets.names.map((name, index) => ({
|
|
||||||
id: `u${String(index + 1).padStart(2, '0')}`,
|
|
||||||
name,
|
|
||||||
})),
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
id: 'f00',
|
|
||||||
name: 'Factory Preset',
|
|
||||||
},
|
|
||||||
...Array.from({ length: 20 }, (_, index) => ({
|
|
||||||
id: `u${String(index + 1).padStart(2, '0')}`,
|
|
||||||
name: 'Default Preset',
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
const onAppChange = async (id: string) => {
|
|
||||||
const presetIndex = presets.findIndex((preset) => preset.id === id);
|
|
||||||
if (presetIndex === -1) return;
|
|
||||||
|
|
||||||
setPresetLoading(true);
|
|
||||||
const success = await dsp408.recallPreset(presetIndex);
|
|
||||||
if (success) {
|
|
||||||
setSelectedPreset(presetIndex);
|
|
||||||
}
|
|
||||||
setPresetLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderTab = () => {
|
|
||||||
switch (activeSidebar) {
|
|
||||||
case 'overview':
|
|
||||||
return (
|
|
||||||
<ConnectionPanel dsp={dsp} onConnect={dsp408.connect} onDisconnect={dsp408.disconnect} />
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'gain':
|
|
||||||
return (
|
|
||||||
<GainSection
|
|
||||||
dsp={dsp}
|
|
||||||
setGain={dsp408.setGain}
|
|
||||||
setMute={dsp408.setMute}
|
|
||||||
setInverse={dsp408.setInverse}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'gate':
|
|
||||||
return <GateSection dsp={dsp} set_gate={dsp408.setGate} />;
|
|
||||||
|
|
||||||
case 'compressor':
|
|
||||||
return <CompressorSection dsp={dsp} set_compressor={dsp408.setCompressor} />;
|
|
||||||
|
|
||||||
case 'limiter':
|
|
||||||
return <LimiterSection dsp={dsp} set_limiter={dsp408.setLimiter} />;
|
|
||||||
|
|
||||||
case 'delay':
|
|
||||||
return (
|
|
||||||
<DelaySection
|
|
||||||
dsp={dsp}
|
|
||||||
setDelay={dsp408.setDelay}
|
|
||||||
setDelayUnit={dsp408.setDelayUnit}
|
|
||||||
setDSPState={dsp408.setDSPState}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'ina':
|
|
||||||
return (
|
|
||||||
<InputSection
|
|
||||||
dsp={dsp}
|
|
||||||
channel={InputChannel.InA}
|
|
||||||
set_low_pass={dsp408.setLowPass}
|
|
||||||
set_high_pass={dsp408.setHighPass}
|
|
||||||
setPeq={dsp408.setPeqBand}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'matrix':
|
|
||||||
return (
|
|
||||||
<MatrixSection
|
|
||||||
dsp={dsp}
|
|
||||||
setMatrixRouting={dsp408.setMatrixRouting}
|
|
||||||
setMatrixGain={dsp408.setMatrixGain}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'geq':
|
|
||||||
return (
|
|
||||||
<GeqSection dsp={dsp} setGeqBand={dsp408.setGeqBand} setGeqBypass={dsp408.setGeqBypass} />
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`app-root ${sidebarExpanded ? 'app-root--sidebar-expanded' : ''}`}>
|
|
||||||
<Sidebar
|
|
||||||
items={sidebarItems}
|
|
||||||
activeId={activeSidebar}
|
|
||||||
onSelect={setActiveSidebar}
|
|
||||||
appName={dsp.name}
|
|
||||||
appOptions={presets}
|
|
||||||
selectedAppId={presets[selectedPreset]?.id}
|
|
||||||
onAppChange={onAppChange}
|
|
||||||
onAppRename={dsp408.setCurrentPresetName}
|
|
||||||
expanded={sidebarExpanded}
|
|
||||||
onToggle={() => setSidebarExpanded((v) => !v)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoadingOverlay
|
|
||||||
active={presetLoading}
|
|
||||||
label="Recalling preset…"
|
|
||||||
overlayClassName="dsp-loading-overlay"
|
|
||||||
>
|
|
||||||
<div className="dsp-content">{renderTab()}</div>
|
|
||||||
</LoadingOverlay>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DSPPage;
|
export default DSPPage;
|
||||||
|
|||||||
@@ -379,8 +379,8 @@ export const defaultDSPState: DSPState = {
|
|||||||
|
|
||||||
presets: {
|
presets: {
|
||||||
current_index: 0,
|
current_index: 0,
|
||||||
names: ['Default'],
|
names: Array(20).fill('Default'),
|
||||||
modified: [false],
|
modified: Array(20).fill(false),
|
||||||
},
|
},
|
||||||
|
|
||||||
current_config: {
|
current_config: {
|
||||||
+41
-26
@@ -1,33 +1,8 @@
|
|||||||
import { DSPState, Meters } from './dsp408State';
|
import { DSPState as DSP408State, Meters as DSP408Meters } from './dsp408';
|
||||||
|
|
||||||
export type DSPStatus = 'connected' | 'connecting' | 'disconnected';
|
export type DSPStatus = 'connected' | 'connecting' | 'disconnected';
|
||||||
|
|
||||||
export type DSP = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
deviceId: number;
|
|
||||||
status: DSPStatus;
|
|
||||||
state: DSPState;
|
|
||||||
meters: Meters;
|
|
||||||
pollingPaused: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DSP_TYPES = [
|
|
||||||
{
|
|
||||||
name: 'DSP408',
|
|
||||||
defaultPort: 9761,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'DSP204',
|
|
||||||
defaultPort: 94,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export type NotificationType = 'success' | 'error' | 'warning';
|
export type NotificationType = 'success' | 'error' | 'warning';
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: number;
|
id: number;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -36,3 +11,43 @@ export type Notification = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AppPage = 'home' | 'add-dsp' | 'view-dsp' | 'credits';
|
export type AppPage = 'home' | 'add-dsp' | 'view-dsp' | 'credits';
|
||||||
|
|
||||||
|
type DSPDefaultParameters = {
|
||||||
|
ip: string;
|
||||||
|
defaultPort: number;
|
||||||
|
device_id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DSPTypes = 'DSP408' | 'DSP204';
|
||||||
|
|
||||||
|
export const DSP_DEFAULT_PARAMETERS: Record<DSPTypes, DSPDefaultParameters> = {
|
||||||
|
DSP408: { ip: '192.168.49.30', defaultPort: 9761, device_id: 1 },
|
||||||
|
DSP204: { ip: '192.168.49.30', defaultPort: 94, device_id: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DSPBase {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
deviceId: number;
|
||||||
|
demo: boolean;
|
||||||
|
|
||||||
|
status: DSPStatus;
|
||||||
|
|
||||||
|
pollingPaused: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DSP408 = DSPBase & {
|
||||||
|
type: 'DSP408';
|
||||||
|
state: DSP408State;
|
||||||
|
meters: DSP408Meters;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DSP204 = DSPBase & {
|
||||||
|
type: 'DSP204';
|
||||||
|
state: null;
|
||||||
|
meters: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DSP = DSP408 | DSP204;
|
||||||
|
|||||||
Reference in New Issue
Block a user