diff --git a/client/src/lib/luxsinApi.ts b/client/src/lib/luxsinApi.ts
index ca30d03..0068a75 100644
--- a/client/src/lib/luxsinApi.ts
+++ b/client/src/lib/luxsinApi.ts
@@ -111,6 +111,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;
@@ -539,6 +543,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 16ec82f..fd8519b 100644
--- a/client/src/locales/data-de.json
+++ b/client/src/locales/data-de.json
@@ -171,7 +171,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 d174c80..11e2dda 100644
--- a/client/src/locales/data-en.json
+++ b/client/src/locales/data-en.json
@@ -171,7 +171,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 054fb5b..1a41697 100644
--- a/client/src/locales/data-es.json
+++ b/client/src/locales/data-es.json
@@ -171,7 +171,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 16ef691..0076185 100644
--- a/client/src/locales/data-fr.json
+++ b/client/src/locales/data-fr.json
@@ -171,7 +171,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 6f25527..94c42df 100644
--- a/client/src/locales/data-zh-HK.json
+++ b/client/src/locales/data-zh-HK.json
@@ -171,7 +171,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 b3cd0a5..dfa3f40 100644
--- a/client/src/locales/data-zh.json
+++ b/client/src/locales/data-zh.json
@@ -171,7 +171,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 c249192..f91ebe6 100644
--- a/client/src/pages/EffectsPage.tsx
+++ b/client/src/pages/EffectsPage.tsx
@@ -184,6 +184,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 ?? "—";
@@ -304,6 +318,69 @@ export default function EffectsPage() {
{crossfeedDisplay}