feat: remove state and let rust handle
This commit is contained in:
@@ -41,16 +41,28 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct DSP408Snapshot {
|
||||
pub state: types::DSPState,
|
||||
pub meters: types::Meters,
|
||||
pub mode: types::DSPMode,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_dsp408(
|
||||
state: State<AppState>,
|
||||
ip: String,
|
||||
port: u16,
|
||||
device_id: u8,
|
||||
demo: bool,
|
||||
) -> Result<u64, String> {
|
||||
let ip = IpAddr::from_str(&ip).map_err(|e| e.to_string())?;
|
||||
|
||||
let dsp = DSP408::new(ip, port, device_id).map_err(|e| e.to_string())?;
|
||||
let dsp = if demo {
|
||||
DSP408::new_demo()
|
||||
} else {
|
||||
DSP408::new(ip, port, device_id).map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
let mut next_id = state.next_id.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -81,6 +93,20 @@ pub async fn remove_dsp408(state: State<'_, AppState>, id: u64) -> Result<(), St
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_dsp_snapshot(
|
||||
state: State<'_, AppState>,
|
||||
id: u64,
|
||||
) -> Result<DSP408Snapshot, String> {
|
||||
state.with_device_mut(id, |device| {
|
||||
Ok(DSP408Snapshot {
|
||||
state: device.dsp.state().clone(),
|
||||
meters: device.dsp.meters().clone(),
|
||||
mode: device.dsp.mode(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect_dsp408(state: State<'_, AppState>, id: u64) -> Result<(), String> {
|
||||
state.with_device_mut(id, |device| device.dsp.connect().map_err(|e| e.to_string()))
|
||||
@@ -99,7 +125,7 @@ pub async fn get_dsp_state(
|
||||
id: u64,
|
||||
) -> Result<dsp_thomann::dsp408::types::DSPState, String> {
|
||||
state.with_device_mut(id, |device| {
|
||||
let dsp_state = device.dsp.state().map_err(|e| e.to_string())?;
|
||||
let dsp_state = device.dsp.state();
|
||||
|
||||
Ok(dsp_state.clone())
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::create_dsp408,
|
||||
commands::remove_dsp408,
|
||||
commands::get_dsp_snapshot,
|
||||
commands::get_dsp_state,
|
||||
commands::connect_dsp408, // 0x10
|
||||
commands::disconnect_dsp408, // 0x11
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export function TriangleUpIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M8 3L2 12H14L8 3Z"
|
||||
stroke="currentColor"
|
||||
@@ -19,12 +14,7 @@ export function TriangleUpIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
|
||||
export function TriangleDownIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M8 13L2 4H14L8 13Z"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -9,6 +9,8 @@ type ChoiceSelectProps<T extends string> = {
|
||||
onChange: (value: T) => void;
|
||||
id?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
function ChoiceSelect<T extends string>({
|
||||
@@ -18,15 +20,21 @@ function ChoiceSelect<T extends string>({
|
||||
onChange,
|
||||
id,
|
||||
disabled,
|
||||
className = '',
|
||||
style,
|
||||
}: ChoiceSelectProps<T>) {
|
||||
return (
|
||||
<div className="choice-select" data-disabled={disabled || undefined}>
|
||||
<div className={`choice-select ${className}`} data-disabled={disabled || undefined}>
|
||||
<select
|
||||
id={id}
|
||||
className="choice-select__input"
|
||||
style={style}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value as T)}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value as T);
|
||||
e.currentTarget.blur();
|
||||
}}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
@@ -34,6 +42,7 @@ function ChoiceSelect<T extends string>({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<span className="choice-select__chevron" aria-hidden="true">
|
||||
<TriangleDownIcon />
|
||||
</span>
|
||||
|
||||
@@ -67,9 +67,15 @@ function ChoiceStepperList<T>({
|
||||
useEffect(() => setText(formatLabel(value)), [value, formatLabel]);
|
||||
|
||||
const step = (direction: 1 | -1) => {
|
||||
const currentIndex = index === -1 ? 0 : index;
|
||||
const nextIndex = Math.min(values.length - 1, Math.max(0, currentIndex + direction));
|
||||
if (nextIndex !== currentIndex) onChange(values[nextIndex]);
|
||||
const currentIndex = values.indexOf(value);
|
||||
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
const nextIndex = currentIndex + direction;
|
||||
|
||||
if (nextIndex >= 0 && nextIndex < values.length) {
|
||||
onChange(values[nextIndex]);
|
||||
}
|
||||
};
|
||||
|
||||
const commitText = () => {
|
||||
@@ -89,6 +95,15 @@ function ChoiceStepperList<T>({
|
||||
disabled={disabled}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onBlur={commitText}
|
||||
onWheel={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.deltaY < 0 && canUp) {
|
||||
step(1);
|
||||
} else if (e.deltaY > 0 && canDown) {
|
||||
step(-1);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') e.currentTarget.blur();
|
||||
else if (e.key === 'ArrowUp') {
|
||||
@@ -164,14 +179,24 @@ function ChoiceStepperRange({
|
||||
disabled={disabled}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onBlur={commitText}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') e.currentTarget.blur();
|
||||
else if (e.key === 'ArrowUp') {
|
||||
onWheel={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.deltaY < 0 && canUp) {
|
||||
step(1);
|
||||
} else if (e.deltaY > 0 && canDown) {
|
||||
step(-1);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.currentTarget.blur();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (canUp) step(1);
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
step(-1);
|
||||
if (canDown) step(-1);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
box-sizing: border-box;
|
||||
background: var(--bg-panel);
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input-graph-panel {
|
||||
@@ -40,6 +43,9 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Bottom control bar — same pattern as .compressor-section: absolute,
|
||||
bottom-anchored, panels flow in a row and scroll horizontally instead
|
||||
of squeezing into a fixed max-width. */
|
||||
.input-control-section {
|
||||
container-type: size;
|
||||
position: absolute;
|
||||
@@ -50,7 +56,7 @@
|
||||
height: var(--dsp-control-heigth);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
@@ -60,104 +66,23 @@
|
||||
z-index: 20;
|
||||
|
||||
border-top: var(--border-width) solid var(--border-hairline);
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.input-control-panel {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 60cqw;
|
||||
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: center; /* ← add this */
|
||||
gap: var(--space-4);
|
||||
gap: var(--space-2);
|
||||
overflow-x: auto;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: 2cqh;
|
||||
}
|
||||
|
||||
.input-control-panel__sliders {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4cqh;
|
||||
}
|
||||
|
||||
.input-control-panel__cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
gap: 1cqh;
|
||||
}
|
||||
|
||||
.input-control-panel__cell-label {
|
||||
flex: 0 0 auto;
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2.5cqh;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.input-control-panel__side {
|
||||
flex: 0 0 auto;
|
||||
width: 16cqh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
gap: 2cqh;
|
||||
}
|
||||
|
||||
.input-control-panel__select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5cqh;
|
||||
}
|
||||
|
||||
.input-control-panel__select-label {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2.2cqh;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.input-control-panel__select select {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2.5cqh;
|
||||
padding: 0.8cqh;
|
||||
background: var(--bg-void);
|
||||
color: var(--text-primary);
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.input-control-panel__select select:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.input-control-panel__bypass-btn {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 2.5cqh;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
padding: 1cqh;
|
||||
|
||||
background: var(--bg-void);
|
||||
color: var(--text-primary);
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-control-panel__bypass-btn--active {
|
||||
background: var(--accent, #f87171);
|
||||
color: var(--bg-void);
|
||||
border-color: var(--accent, #f87171);
|
||||
}
|
||||
|
||||
/* Peq Sliders */
|
||||
.input-panel {
|
||||
position: relative;
|
||||
@@ -260,16 +185,26 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Now the root of a ChoiceSelect rather than a native <select>. */
|
||||
.crossover-panel__select {
|
||||
flex: 0 0 auto;
|
||||
width: 85%;
|
||||
font-family: var(--font-ui);
|
||||
font-size: 3cqh;
|
||||
padding: 1cqh;
|
||||
background: var(--bg-panel);
|
||||
color: inherit;
|
||||
border: var(--border-width) solid var(--border-hairline);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.crossover-panel__select .choice-select__input,
|
||||
.crossover-panel__select .choice-select__input option {
|
||||
font-size: 4cqh;
|
||||
color: var(--accent-brand);
|
||||
/* padding-left: 1cqh; */
|
||||
}
|
||||
|
||||
.crossover-panel__select {
|
||||
font-size: 4cqh;
|
||||
}
|
||||
|
||||
.crossover-panel__select .choice-select__input {
|
||||
font-size: 4cqh;
|
||||
}
|
||||
|
||||
.peq-band-list {
|
||||
@@ -298,7 +233,7 @@
|
||||
min-width: 0;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr 1fr 1fr 1.4fr 1.2fr;
|
||||
grid-template-columns: 1fr 1.2fr 1fr 1fr 1.6fr 1.2fr;
|
||||
align-items: stretch;
|
||||
gap: 1cqh;
|
||||
|
||||
@@ -340,8 +275,7 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.peq-band-list__input,
|
||||
.peq-band-list__select {
|
||||
.peq-band-list__input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
@@ -362,6 +296,17 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.peq-band-list__stepper,
|
||||
.peq-band-list__select {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
font-family: var(--font-ui);
|
||||
font-size: 4cqh;
|
||||
}
|
||||
|
||||
.input-graph-panel__magnitude-wrap {
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
@@ -405,3 +350,15 @@
|
||||
background: var(--accent, #4dd0e1);
|
||||
color: var(--bg-void);
|
||||
}
|
||||
|
||||
.peq-band-list__stepper .choice-stepper__input,
|
||||
.peq-band-list__select .choice-select__input {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
|
||||
font-family: var(--font-mono);
|
||||
font-size: 4cqh;
|
||||
color: var(--accent-brand);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import VerticalSlider, {
|
||||
makeLogStep,
|
||||
makeLogQuantize,
|
||||
} from '../../../../components/VerticalSlider/VerticalSlider';
|
||||
import ChoiceStepper from '../../../../components/ChoiceStepper/ChoiceStepper';
|
||||
import ChoiceSelect from '../../../../components/ChoiceSelect/ChoiceSelect';
|
||||
import {
|
||||
CROSSOVER_FILTER_LABEL,
|
||||
PEQ_BANDS_ORDER,
|
||||
@@ -18,7 +20,6 @@ import { useRef, useState } from 'react';
|
||||
import './InputSection.css';
|
||||
import ChannelGroup from '../../../../components/ChannelGroup/ChannelGroup';
|
||||
import Button from '../../../../components/Button/Button';
|
||||
// import { buildTotalCurveDraw, buildTotalPhaseCurveDraw } from '../../utils/totalCurve';
|
||||
import {
|
||||
FREQUENCY_MAX,
|
||||
FREQUENCY_MIN,
|
||||
@@ -258,17 +259,15 @@ function CrossoverPanel({
|
||||
style={{ width: '100%', flex: '1 1 auto' }}
|
||||
/>
|
||||
|
||||
<select
|
||||
<ChoiceSelect
|
||||
className="crossover-panel__select"
|
||||
value={bypass ? lastFilterType.current : filter_type}
|
||||
onChange={(e) => commit({ filter_type: e.target.value as CrossoverFilter })}
|
||||
>
|
||||
{CROSSOVER_FILTER_LABEL.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={CROSSOVER_FILTER_LABEL.map((opt) => opt.value)}
|
||||
formatLabel={(v) =>
|
||||
CROSSOVER_FILTER_LABEL.find((opt) => opt.value === v)?.label ?? String(v)
|
||||
}
|
||||
onChange={(value) => commit({ filter_type: value })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="toggle"
|
||||
@@ -331,6 +330,9 @@ function PeqBandList({
|
||||
const peq = liveBands[band] ?? bands[band];
|
||||
const isActive = band === activeBand;
|
||||
|
||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||
const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`peq-band-list__row${isActive ? ' peq-band-list__row--active' : ''}`}
|
||||
@@ -338,62 +340,56 @@ function PeqBandList({
|
||||
>
|
||||
<Button
|
||||
variant="toggle"
|
||||
className={isActive ? 'active' : ''}
|
||||
className={isActive ? 'active-info' : ''}
|
||||
onClick={() => setActiveBand(band)}
|
||||
style={{ minHeight: 0 }}
|
||||
style={{
|
||||
minHeight: 0,
|
||||
fontSize: '4cqh',
|
||||
}}
|
||||
>
|
||||
{`B${i + 1}`}
|
||||
</Button>
|
||||
|
||||
{/* Frequency */}
|
||||
<input
|
||||
className="peq-band-list__input"
|
||||
type="text"
|
||||
value={
|
||||
peq.frequency >= 1000 ? (peq.frequency / 1000).toFixed(2) : peq.frequency.toFixed(1)
|
||||
<ChoiceStepper
|
||||
className="peq-band-list__stepper"
|
||||
value={peq.frequency}
|
||||
min={FREQUENCY_MIN}
|
||||
max={FREQUENCY_MAX}
|
||||
step={FREQUENCY_QUANTIZATION}
|
||||
formatLabel={(v) =>
|
||||
v >= 1000 ? `${(v / 1000).toFixed(2)} kHz` : `${v.toFixed(1)} Hz`
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = Number(e.target.value);
|
||||
|
||||
if (!Number.isNaN(value)) {
|
||||
commit(band, {
|
||||
frequency: peq.frequency >= 1000 ? value * 1000 : value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onChange={(value) => commit(band, { frequency: value })}
|
||||
/>
|
||||
|
||||
<input
|
||||
className="peq-band-list__input"
|
||||
type="number"
|
||||
step={1}
|
||||
min={1}
|
||||
max={999}
|
||||
<ChoiceStepper
|
||||
className="peq-band-list__stepper"
|
||||
value={peq.q}
|
||||
onChange={(e) => commit(band, { q: Number(e.target.value) })}
|
||||
values={qValues}
|
||||
formatLabel={(v) => v.toFixed(2)}
|
||||
onChange={(value) => commit(band, { q: value })}
|
||||
/>
|
||||
|
||||
<input
|
||||
className="peq-band-list__input"
|
||||
type="number"
|
||||
step={0.1}
|
||||
<ChoiceStepper
|
||||
className="peq-band-list__stepper"
|
||||
value={peq.gain}
|
||||
min={-12}
|
||||
max={12}
|
||||
value={peq.gain}
|
||||
onChange={(e) => commit(band, { gain: Number(e.target.value) })}
|
||||
step={0.1}
|
||||
formatLabel={(v) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||
onChange={(value) => commit(band, { gain: value })}
|
||||
/>
|
||||
|
||||
<select
|
||||
<ChoiceSelect<PEQFilter>
|
||||
className="peq-band-list__select"
|
||||
value={peq.filter_type}
|
||||
onChange={(e) => commit(band, { filter_type: e.target.value as PEQFilter, q: 3.0 })}
|
||||
>
|
||||
{PEQ_FILTERS_LABEL.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={PEQ_FILTERS_LABEL.map((opt) => opt.value)}
|
||||
formatLabel={(v) =>
|
||||
PEQ_FILTERS_LABEL.find((opt) => opt.value === v)?.label ?? String(v)
|
||||
}
|
||||
onChange={(value) => commit(band, { filter_type: value, q: 3.0 })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="toggle"
|
||||
@@ -416,75 +412,6 @@ type SetFilterFn = (
|
||||
filterType: CrossoverFilter
|
||||
) => Promise<boolean>;
|
||||
|
||||
/** Small DOM-overlay markers positioned on top of the magnitude graph via its exposed toPxX/toPxY. */
|
||||
// function GraphMarkers({
|
||||
// graphRef,
|
||||
// bands,
|
||||
// activeBand,
|
||||
// setActiveBand,
|
||||
// hpFreq,
|
||||
// hpBypassed,
|
||||
// lpFreq,
|
||||
// lpBypassed,
|
||||
// }: {
|
||||
// graphRef: React.RefObject<LinearGraphHandle>;
|
||||
// bands: Record<PEQBand, PEQ>;
|
||||
// activeBand: PEQBand;
|
||||
// setActiveBand: (band: PEQBand) => void;
|
||||
// hpFreq: number;
|
||||
// hpBypassed: boolean;
|
||||
// lpFreq: number;
|
||||
// lpBypassed: boolean;
|
||||
// }) {
|
||||
// const g = graphRef.current;
|
||||
// if (!g) return null;
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {PEQ_BANDS_ORDER.map((band, i) => {
|
||||
// const peq = bands[band];
|
||||
// const left = g.toPxX(peq.frequency);
|
||||
// const top = g.toPxY(peq.gain);
|
||||
// const isActive = band === activeBand;
|
||||
|
||||
// return (
|
||||
// <button
|
||||
// key={band}
|
||||
// type="button"
|
||||
// className={`graph-marker graph-marker--peq${isActive ? ' graph-marker--active' : ''}${
|
||||
// peq.bypass ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left, top }}
|
||||
// onClick={() => setActiveBand(band)}
|
||||
// title={`B${i + 1} · ${Math.round(peq.frequency)} Hz · ${peq.gain.toFixed(1)} dB`}
|
||||
// >
|
||||
// {`B${i + 1}`}
|
||||
// </button>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <div
|
||||
// className={`graph-marker graph-marker--crossover graph-marker--hp${
|
||||
// hpBypassed ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left: g.toPxX(hpFreq), top: g.toPxY(0) }}
|
||||
// title={`HPF · ${Math.round(hpFreq)} Hz`}
|
||||
// >
|
||||
// HP
|
||||
// </div>
|
||||
// <div
|
||||
// className={`graph-marker graph-marker--crossover graph-marker--lp${
|
||||
// lpBypassed ? ' graph-marker--bypassed' : ''
|
||||
// }`}
|
||||
// style={{ left: g.toPxX(lpFreq), top: g.toPxY(0) }}
|
||||
// title={`LPF · ${Math.round(lpFreq)} Hz`}
|
||||
// >
|
||||
// LP
|
||||
// </div>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
function InputSection({
|
||||
dsp,
|
||||
channel,
|
||||
@@ -626,8 +553,8 @@ function InputSection({
|
||||
yMin={GRAPH_Y_MIN}
|
||||
yMax={GRAPH_Y_MAX}
|
||||
yStep={3}
|
||||
xLabel="Hz"
|
||||
yLabel="dB"
|
||||
xLabel="Frequency - Hz"
|
||||
yLabel="Magnituded - dB"
|
||||
showGrid={true}
|
||||
formatX={(v) => Math.round(v).toString()}
|
||||
formatY={(v) => Math.round(v).toString()}
|
||||
@@ -635,7 +562,6 @@ function InputSection({
|
||||
drawCurves({
|
||||
ctx,
|
||||
toPx,
|
||||
|
||||
xMin: GRAPH_X_MIN,
|
||||
xMax: GRAPH_X_MAX,
|
||||
yMin: GRAPH_Y_MIN,
|
||||
@@ -667,8 +593,8 @@ function InputSection({
|
||||
yMin={-180}
|
||||
yMax={180}
|
||||
yStep={45}
|
||||
xLabel="Hz"
|
||||
yLabel="°"
|
||||
xLabel="Frequency - Hz"
|
||||
yLabel="Phase - Deg"
|
||||
showGrid={true}
|
||||
formatX={(v) => Math.round(v).toString()}
|
||||
formatY={(v) => Math.round(v).toString()}
|
||||
@@ -676,7 +602,6 @@ function InputSection({
|
||||
drawCurves({
|
||||
ctx,
|
||||
toPx,
|
||||
|
||||
xMin: GRAPH_X_MIN,
|
||||
xMax: GRAPH_X_MAX,
|
||||
yMin: GRAPH_Y_MIN,
|
||||
|
||||
@@ -127,6 +127,7 @@ export const PEQ_BANDS_ORDER: PEQBand[] = [
|
||||
PEQBand.B6,
|
||||
PEQBand.B7,
|
||||
PEQBand.B8,
|
||||
PEQBand.B9,
|
||||
];
|
||||
|
||||
export const PEQ_Q_RANGES: Record<PEQFilter, readonly [number, number]> = {
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import {
|
||||
Gate,
|
||||
GraphicEQ,
|
||||
DiscreteFrequency,
|
||||
PEQ,
|
||||
PEQFilter,
|
||||
PEQBand,
|
||||
PEQChain,
|
||||
Crossover,
|
||||
CrossoverFilter,
|
||||
CrossoverFilters,
|
||||
InputChannelState,
|
||||
OutputChannelState,
|
||||
InputChannel,
|
||||
DSPState,
|
||||
Meters,
|
||||
Meter,
|
||||
Ratio,
|
||||
OutputChannel,
|
||||
DelayUnit,
|
||||
} from './types';
|
||||
import { FREQUENCY_MIN, FREQUENCY_MAX } from './constants';
|
||||
|
||||
const createDefaultGate = (): Gate => ({
|
||||
attack: 10,
|
||||
release: 10,
|
||||
hold: 10,
|
||||
threshold: 0,
|
||||
});
|
||||
|
||||
const createDefaultGraphicEQ = (): GraphicEQ => ({
|
||||
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,
|
||||
});
|
||||
|
||||
const createDefaultPEQ = (): PEQ => ({
|
||||
gain: 0,
|
||||
frequency: 1000,
|
||||
q: 1,
|
||||
filter_type: PEQFilter.Peak,
|
||||
bypass: false,
|
||||
});
|
||||
|
||||
const createDefaultPEQChain = (): PEQChain => ({
|
||||
bands: Object.fromEntries(
|
||||
Object.values(PEQBand).map((band) => [band, createDefaultPEQ()])
|
||||
) as Record<PEQBand, PEQ>,
|
||||
bypass: false,
|
||||
});
|
||||
|
||||
const createDefaultCrossover = (frequency: number): Crossover => ({
|
||||
frequency: frequency,
|
||||
slope: CrossoverFilter.Bypass,
|
||||
});
|
||||
|
||||
const createDefaultCrossoverFilters = (): CrossoverFilters => ({
|
||||
high_pass: createDefaultCrossover(FREQUENCY_MIN),
|
||||
low_pass: createDefaultCrossover(FREQUENCY_MAX),
|
||||
});
|
||||
|
||||
const createDefaultInputChannelState = (name: string): InputChannelState => ({
|
||||
name,
|
||||
gain: 0,
|
||||
phase_inverted: false,
|
||||
delay: 0,
|
||||
|
||||
gate: createDefaultGate(),
|
||||
geq: createDefaultGraphicEQ(),
|
||||
peq_chain: createDefaultPEQChain(),
|
||||
crossover: createDefaultCrossoverFilters(),
|
||||
|
||||
linked_channels: [],
|
||||
|
||||
mute: false,
|
||||
});
|
||||
|
||||
const createDefaultOutputChannelState = (name: string): OutputChannelState => ({
|
||||
name,
|
||||
|
||||
matrix_routes: {
|
||||
connected: [InputChannel.InA],
|
||||
gains: {
|
||||
[InputChannel.InA]: 0,
|
||||
[InputChannel.InB]: 0,
|
||||
[InputChannel.InC]: 0,
|
||||
[InputChannel.InD]: 0,
|
||||
},
|
||||
},
|
||||
|
||||
crossover: createDefaultCrossoverFilters(),
|
||||
peq_chain: createDefaultPEQChain(),
|
||||
|
||||
compressor: {
|
||||
threshold: -90,
|
||||
ratio: Ratio.Ratio1_1,
|
||||
attack: 1,
|
||||
release: 10,
|
||||
knee: 0,
|
||||
},
|
||||
|
||||
limiter: {
|
||||
threshold: -90,
|
||||
attack: 1,
|
||||
release: 10,
|
||||
},
|
||||
|
||||
gain: 0,
|
||||
phase_inverted: false,
|
||||
delay: 0,
|
||||
|
||||
linked_channels: [],
|
||||
|
||||
mute: false,
|
||||
});
|
||||
|
||||
export const defaultDSPState: DSPState = {
|
||||
name: 'Default',
|
||||
flags: {
|
||||
is_locked: false,
|
||||
},
|
||||
|
||||
presets: {
|
||||
current_index: 0,
|
||||
names: Array(20).fill('Default'),
|
||||
modified: Array(20).fill(false),
|
||||
},
|
||||
|
||||
current_config: {
|
||||
input_source: {
|
||||
type: 'Analog',
|
||||
frequency: '48kHz',
|
||||
},
|
||||
|
||||
delay_unit: DelayUnit.Millisecond,
|
||||
|
||||
input_states: {
|
||||
[InputChannel.InA]: createDefaultInputChannelState('InInInIn'),
|
||||
[InputChannel.InB]: createDefaultInputChannelState('In B'),
|
||||
[InputChannel.InC]: createDefaultInputChannelState('In C'),
|
||||
[InputChannel.InD]: createDefaultInputChannelState('In D'),
|
||||
},
|
||||
|
||||
output_states: {
|
||||
[OutputChannel.Out1]: createDefaultOutputChannelState('Out 1'),
|
||||
[OutputChannel.Out2]: createDefaultOutputChannelState('Out 2'),
|
||||
[OutputChannel.Out3]: createDefaultOutputChannelState('Out 3'),
|
||||
[OutputChannel.Out4]: createDefaultOutputChannelState('Out 4'),
|
||||
[OutputChannel.Out5]: createDefaultOutputChannelState('Out 5'),
|
||||
[OutputChannel.Out6]: createDefaultOutputChannelState('Out 6'),
|
||||
[OutputChannel.Out7]: createDefaultOutputChannelState('Out 7'),
|
||||
[OutputChannel.Out8]: createDefaultOutputChannelState('Out 8'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const createDefaultMeter = (): Meter => ({
|
||||
rms: -60,
|
||||
peak: -60,
|
||||
clip: false,
|
||||
});
|
||||
|
||||
export const defaultMeters: Meters = {
|
||||
channels: Array.from({ length: 12 }, createDefaultMeter),
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -238,6 +238,14 @@ export type DSPState = {
|
||||
current_config: DSPConfigState;
|
||||
};
|
||||
|
||||
export type DSPMode = 'demo' | 'disconnected' | 'connected';
|
||||
|
||||
export type DSP408Snapshot = {
|
||||
state: DSPState;
|
||||
meters: Meters;
|
||||
mode: DSPMode;
|
||||
};
|
||||
|
||||
export interface Meter {
|
||||
rms: number;
|
||||
peak: number;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { DSP, DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types';
|
||||
import { DSP_DEFAULT_PARAMETERS, DSPTypes } from '../../types';
|
||||
import Button from '../../components/Button/Button';
|
||||
import Card from '../../components/Card/Card';
|
||||
import ChoiceStepper from '../../components/ChoiceStepper/ChoiceStepper';
|
||||
@@ -37,7 +37,7 @@ function AddDSPForm({
|
||||
onAdd,
|
||||
}: {
|
||||
onCancel: () => void;
|
||||
onAdd: (dsp: Omit<DSP, 'status' | 'id' | 'state' | 'meters' | 'pollingPaused'>) => void;
|
||||
onAdd: (params: DSPForm) => void;
|
||||
}) {
|
||||
const [form, setForm] = useState<DSPForm>({
|
||||
name: '',
|
||||
@@ -78,21 +78,19 @@ function AddDSPForm({
|
||||
...prev,
|
||||
ip: lastValidIp.current,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!canSubmit) return;
|
||||
|
||||
onAdd({ ...form });
|
||||
onAdd(form);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="add-dsp-form">
|
||||
<div className="add-dsp-form__header">
|
||||
<h1 className="add-dsp-form__title">Add DSP</h1>
|
||||
<p className="add-dsp-form__hint">
|
||||
Register a new device to control from this app.
|
||||
</p>
|
||||
<p className="add-dsp-form__hint">Register a new device to control from this app.</p>
|
||||
</div>
|
||||
|
||||
<div className="add-dsp-form__field">
|
||||
@@ -109,6 +107,7 @@ function AddDSPForm({
|
||||
<div className="add-dsp-form__field">
|
||||
<label htmlFor="f-type">Model</label>
|
||||
<ChoiceSelect
|
||||
className=""
|
||||
id="f-type"
|
||||
value={form.type}
|
||||
options={DSP_TYPES}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { DSPState as DSP408State, Meters as DSP408Meters } from './features/dsp408/types';
|
||||
import { DSPState as DSP408State, Meters as DSP408Meters, DSPMode } from './features/dsp408/types';
|
||||
|
||||
export type DSPStatus = 'connected' | 'connecting' | 'disconnected';
|
||||
|
||||
@@ -31,7 +31,7 @@ interface DSPBase {
|
||||
ip: string;
|
||||
port: number;
|
||||
deviceId: number;
|
||||
demo: boolean;
|
||||
mode: DSPMode;
|
||||
|
||||
status: DSPStatus;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user