feat: remove useless api file
This commit is contained in:
@@ -339,3 +339,65 @@ pub async fn set_delay(
|
|||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_matrix_routing(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
output: types::OutputChannel,
|
||||||
|
inputs: Vec<types::InputChannel>,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_matrix_routing(output, inputs)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_matrix_gain(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
input_channel: types::InputChannel,
|
||||||
|
output_channel: types::OutputChannel,
|
||||||
|
gain: f32,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_matrix_gain(input_channel, output_channel, gain)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_geq_band(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
input_channel: types::InputChannel,
|
||||||
|
frequency_band: types::DiscreteFrequency,
|
||||||
|
gain: f32,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_geq_band(input_channel, frequency_band, gain)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn set_geq_bypass(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
id: u64,
|
||||||
|
input_channel: types::InputChannel,
|
||||||
|
bypass: bool,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
state.with_device_mut(id, |device| {
|
||||||
|
device
|
||||||
|
.dsp
|
||||||
|
.set_geq_bypass(input_channel, bypass)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -25,6 +25,10 @@ pub fn run() {
|
|||||||
commands::set_limiter,
|
commands::set_limiter,
|
||||||
commands::set_delay_unit,
|
commands::set_delay_unit,
|
||||||
commands::set_delay,
|
commands::set_delay,
|
||||||
|
commands::set_matrix_routing,
|
||||||
|
commands::set_matrix_gain,
|
||||||
|
commands::set_geq_band,
|
||||||
|
commands::set_geq_bypass,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"title": "thomann-dsp-app",
|
"title": "thomann-dsp-app",
|
||||||
"width": 1200,
|
"width": 1200,
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"minWidth": 1000,
|
"minWidth": 1200,
|
||||||
"minHeight": 600
|
"minHeight": 600
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// CompressorSection.tsx
|
// CompressorSection.tsx
|
||||||
import VerticalSlider from '../Slider/VerticalSlider';
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
import { OutputChannel, Ratio } from '../../../types/dsp408State';
|
import { OutputChannel, Ratio } from '../../../types/dsp408State';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import VerticalSlider from '../Slider/VerticalSlider';
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel, Channel, OutputChannel, DelayUnit } from '../../../types/dsp408State';
|
import { InputChannel, Channel, OutputChannel, DelayUnit } from '../../../types/dsp408State';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import VerticalSlider from '../Slider/VerticalSlider';
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
import { InputChannel, Channel, OutputChannel } from '../../../types/dsp408State';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP } from '../../../types/types';
|
||||||
import Button from '../../Button/Button';
|
import Button from '../../Button/Button';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// GateSection.tsx
|
// GateSection.tsx
|
||||||
import VerticalSlider from '../Slider/VerticalSlider';
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
import { InputChannel } from '../../../types/dsp408State';
|
import { InputChannel } from '../../../types/dsp408State';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP } from '../../../types/types';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
.geq-section {
|
||||||
|
container-type: size;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: var(--dsp-control-heigth);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background: var(--bg-panel);
|
||||||
|
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
border-top: var(--border-width) solid var(--border-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-panels {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-2);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-band-panel {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 4.2;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
/* padding: 2cqh 1.5cqh; */
|
||||||
|
|
||||||
|
border: var(--border-width) solid var(--border-hairline);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-sizing: border-box;
|
||||||
|
gap: 0cqh;
|
||||||
|
background: var(--bg-void);
|
||||||
|
padding-bottom: 2cqh;
|
||||||
|
padding-top: 2cqh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-band-cell {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 2cqh;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.6cqh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-band-cell__label {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 3.2cqh;
|
||||||
|
color: var(--text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-channel-selector {
|
||||||
|
height: 100%;
|
||||||
|
width: 20cqh;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5cqh;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-side-panels {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 2cqh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-side-panels > :first-child {
|
||||||
|
flex: 0 0 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-side-panels > :last-child {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-linear-graph-section {
|
||||||
|
flex: 0 0 calc(100% - var(--dsp-control-heigth));
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1cqh;
|
||||||
|
padding: 0.5cqh;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--bg-panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-graph-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
border: var(--border-width) solid var(--border-hairline);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-graph-title {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
padding: 1cqh;
|
||||||
|
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 2.5cqh;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geq-graph-panel > .container {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
|
import { InputChannel, DiscreteFrequency } from '../../../types/dsp408State';
|
||||||
|
import { DSP } from '../../../types/types';
|
||||||
|
import Button from '../../Button/Button';
|
||||||
|
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||||
|
import { LinearGraph } from '../LinearGraph/LinearGraph';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import './GeqSection.css';
|
||||||
|
|
||||||
|
const GEQ_BANDS = [
|
||||||
|
{ frequency: DiscreteFrequency.F20, label: '20' },
|
||||||
|
{ frequency: DiscreteFrequency.F25, label: '25' },
|
||||||
|
{ frequency: DiscreteFrequency.F31_5, label: '31.5' },
|
||||||
|
{ frequency: DiscreteFrequency.F40, label: '40' },
|
||||||
|
{ frequency: DiscreteFrequency.F50, label: '50' },
|
||||||
|
{ frequency: DiscreteFrequency.F63, label: '63' },
|
||||||
|
{ frequency: DiscreteFrequency.F80, label: '80' },
|
||||||
|
{ frequency: DiscreteFrequency.F100, label: '100' },
|
||||||
|
{ frequency: DiscreteFrequency.F125, label: '125' },
|
||||||
|
{ frequency: DiscreteFrequency.F160, label: '160' },
|
||||||
|
{ frequency: DiscreteFrequency.F200, label: '200' },
|
||||||
|
{ frequency: DiscreteFrequency.F250, label: '250' },
|
||||||
|
{ frequency: DiscreteFrequency.F315, label: '315' },
|
||||||
|
{ frequency: DiscreteFrequency.F400, label: '400' },
|
||||||
|
{ frequency: DiscreteFrequency.F500, label: '500' },
|
||||||
|
{ frequency: DiscreteFrequency.F630, label: '630' },
|
||||||
|
{ frequency: DiscreteFrequency.F800, label: '800' },
|
||||||
|
{ frequency: DiscreteFrequency.F1000, label: '1k' },
|
||||||
|
{ frequency: DiscreteFrequency.F1250, label: '1.25k' },
|
||||||
|
{ frequency: DiscreteFrequency.F1600, label: '1.6k' },
|
||||||
|
{ frequency: DiscreteFrequency.F2000, label: '2k' },
|
||||||
|
{ frequency: DiscreteFrequency.F2500, label: '2.5k' },
|
||||||
|
{ frequency: DiscreteFrequency.F3150, label: '3.15k' },
|
||||||
|
{ frequency: DiscreteFrequency.F4000, label: '4k' },
|
||||||
|
{ frequency: DiscreteFrequency.F5000, label: '5k' },
|
||||||
|
{ frequency: DiscreteFrequency.F6300, label: '6.3k' },
|
||||||
|
{ frequency: DiscreteFrequency.F8000, label: '8k' },
|
||||||
|
{ frequency: DiscreteFrequency.F10000, label: '10k' },
|
||||||
|
{ frequency: DiscreteFrequency.F12500, label: '12.5k' },
|
||||||
|
{ frequency: DiscreteFrequency.F16000, label: '16k' },
|
||||||
|
{ frequency: DiscreteFrequency.F20000, label: '20k' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const GEQ_GAIN_MIN = -12;
|
||||||
|
const GEQ_GAIN_MAX = 12;
|
||||||
|
const GEQ_GAIN_STEP = 0.1;
|
||||||
|
|
||||||
|
const INPUT_ORDER: InputChannel[] = [
|
||||||
|
InputChannel.InA,
|
||||||
|
InputChannel.InB,
|
||||||
|
InputChannel.InC,
|
||||||
|
InputChannel.InD,
|
||||||
|
];
|
||||||
|
|
||||||
|
type LiveBands = Partial<Record<DiscreteFrequency, number>>;
|
||||||
|
|
||||||
|
type SetGeqBandFn = (
|
||||||
|
input: InputChannel,
|
||||||
|
band: DiscreteFrequency,
|
||||||
|
gain: number
|
||||||
|
) => Promise<boolean>;
|
||||||
|
|
||||||
|
type SetGeqBypassFn = (input: InputChannel, bypass: boolean) => Promise<boolean>;
|
||||||
|
|
||||||
|
function GeqBandPanel({
|
||||||
|
channel,
|
||||||
|
dsp,
|
||||||
|
setBand,
|
||||||
|
onLiveValue,
|
||||||
|
}: {
|
||||||
|
channel: InputChannel;
|
||||||
|
dsp: DSP;
|
||||||
|
setBand: SetGeqBandFn;
|
||||||
|
onLiveValue: (channel: InputChannel, band: DiscreteFrequency, value: number) => void;
|
||||||
|
}) {
|
||||||
|
const inputState = dsp.state.current_config.input_states[channel];
|
||||||
|
const geq = inputState.geq;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="geq-band-panel">
|
||||||
|
{GEQ_BANDS.map(({ frequency, label }) => (
|
||||||
|
<div className="geq-band-cell" key={frequency}>
|
||||||
|
<VerticalSlider
|
||||||
|
min={GEQ_GAIN_MIN}
|
||||||
|
max={GEQ_GAIN_MAX}
|
||||||
|
step={GEQ_GAIN_STEP}
|
||||||
|
unit=""
|
||||||
|
showTicks={false}
|
||||||
|
value={geq.gains[frequency]}
|
||||||
|
onInput={(value) => onLiveValue(channel, frequency, value)}
|
||||||
|
onChange={(value) => setBand(channel, frequency, value)}
|
||||||
|
style={{ width: '100%', flex: '1 1 auto' }}
|
||||||
|
/>
|
||||||
|
<div className="geq-band-cell__label">{label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeqGraph({
|
||||||
|
title,
|
||||||
|
channel,
|
||||||
|
dsp,
|
||||||
|
liveBands,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
channel: InputChannel;
|
||||||
|
dsp: DSP;
|
||||||
|
liveBands: LiveBands | undefined;
|
||||||
|
}) {
|
||||||
|
const geq = dsp.state.current_config.input_states[channel].geq;
|
||||||
|
|
||||||
|
const labelByX = useMemo(() => {
|
||||||
|
const map = new Map<number, string>();
|
||||||
|
GEQ_BANDS.forEach((band, i) => map.set(i + 1, band.label));
|
||||||
|
return map;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const values = GEQ_BANDS.map(
|
||||||
|
({ frequency }) => liveBands?.[frequency] ?? geq.gains[frequency] ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="geq-graph-panel">
|
||||||
|
<div className="geq-graph-title">{title}</div>
|
||||||
|
<LinearGraph
|
||||||
|
xMin={0.5}
|
||||||
|
xMax={GEQ_BANDS.length + 0.5}
|
||||||
|
xStep={1}
|
||||||
|
yMin={GEQ_GAIN_MIN}
|
||||||
|
yMax={GEQ_GAIN_MAX}
|
||||||
|
xLabel="Band Hz"
|
||||||
|
yLabel="dB"
|
||||||
|
showGrid={true}
|
||||||
|
formatX={(v) => labelByX.get(Math.round(v)) ?? ''}
|
||||||
|
formatY={(v) => v.toFixed(0)}
|
||||||
|
draw={({ ctx, toPx, plot }) => {
|
||||||
|
const barWidth = (plot.width / GEQ_BANDS.length) * 0.6;
|
||||||
|
|
||||||
|
values.forEach((gain, i) => {
|
||||||
|
const x = i + 1;
|
||||||
|
const [xPx, zeroY] = toPx(x, 0);
|
||||||
|
const [, gainY] = toPx(x, gain);
|
||||||
|
|
||||||
|
ctx.fillStyle = gain >= 0 ? '#4ade80' : '#f87171';
|
||||||
|
const top = Math.min(zeroY, gainY);
|
||||||
|
const height = Math.abs(gainY - zeroY);
|
||||||
|
ctx.fillRect(xPx - barWidth / 2, top, barWidth, height);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeqChannelSelector({
|
||||||
|
channel,
|
||||||
|
inputs,
|
||||||
|
onSelect,
|
||||||
|
}: {
|
||||||
|
channel: InputChannel;
|
||||||
|
inputs: { channel: InputChannel; label: string }[];
|
||||||
|
onSelect: (channel: InputChannel) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="geq-channel-selector">
|
||||||
|
{inputs.map(({ channel: c, label }) => (
|
||||||
|
<Button
|
||||||
|
key={c}
|
||||||
|
variant="toggle"
|
||||||
|
className={c === channel ? 'active-info' : ''}
|
||||||
|
onClick={() => onSelect(c)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
fontSize: '3.5cqh',
|
||||||
|
paddingTop: '2cqh',
|
||||||
|
paddingBottom: '2cqh',
|
||||||
|
minHeight: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeqActions({
|
||||||
|
bypass,
|
||||||
|
resetting,
|
||||||
|
onBypass,
|
||||||
|
onReset,
|
||||||
|
}: {
|
||||||
|
bypass: boolean;
|
||||||
|
resetting: boolean;
|
||||||
|
onBypass: () => void;
|
||||||
|
onReset: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="geq-channel-selector">
|
||||||
|
<Button
|
||||||
|
variant="toggle"
|
||||||
|
className={bypass ? 'active-info' : ''}
|
||||||
|
onClick={onBypass}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
fontSize: '3.5cqh',
|
||||||
|
paddingTop: '2cqh',
|
||||||
|
paddingBottom: '2cqh',
|
||||||
|
minHeight: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{bypass ? 'Bypassed' : 'Bypass'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="toggle"
|
||||||
|
onClick={onReset}
|
||||||
|
disabled={resetting}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
fontSize: '3.5cqh',
|
||||||
|
paddingTop: '2cqh',
|
||||||
|
paddingBottom: '2cqh',
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resetting ? 'Resetting…' : 'Reset'} {/* NEW */}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeqSection({
|
||||||
|
dsp,
|
||||||
|
setGeqBand,
|
||||||
|
setGeqBypass,
|
||||||
|
}: {
|
||||||
|
dsp: DSP;
|
||||||
|
setGeqBand: SetGeqBandFn;
|
||||||
|
setGeqBypass: SetGeqBypassFn;
|
||||||
|
}) {
|
||||||
|
const [activeChannel, setActiveChannel] = useState<InputChannel>(INPUT_ORDER[0]);
|
||||||
|
const [liveGeq, setLiveGeq] = useState<Partial<Record<InputChannel, LiveBands>>>({});
|
||||||
|
const [resetting, setResetting] = useState(false); // NEW
|
||||||
|
|
||||||
|
const handleLiveValue = (channel: InputChannel, band: DiscreteFrequency, value: number) => {
|
||||||
|
setLiveGeq((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[channel]: { ...prev[channel], [band]: value },
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetBand: SetGeqBandFn = async (channel, band, gain) => {
|
||||||
|
const success = await setGeqBand(channel, band, gain);
|
||||||
|
if (success) {
|
||||||
|
setLiveGeq((prev) => {
|
||||||
|
const channelLive = prev[channel];
|
||||||
|
if (!channelLive || !(band in channelLive)) return prev;
|
||||||
|
const next = { ...channelLive };
|
||||||
|
delete next[band];
|
||||||
|
return { ...prev, [channel]: next };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NEW: sequential per-band reset, since hardware has no bulk reset command.
|
||||||
|
const handleReset = async (channel: InputChannel) => {
|
||||||
|
if (resetting) return;
|
||||||
|
setResetting(true);
|
||||||
|
|
||||||
|
for (const { frequency } of GEQ_BANDS) {
|
||||||
|
// Skip bands already at 0 to save a round trip.
|
||||||
|
const current = dsp.state.current_config.input_states[channel].geq.gains[frequency] ?? 0;
|
||||||
|
if (current === 0) continue;
|
||||||
|
|
||||||
|
await handleSetBand(channel, frequency, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setResetting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeInputState = dsp.state.current_config.input_states[activeChannel];
|
||||||
|
const activeGeq = activeInputState.geq;
|
||||||
|
const activeLiveBands = liveGeq[activeChannel];
|
||||||
|
|
||||||
|
const handleBypassClick = () => {
|
||||||
|
setGeqBypass(activeChannel, !activeGeq.bypass);
|
||||||
|
};
|
||||||
|
|
||||||
|
const channelOptions = INPUT_ORDER.map((channel) => ({
|
||||||
|
channel,
|
||||||
|
label: dsp.state.current_config.input_states[channel].name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="geq-linear-graph-section">
|
||||||
|
<GeqGraph
|
||||||
|
title={`${activeInputState.name} - GEQ`}
|
||||||
|
channel={activeChannel}
|
||||||
|
dsp={dsp}
|
||||||
|
liveBands={activeLiveBands}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="geq-section">
|
||||||
|
<div className="geq-panels">
|
||||||
|
<ChannelGroup title="Bands">
|
||||||
|
<GeqBandPanel
|
||||||
|
channel={activeChannel}
|
||||||
|
dsp={dsp}
|
||||||
|
setBand={handleSetBand}
|
||||||
|
onLiveValue={handleLiveValue}
|
||||||
|
/>
|
||||||
|
</ChannelGroup>
|
||||||
|
|
||||||
|
<div className="geq-side-panels">
|
||||||
|
<ChannelGroup title="Input">
|
||||||
|
<GeqChannelSelector
|
||||||
|
channel={activeChannel}
|
||||||
|
inputs={channelOptions}
|
||||||
|
onSelect={setActiveChannel}
|
||||||
|
/>
|
||||||
|
</ChannelGroup>
|
||||||
|
|
||||||
|
<ChannelGroup title="Actions">
|
||||||
|
<GeqActions
|
||||||
|
bypass={activeGeq.bypass}
|
||||||
|
resetting={resetting}
|
||||||
|
onBypass={handleBypassClick}
|
||||||
|
onReset={() => handleReset(activeChannel)}
|
||||||
|
/>
|
||||||
|
</ChannelGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GeqSection;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// LimiterSection.tsx
|
// LimiterSection.tsx
|
||||||
import VerticalSlider from '../Slider/VerticalSlider';
|
import VerticalSlider from '../VerticalSlider/VerticalSlider';
|
||||||
import { OutputChannel } from '../../../types/dsp408State';
|
import { OutputChannel } from '../../../types/dsp408State';
|
||||||
import { DSP } from '../../../types/types';
|
import { DSP } from '../../../types/types';
|
||||||
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
import ChannelGroup from '../ChannelGroup/ChannelGroup';
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export const LinearGraph = forwardRef<LinearGraphHandle, LinearGraphProps>(funct
|
|||||||
const bottomBase = cq.h * 4;
|
const bottomBase = cq.h * 4;
|
||||||
const leftBase = cq.w * 2;
|
const leftBase = cq.w * 2;
|
||||||
const xLabelSpace = xLabel ? cq.h * 7 : 0;
|
const xLabelSpace = xLabel ? cq.h * 7 : 0;
|
||||||
const yLabelSpace = yLabel ? cq.w * 6 : 0;
|
const yLabelSpace = yLabel ? Math.min(cq.w * 6, 24) : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: padding?.top ?? topBase,
|
top: padding?.top ?? topBase,
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
.matrix-section {
|
||||||
|
container-type: size;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: var(--dsp-control-heigth);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
background: var(--bg-panel);
|
||||||
|
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
border-top: var(--border-width) solid var(--border-hairline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Placeholder graph area (top) ---------- */
|
||||||
|
/* Intentionally minimal for now — see chat for ideas on what belongs here. */
|
||||||
|
|
||||||
|
.matrix-graph-section {
|
||||||
|
flex: 0 0 calc(100% - var(--dsp-control-heigth));
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1cqh;
|
||||||
|
padding: 0.5cqh;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--bg-panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-graph-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border: var(--border-width) dashed var(--border-hairline);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Matrix grid (bottom) ---------- */
|
||||||
|
|
||||||
|
.matrix-grid-panel {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding: var(--space-2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-grid {
|
||||||
|
display: grid;
|
||||||
|
/* 1 corner cell + 1 column per input */
|
||||||
|
grid-template-columns: minmax(8ch, 12cqh) repeat(var(--matrix-input-count, 4), minmax(9cqh, 1fr));
|
||||||
|
/* 1 header row + 1 row per output */
|
||||||
|
grid-auto-rows: minmax(6cqh, 1fr);
|
||||||
|
|
||||||
|
gap: var(--border-width);
|
||||||
|
background: var(--border-hairline);
|
||||||
|
|
||||||
|
border: var(--border-width) solid var(--border-hairline);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-grid__corner,
|
||||||
|
.matrix-grid__col-header,
|
||||||
|
.matrix-grid__row-header {
|
||||||
|
background: var(--bg-void);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--text-muted);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
font-size: 2.5cqh;
|
||||||
|
padding: 0.5cqh;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-grid__row-header {
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-left: 1cqh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background: var(--bg-panel);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5cqh;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell:focus-visible {
|
||||||
|
outline: 2px solid var(--accent-brand);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell--off {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell--off .matrix-cell__dot {
|
||||||
|
width: 0.8cqh;
|
||||||
|
height: 0.8cqh;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell--on {
|
||||||
|
background: color-mix(in srgb, var(--accent-brand) 14%, var(--bg-panel));
|
||||||
|
color: var(--accent-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell--dragging {
|
||||||
|
background: color-mix(in srgb, var(--accent-brand) 26%, var(--bg-panel));
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell__gain {
|
||||||
|
font-size: 2.6cqh;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell__link-icon {
|
||||||
|
width: 2.2cqh;
|
||||||
|
height: 2.2cqh;
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// MatrixSection.tsx
|
||||||
|
import { Fragment, useRef, useState } from 'react';
|
||||||
|
import { InputChannel, OutputChannel } from '../../../types/dsp408State';
|
||||||
|
import { DSP } from '../../../types/types';
|
||||||
|
import './MatrixSection.css';
|
||||||
|
|
||||||
|
// ASSUMPTION: an InputChannel enum exists alongside OutputChannel in
|
||||||
|
// dsp408State, e.g. In1..In4. Adjust the import/labels below if the real
|
||||||
|
// enum differs.
|
||||||
|
const INPUTS: InputChannel[] = [
|
||||||
|
InputChannel.InA,
|
||||||
|
InputChannel.InB,
|
||||||
|
InputChannel.InC,
|
||||||
|
InputChannel.InD,
|
||||||
|
];
|
||||||
|
|
||||||
|
const OUTPUTS: OutputChannel[] = [
|
||||||
|
OutputChannel.Out1,
|
||||||
|
OutputChannel.Out2,
|
||||||
|
OutputChannel.Out3,
|
||||||
|
OutputChannel.Out4,
|
||||||
|
OutputChannel.Out5,
|
||||||
|
OutputChannel.Out6,
|
||||||
|
OutputChannel.Out7,
|
||||||
|
OutputChannel.Out8,
|
||||||
|
];
|
||||||
|
|
||||||
|
const INPUT_LABEL: Record<InputChannel, string> = {
|
||||||
|
[InputChannel.InA]: 'In 1',
|
||||||
|
[InputChannel.InB]: 'In 2',
|
||||||
|
[InputChannel.InC]: 'In 3',
|
||||||
|
[InputChannel.InD]: 'In 4',
|
||||||
|
};
|
||||||
|
|
||||||
|
const GAIN_MIN = -60;
|
||||||
|
const GAIN_MAX = 12;
|
||||||
|
const GAIN_DEFAULT = 0; // gain a link is set to the moment it's first routed
|
||||||
|
const GAIN_STEP = 0.5; // wheel / arrow-key nudge
|
||||||
|
const DRAG_PX_PER_DB = 4; // vertical drag sensitivity
|
||||||
|
|
||||||
|
function clampGain(v: number) {
|
||||||
|
return Math.min(GAIN_MAX, Math.max(GAIN_MIN, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatGain(v: number) {
|
||||||
|
return `${v > 0 ? '+' : ''}${v.toFixed(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSUMPTION: each output tracks which inputs are currently routed to it
|
||||||
|
// (output_states[out].matrix_inputs: InputChannel[]) and the last-known gain
|
||||||
|
// per input (output_states[out].matrix_gains: Partial<Record<InputChannel,
|
||||||
|
// number>>, in dB, kept around even if the link is later removed so it can
|
||||||
|
// be restored). Adjust these two accessors if the real shape differs —
|
||||||
|
// everything else only depends on the two functions below.
|
||||||
|
type MatrixOutputState = {
|
||||||
|
matrix_inputs?: InputChannel[];
|
||||||
|
matrix_gains?: Partial<Record<InputChannel, number>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isRouted(dsp: DSP, output: OutputChannel, input: InputChannel): boolean {
|
||||||
|
const state = dsp.state.current_config.output_states[output] as unknown as MatrixOutputState;
|
||||||
|
return state.matrix_inputs?.includes(input) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLinkGain(dsp: DSP, output: OutputChannel, input: InputChannel): number {
|
||||||
|
const state = dsp.state.current_config.output_states[output] as unknown as MatrixOutputState;
|
||||||
|
return state.matrix_gains?.[input] ?? GAIN_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoutedInputs(dsp: DSP, output: OutputChannel): InputChannel[] {
|
||||||
|
const state = dsp.state.current_config.output_states[output] as unknown as MatrixOutputState;
|
||||||
|
return state.matrix_inputs ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function MatrixCell({
|
||||||
|
output,
|
||||||
|
input,
|
||||||
|
connected,
|
||||||
|
gain,
|
||||||
|
onToggle,
|
||||||
|
onGainChange,
|
||||||
|
}: {
|
||||||
|
output: OutputChannel;
|
||||||
|
input: InputChannel;
|
||||||
|
connected: boolean;
|
||||||
|
gain: number;
|
||||||
|
onToggle: () => void;
|
||||||
|
onGainChange: (gain: number) => void;
|
||||||
|
}) {
|
||||||
|
const [liveGain, setLiveGain] = useState<number | null>(null);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const dragStart = useRef<{ y: number; base: number } | null>(null);
|
||||||
|
|
||||||
|
const displayGain = liveGain ?? gain;
|
||||||
|
|
||||||
|
const handlePointerDown = (e: React.PointerEvent) => {
|
||||||
|
if (!connected) return; // plain click on an off cell just routes it (see onClick)
|
||||||
|
e.currentTarget.setPointerCapture(e.pointerId);
|
||||||
|
dragStart.current = { y: e.clientY, base: gain };
|
||||||
|
setDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e: React.PointerEvent) => {
|
||||||
|
if (!dragStart.current) return;
|
||||||
|
const deltaPx = dragStart.current.y - e.clientY; // up = louder
|
||||||
|
const next = clampGain(dragStart.current.base + deltaPx / DRAG_PX_PER_DB);
|
||||||
|
setLiveGain(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
const endDrag = () => {
|
||||||
|
if (dragStart.current && liveGain !== null) {
|
||||||
|
onGainChange(liveGain);
|
||||||
|
}
|
||||||
|
dragStart.current = null;
|
||||||
|
setDragging(false);
|
||||||
|
setLiveGain(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
|
if (!connected) return;
|
||||||
|
e.preventDefault();
|
||||||
|
onGainChange(clampGain(gain + (e.deltaY < 0 ? GAIN_STEP : -GAIN_STEP)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
onToggle();
|
||||||
|
} else if (connected && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const dir = e.key === 'ArrowUp' ? 1 : -1;
|
||||||
|
onGainChange(clampGain(gain + dir * GAIN_STEP));
|
||||||
|
} else if (connected && (e.key === 'Backspace' || e.key === 'Delete')) {
|
||||||
|
e.preventDefault();
|
||||||
|
onToggle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-pressed={connected}
|
||||||
|
aria-label={`${INPUT_LABEL[input]} to ${String(output)}${
|
||||||
|
connected ? `, ${formatGain(displayGain)} dB` : ', not routed'
|
||||||
|
}`}
|
||||||
|
className={[
|
||||||
|
'matrix-cell',
|
||||||
|
connected ? 'matrix-cell--on' : 'matrix-cell--off',
|
||||||
|
dragging ? 'matrix-cell--dragging' : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')}
|
||||||
|
onClick={connected ? undefined : onToggle}
|
||||||
|
onDoubleClick={connected ? onToggle : undefined}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
onPointerUp={endDrag}
|
||||||
|
onPointerCancel={endDrag}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
title={
|
||||||
|
connected
|
||||||
|
? `${formatGain(displayGain)} dB — drag/scroll to adjust, double-click to remove`
|
||||||
|
: 'Click to route'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{connected ? (
|
||||||
|
<span className="matrix-cell__gain">{formatGain(displayGain)}</span>
|
||||||
|
) : (
|
||||||
|
<span className="matrix-cell__dot" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MatrixSection({
|
||||||
|
dsp,
|
||||||
|
setMatrixRouting,
|
||||||
|
setMatrixGain,
|
||||||
|
}: {
|
||||||
|
dsp: DSP;
|
||||||
|
setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) => Promise<boolean>;
|
||||||
|
setMatrixGain: (
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
outputChannel: OutputChannel,
|
||||||
|
gain: number
|
||||||
|
) => Promise<boolean>;
|
||||||
|
}) {
|
||||||
|
// Toggling a cell sends the *whole* next input list for that output —
|
||||||
|
// setMatrixRouting replaces, it doesn't add/remove a single link.
|
||||||
|
const handleToggle = (output: OutputChannel, input: InputChannel) => {
|
||||||
|
const current = getRoutedInputs(dsp, output);
|
||||||
|
const nowConnecting = !current.includes(input);
|
||||||
|
const next = nowConnecting ? [...current, input] : current.filter((i) => i !== input);
|
||||||
|
|
||||||
|
void setMatrixRouting(output, next).then((ok) => {
|
||||||
|
// Give a freshly-connected link a defined starting gain rather than
|
||||||
|
// leaving it at whatever stale value the backend last had for it.
|
||||||
|
if (ok && nowConnecting) {
|
||||||
|
void setMatrixGain(input, output, GAIN_DEFAULT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGainChange = (output: OutputChannel, input: InputChannel, gain: number) => {
|
||||||
|
void setMatrixGain(input, output, gain);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="matrix-graph-section">
|
||||||
|
<div className="matrix-graph-panel">Routing overview — see chat for ideas</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="matrix-section">
|
||||||
|
<div
|
||||||
|
className="matrix-grid-panel"
|
||||||
|
style={{ ['--matrix-input-count' as string]: INPUTS.length }}
|
||||||
|
>
|
||||||
|
<div className="matrix-grid">
|
||||||
|
<div className="matrix-grid__corner" />
|
||||||
|
{INPUTS.map((input) => (
|
||||||
|
<div className="matrix-grid__col-header" key={String(input)}>
|
||||||
|
{INPUT_LABEL[input]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{OUTPUTS.map((output) => (
|
||||||
|
<Fragment key={String(output)}>
|
||||||
|
<div className="matrix-grid__row-header">
|
||||||
|
{dsp.state.current_config.output_states[output].name}
|
||||||
|
</div>
|
||||||
|
{INPUTS.map((input) => {
|
||||||
|
const connected = isRouted(dsp, output, input);
|
||||||
|
return (
|
||||||
|
<MatrixCell
|
||||||
|
key={`${String(output)}-${String(input)}`}
|
||||||
|
output={output}
|
||||||
|
input={input}
|
||||||
|
connected={connected}
|
||||||
|
gain={getLinkGain(dsp, output, input)}
|
||||||
|
onToggle={() => handleToggle(output, input)}
|
||||||
|
onGainChange={(gain) => handleGainChange(output, input, gain)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MatrixSection;
|
||||||
+10
@@ -26,6 +26,11 @@
|
|||||||
padding-top: 1.2cqh;
|
padding-top: 1.2cqh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gauge-track.no-ticks {
|
||||||
|
padding-left: calc(2.75cqh);
|
||||||
|
padding-right: calc(2.75cqh);
|
||||||
|
}
|
||||||
|
|
||||||
.ticks {
|
.ticks {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
@@ -114,6 +119,11 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.value.no-ticks {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.value input {
|
.value input {
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
+23
-13
@@ -16,6 +16,8 @@ type VerticalSliderProps = {
|
|||||||
|
|
||||||
unit?: string;
|
unit?: string;
|
||||||
|
|
||||||
|
showTicks?: boolean;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
};
|
};
|
||||||
@@ -39,6 +41,7 @@ export default function VerticalSlider({
|
|||||||
onChange,
|
onChange,
|
||||||
onInput,
|
onInput,
|
||||||
unit = '',
|
unit = '',
|
||||||
|
showTicks = true,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
}: VerticalSliderProps) {
|
}: VerticalSliderProps) {
|
||||||
@@ -221,12 +224,17 @@ export default function VerticalSlider({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={['gauge-viewport', className].filter(Boolean).join(' ')} style={style}>
|
<div className={['gauge-viewport', className].filter(Boolean).join(' ')} style={style}>
|
||||||
<div className="gauge-track" ref={trackRef}>
|
<div
|
||||||
<div className="ticks left">
|
className={['gauge-track', !showTicks && 'no-ticks'].filter(Boolean).join(' ')}
|
||||||
{Array.from({ length: tickCount }).map((_, i) => (
|
ref={trackRef}
|
||||||
<span key={i} />
|
>
|
||||||
))}
|
{showTicks && (
|
||||||
</div>
|
<div className="ticks left">
|
||||||
|
{Array.from({ length: tickCount }).map((_, i) => (
|
||||||
|
<span key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="slider"
|
className="slider"
|
||||||
@@ -243,14 +251,16 @@ export default function VerticalSlider({
|
|||||||
<div ref={thumbRef} className="thumb" style={{ bottom: `${percent * 100}%` }} />
|
<div ref={thumbRef} className="thumb" style={{ bottom: `${percent * 100}%` }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ticks right">
|
{showTicks && (
|
||||||
{Array.from({ length: tickCount }).map((_, i) => (
|
<div className="ticks right">
|
||||||
<span key={i} />
|
{Array.from({ length: tickCount }).map((_, i) => (
|
||||||
))}
|
<span key={i} />
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="value">
|
<div className={['value', !showTicks && 'no-ticks'].filter(Boolean).join(' ')}>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -268,7 +278,7 @@ export default function VerticalSlider({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="value-unit">{unit}</span>
|
{unit && <span className="value-unit">{unit}</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
+252
-11
@@ -14,19 +14,9 @@ import {
|
|||||||
DelayUnit,
|
DelayUnit,
|
||||||
Meters,
|
Meters,
|
||||||
DSPState,
|
DSPState,
|
||||||
|
DiscreteFrequency,
|
||||||
} from '../types/dsp408State';
|
} from '../types/dsp408State';
|
||||||
|
|
||||||
// import {
|
|
||||||
// createDSP408,
|
|
||||||
// removeDSP408,
|
|
||||||
// connectDSP408,
|
|
||||||
// disconnectDSP408,
|
|
||||||
// getDSP408State,
|
|
||||||
// recallPresetDSP408,
|
|
||||||
// DSP408SetCurrentPresetName,
|
|
||||||
// getDSP408MeterLevels,
|
|
||||||
// } from '../api/dsp408';
|
|
||||||
|
|
||||||
type NotifyFunction = (message: string, type: 'success' | 'error', dspName?: string) => void;
|
type NotifyFunction = (message: string, type: 'success' | 'error', dspName?: string) => void;
|
||||||
|
|
||||||
type SetDsps = React.Dispatch<React.SetStateAction<DSP[]>>;
|
type SetDsps = React.Dispatch<React.SetStateAction<DSP[]>>;
|
||||||
@@ -68,6 +58,18 @@ export type DSP408Device = {
|
|||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
setDelayUnit: (unit: DelayUnit) => Promise<boolean>;
|
setDelayUnit: (unit: DelayUnit) => Promise<boolean>;
|
||||||
setDelay: (channel: Channel, delay: number) => Promise<boolean>;
|
setDelay: (channel: Channel, delay: number) => Promise<boolean>;
|
||||||
|
setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) => Promise<boolean>;
|
||||||
|
setMatrixGain: (
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
outputChannel: OutputChannel,
|
||||||
|
gain: number
|
||||||
|
) => Promise<boolean>;
|
||||||
|
setGeqBand: (
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
frequencyBand: DiscreteFrequency,
|
||||||
|
gain: number
|
||||||
|
) => Promise<boolean>;
|
||||||
|
setGeqBypass: (inputChannel: InputChannel, bypass: boolean) => Promise<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Polling
|
// Polling
|
||||||
@@ -894,6 +896,237 @@ export function useDSP408(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matrix Routing
|
||||||
|
async function setMatrixRouting(
|
||||||
|
dsp: DSP,
|
||||||
|
output: OutputChannel,
|
||||||
|
inputs: InputChannel[]
|
||||||
|
): Promise<boolean> {
|
||||||
|
return withPollingPaused(dsp, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDSP(setDsps, dsp.id, (d) => {
|
||||||
|
if (!d.state) return d;
|
||||||
|
|
||||||
|
const currentConfig = d.state.current_config;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
state: {
|
||||||
|
...d.state,
|
||||||
|
current_config: {
|
||||||
|
...currentConfig,
|
||||||
|
output_states: {
|
||||||
|
...currentConfig.output_states,
|
||||||
|
[output]: {
|
||||||
|
...currentConfig.output_states[output],
|
||||||
|
matrix_routes: {
|
||||||
|
...currentConfig.output_states[output].matrix_routes,
|
||||||
|
connected: inputs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
notify('Matrix routing updated', 'success', dsp.name);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
notify(`Matrix routing update failed: ${String(err)}`, 'error', dsp.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix Gain
|
||||||
|
async function setMatrixGain(
|
||||||
|
dsp: DSP,
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
outputChannel: OutputChannel,
|
||||||
|
gain: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
return withPollingPaused(dsp, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDSP(setDsps, dsp.id, (d) => {
|
||||||
|
if (!d.state) return d;
|
||||||
|
|
||||||
|
const currentConfig = d.state.current_config;
|
||||||
|
|
||||||
|
const outputState = currentConfig.output_states[outputChannel];
|
||||||
|
|
||||||
|
const inputIndex = Object.values(InputChannel).indexOf(inputChannel);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
state: {
|
||||||
|
...d.state,
|
||||||
|
current_config: {
|
||||||
|
...currentConfig,
|
||||||
|
output_states: {
|
||||||
|
...currentConfig.output_states,
|
||||||
|
[outputChannel]: {
|
||||||
|
...outputState,
|
||||||
|
matrix_routes: {
|
||||||
|
...outputState.matrix_routes,
|
||||||
|
gains: outputState.matrix_routes.gains.map((g, i) =>
|
||||||
|
i === inputIndex ? gain : g
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
notify('Matrix gain updated', 'success', dsp.name);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
notify(`Matrix gain update failed: ${String(err)}`, 'error', dsp.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// GEQ Band
|
||||||
|
async function setGeqBand(
|
||||||
|
dsp: DSP,
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
frequencyBand: DiscreteFrequency,
|
||||||
|
gain: number
|
||||||
|
): Promise<boolean> {
|
||||||
|
return withPollingPaused(dsp, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDSP(setDsps, dsp.id, (d) => {
|
||||||
|
if (!d.state) return d;
|
||||||
|
|
||||||
|
const currentConfig = d.state.current_config;
|
||||||
|
const inputState = currentConfig.input_states[inputChannel];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
state: {
|
||||||
|
...d.state,
|
||||||
|
current_config: {
|
||||||
|
...currentConfig,
|
||||||
|
input_states: {
|
||||||
|
...currentConfig.input_states,
|
||||||
|
[inputChannel]: {
|
||||||
|
...inputState,
|
||||||
|
geq: {
|
||||||
|
...inputState.geq,
|
||||||
|
gains: {
|
||||||
|
...inputState.geq.gains,
|
||||||
|
[frequencyBand]: gain,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
notify('GEQ band updated', 'success', dsp.name);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
notify(`GEQ band update failed: ${String(err)}`, 'error', dsp.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// GEQ Bypass
|
||||||
|
async function setGeqBypass(
|
||||||
|
dsp: DSP,
|
||||||
|
inputChannel: InputChannel,
|
||||||
|
bypass: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
|
return withPollingPaused(dsp, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDSP(setDsps, dsp.id, (d) => {
|
||||||
|
if (!d.state) return d;
|
||||||
|
|
||||||
|
const currentConfig = d.state.current_config;
|
||||||
|
const inputState = currentConfig.input_states[inputChannel];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
state: {
|
||||||
|
...d.state,
|
||||||
|
current_config: {
|
||||||
|
...currentConfig,
|
||||||
|
input_states: {
|
||||||
|
...currentConfig.input_states,
|
||||||
|
[inputChannel]: {
|
||||||
|
...inputState,
|
||||||
|
geq: {
|
||||||
|
...inputState.geq,
|
||||||
|
bypass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Return API
|
// Return API
|
||||||
function forDSP(dsp: DSP): DSP408Device {
|
function forDSP(dsp: DSP): DSP408Device {
|
||||||
return {
|
return {
|
||||||
@@ -928,6 +1161,14 @@ export function useDSP408(
|
|||||||
) => setLimiter(dsp, outputChannel, threshold, attack, release),
|
) => setLimiter(dsp, outputChannel, threshold, attack, release),
|
||||||
setDelayUnit: (unit: DelayUnit) => setDelayUnit(dsp, unit),
|
setDelayUnit: (unit: DelayUnit) => setDelayUnit(dsp, unit),
|
||||||
setDelay: (channel: Channel, delay: number) => setDelay(dsp, channel, delay),
|
setDelay: (channel: Channel, delay: number) => setDelay(dsp, channel, delay),
|
||||||
|
setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) =>
|
||||||
|
setMatrixRouting(dsp, output, inputs),
|
||||||
|
setMatrixGain: (inputChannel: InputChannel, outputChannel: OutputChannel, gain: number) =>
|
||||||
|
setMatrixGain(dsp, inputChannel, outputChannel, gain),
|
||||||
|
setGeqBand: (inputChannel: InputChannel, frequencyBand: DiscreteFrequency, gain: number) =>
|
||||||
|
setGeqBand(dsp, inputChannel, frequencyBand, gain),
|
||||||
|
setGeqBypass: (inputChannel: InputChannel, bypass: boolean) =>
|
||||||
|
setGeqBypass(dsp, inputChannel, bypass),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import LimiterSection from '../../components/dsp408/LimiterSection/LimiterSectio
|
|||||||
import DelaySection from '../../components/dsp408/DelaySection/DelaySection';
|
import DelaySection from '../../components/dsp408/DelaySection/DelaySection';
|
||||||
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
|
||||||
import { InputChannel, OutputChannel } from '../../types/dsp408State';
|
import { InputChannel, OutputChannel } from '../../types/dsp408State';
|
||||||
|
import MatrixSection from '../../components/dsp408/Matrixsection/Matrixsection';
|
||||||
|
import GeqSection from '../../components/dsp408/GeqSection/GeqSection';
|
||||||
|
|
||||||
function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
||||||
const [activeSidebar, setActiveSidebar] = useState('overview');
|
const [activeSidebar, setActiveSidebar] = useState('overview');
|
||||||
@@ -198,6 +200,20 @@ function DSPPage({ dsp, dsp408 }: { dsp: DSP; dsp408: DSP408Device }) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,40 @@ export enum PEQFilter {
|
|||||||
AllPass2 = 'AllPass2',
|
AllPass2 = 'AllPass2',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DiscreteFrequency {
|
||||||
|
F20 = 'F20',
|
||||||
|
F25 = 'F25',
|
||||||
|
F31_5 = 'F31_5',
|
||||||
|
F40 = 'F40',
|
||||||
|
F50 = 'F50',
|
||||||
|
F63 = 'F63',
|
||||||
|
F80 = 'F80',
|
||||||
|
F100 = 'F100',
|
||||||
|
F125 = 'F125',
|
||||||
|
F160 = 'F160',
|
||||||
|
F200 = 'F200',
|
||||||
|
F250 = 'F250',
|
||||||
|
F315 = 'F315',
|
||||||
|
F400 = 'F400',
|
||||||
|
F500 = 'F500',
|
||||||
|
F630 = 'F630',
|
||||||
|
F800 = 'F800',
|
||||||
|
F1000 = 'F1000',
|
||||||
|
F1250 = 'F1250',
|
||||||
|
F1600 = 'F1600',
|
||||||
|
F2000 = 'F2000',
|
||||||
|
F2500 = 'F2500',
|
||||||
|
F3150 = 'F3150',
|
||||||
|
F4000 = 'F4000',
|
||||||
|
F5000 = 'F5000',
|
||||||
|
F6300 = 'F6300',
|
||||||
|
F8000 = 'F8000',
|
||||||
|
F10000 = 'F10000',
|
||||||
|
F12500 = 'F12500',
|
||||||
|
F16000 = 'F16000',
|
||||||
|
F20000 = 'F20000',
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- STATE ----------------
|
// ---------------- STATE ----------------
|
||||||
|
|
||||||
export type Gate = {
|
export type Gate = {
|
||||||
@@ -95,7 +129,7 @@ export type Limiter = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type GraphicEQ = {
|
export type GraphicEQ = {
|
||||||
gains: number[];
|
gains: Record<DiscreteFrequency, number>;
|
||||||
bypass: boolean;
|
bypass: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,7 +246,39 @@ const createDefaultGate = (): Gate => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createDefaultGraphicEQ = (): GraphicEQ => ({
|
const createDefaultGraphicEQ = (): GraphicEQ => ({
|
||||||
gains: Array(31).fill(0),
|
gains: {
|
||||||
|
[DiscreteFrequency.F20]: 0,
|
||||||
|
[DiscreteFrequency.F25]: 0,
|
||||||
|
[DiscreteFrequency.F31_5]: 0,
|
||||||
|
[DiscreteFrequency.F40]: 0,
|
||||||
|
[DiscreteFrequency.F50]: 0,
|
||||||
|
[DiscreteFrequency.F63]: 0,
|
||||||
|
[DiscreteFrequency.F80]: 0,
|
||||||
|
[DiscreteFrequency.F100]: 0,
|
||||||
|
[DiscreteFrequency.F125]: 0,
|
||||||
|
[DiscreteFrequency.F160]: 0,
|
||||||
|
[DiscreteFrequency.F200]: 0,
|
||||||
|
[DiscreteFrequency.F250]: 0,
|
||||||
|
[DiscreteFrequency.F315]: 0,
|
||||||
|
[DiscreteFrequency.F400]: 0,
|
||||||
|
[DiscreteFrequency.F500]: 0,
|
||||||
|
[DiscreteFrequency.F630]: 0,
|
||||||
|
[DiscreteFrequency.F800]: 0,
|
||||||
|
[DiscreteFrequency.F1000]: 0,
|
||||||
|
[DiscreteFrequency.F1250]: 0,
|
||||||
|
[DiscreteFrequency.F1600]: 0,
|
||||||
|
[DiscreteFrequency.F2000]: 0,
|
||||||
|
[DiscreteFrequency.F2500]: 0,
|
||||||
|
[DiscreteFrequency.F3150]: 0,
|
||||||
|
[DiscreteFrequency.F4000]: 0,
|
||||||
|
[DiscreteFrequency.F5000]: 0,
|
||||||
|
[DiscreteFrequency.F6300]: 0,
|
||||||
|
[DiscreteFrequency.F8000]: 0,
|
||||||
|
[DiscreteFrequency.F10000]: 0,
|
||||||
|
[DiscreteFrequency.F12500]: 0,
|
||||||
|
[DiscreteFrequency.F16000]: 0,
|
||||||
|
[DiscreteFrequency.F20000]: 0,
|
||||||
|
},
|
||||||
bypass: false,
|
bypass: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user