diff --git a/client/src/lib/luxsinApi.ts b/client/src/lib/luxsinApi.ts
index 55dd541..1d94d9d 100644
--- a/client/src/lib/luxsinApi.ts
+++ b/client/src/lib/luxsinApi.ts
@@ -105,6 +105,10 @@ export interface DeviceState {
scene_value: number;
crossfeed_enable: number;
crossfeed_value: number;
+ /** Custom crossfeed cut-off frequency in Hz (500–1000, step 1). */
+ crossfeed_custom_fc: number;
+ /** Custom crossfeed attenuation, stored as dB×10 (1.0 dB=10 … 15.0 dB=150). */
+ crossfeed_custom_gain: number;
subwoofer_enable: number;
subwoofer_value: number;
subwoofer_rate: number;
@@ -573,6 +577,8 @@ export const MOCK_DEVICE_STATE: DeviceState = {
scene_value: 0,
crossfeed_enable: 0,
crossfeed_value: 0,
+ crossfeed_custom_fc: 700,
+ crossfeed_custom_gain: 45,
subwoofer_enable: 0,
subwoofer_value: 80,
subwoofer_rate: 0,
diff --git a/client/src/locales/data-de.json b/client/src/locales/data-de.json
index 152bba2..bfc070c 100644
--- a/client/src/locales/data-de.json
+++ b/client/src/locales/data-de.json
@@ -111,7 +111,10 @@
"BS2B Populär (700 Hz, 6 dB)",
"BS2B Entspannt (650 Hz, 9,5 dB)",
"Custom"
- ]
+ ],
+ "customTitle": "Benutzerdefinierte Einstellungen",
+ "freqLabel": "Frequence",
+ "attenuationLabel": "Attenuation"
},
"hearing": {
"label": "Hörkompensation",
diff --git a/client/src/locales/data-en.json b/client/src/locales/data-en.json
index a414693..e2208a9 100644
--- a/client/src/locales/data-en.json
+++ b/client/src/locales/data-en.json
@@ -111,7 +111,10 @@
"BS2B popular (700 Hz, 6 dB)",
"BS2B relax (650 Hz, 9.5 dB)",
"Custom"
- ]
+ ],
+ "customTitle": "Custom Settings",
+ "freqLabel": "Frequence",
+ "attenuationLabel": "Attenuation"
},
"hearing": {
"label": "Hearing compensation",
diff --git a/client/src/locales/data-es.json b/client/src/locales/data-es.json
index 2f1f2b6..db97a05 100644
--- a/client/src/locales/data-es.json
+++ b/client/src/locales/data-es.json
@@ -111,7 +111,10 @@
"BS2B popular (700 Hz, 6 dB)",
"BS2B relajado (650 Hz, 9,5 dB)",
"Custom"
- ]
+ ],
+ "customTitle": "Configuración personalizada",
+ "freqLabel": "Frequence",
+ "attenuationLabel": "Attenuation"
},
"hearing": {
"label": "Compensación auditiva",
diff --git a/client/src/locales/data-fr.json b/client/src/locales/data-fr.json
index 3a3bcd5..438edf2 100644
--- a/client/src/locales/data-fr.json
+++ b/client/src/locales/data-fr.json
@@ -111,7 +111,10 @@
"BS2B populaire (700 Hz, 6 dB)",
"BS2B relax (650 Hz, 9,5 dB)",
"Custom"
- ]
+ ],
+ "customTitle": "Réglages personnalisés",
+ "freqLabel": "Frequence",
+ "attenuationLabel": "Attenuation"
},
"hearing": {
"label": "Compensation auditive",
diff --git a/client/src/locales/data-zh-HK.json b/client/src/locales/data-zh-HK.json
index 0c97204..9696d0d 100644
--- a/client/src/locales/data-zh-HK.json
+++ b/client/src/locales/data-zh-HK.json
@@ -104,7 +104,10 @@
"BS2B polular(700Hz,6dB)",
"BS2B relax(650Hz,9.5dB)",
"Custom"
- ]
+ ],
+ "customTitle": "自訂設置",
+ "freqLabel": "截止頻率",
+ "attenuationLabel": "衰減量"
},
"hearing": {
"label": "聽力補償",
diff --git a/client/src/locales/data-zh.json b/client/src/locales/data-zh.json
index cbcfb7f..4b4d97f 100644
--- a/client/src/locales/data-zh.json
+++ b/client/src/locales/data-zh.json
@@ -104,7 +104,10 @@
"BS2B polular(700Hz,6dB)",
"BS2B relax(650Hz,9.5dB)",
"Custom"
- ]
+ ],
+ "customTitle": "自定义设置",
+ "freqLabel": "截止频率",
+ "attenuationLabel": "衰减量"
},
"hearing": {
"label": "听力补偿",
diff --git a/client/src/pages/EffectsPage.tsx b/client/src/pages/EffectsPage.tsx
index eddd9e3..7c0a05a 100644
--- a/client/src/pages/EffectsPage.tsx
+++ b/client/src/pages/EffectsPage.tsx
@@ -408,6 +408,20 @@ export default function EffectsPage() {
const sceneDisplay = sceneLabels[sceneIdx] ?? "";
const crossIdx = clampPickIndex(crossfeedVal, crossfeedLabels.length);
const crossfeedDisplay = crossfeedLabels[crossIdx] ?? "";
+ // Custom crossfeed values: fc in Hz, gain stored as dB×10
+ const crossfeedCustomFc = ds?.crossfeed_custom_fc ?? 700;
+ const crossfeedCustomGain = (ds?.crossfeed_custom_gain ?? 45) / 10;
+
+ const [localCrossfeedFc, setLocalCrossfeedFc] = useState(() => crossfeedCustomFc);
+ const [localCrossfeedGain, setLocalCrossfeedGain] = useState(() => crossfeedCustomGain);
+ const isDraggingCrossfeedFc = useRef(false);
+ const isDraggingCrossfeedGain = useRef(false);
+
+ useEffect(() => {
+ if (!isDraggingCrossfeedFc.current) setLocalCrossfeedFc(crossfeedCustomFc);
+ if (!isDraggingCrossfeedGain.current) setLocalCrossfeedGain(crossfeedCustomGain);
+ }, [crossfeedCustomFc, crossfeedCustomGain]);
+
const hearingIdx = clampPickIndex(hearingSelect, hearingLabels.length);
const hearingDisplay =
hearingLabels[hearingIdx] ?? effectText.hearing?.emptyProfile ?? "—";
@@ -527,6 +541,69 @@ export default function EffectsPage() {
{crossfeedDisplay}