新增了耳机音量保护和模拟输入的各项设置

This commit is contained in:
eafonyang
2026-10-22 18:40:18 +08:00
parent c5cdbb4a64
commit 81f5635dd6
15 changed files with 650 additions and 38 deletions
+8 -3
View File
@@ -1,6 +1,7 @@
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import {
DeviceState,
getHomeVolumeMax,
LuxsinAPI,
MOCK_DEVICE_STATE,
MOCK_PEQ_STATE,
@@ -262,12 +263,16 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
}, [api, isDemoMode, applyPeqFiltersToState]);
// Optimistic setters
const maxVolume = getHomeVolumeMax(deviceState);
const setVolume = useCallback((v: number) => {
setDeviceState(prev => prev ? { ...prev, volume: v } : prev);
if (!Number.isFinite(v)) return;
// 指令和乐观状态使用同一上限,防止任何首页入口发送超限音量。
const volume = Math.max(0, Math.min(maxVolume, Math.round(v)));
setDeviceState(prev => prev ? { ...prev, volume } : prev);
if (!isDemoMode && api) {
api.setVolume(v).catch(console.error);
api.setVolume(volume).catch(console.error);
}
}, [api, isDemoMode]);
}, [api, isDemoMode, maxVolume]);
const setInput = useCallback((v: number) => {
setDeviceState(prev => prev ? { ...prev, input: v } : prev);
+41
View File
@@ -21,6 +21,32 @@ export function volumeToDb(vol: number): number {
return (vol - 200) / 2;
}
/** 耳机输出(协议索引 2)使用设置上限;其他输出及未设置时保持 0dB。 */
export function getHomeVolumeMax(
state: Pick<DeviceState, "output" | "hpMaxVolume"> | null | undefined,
): number {
if (state?.output !== 2) return 200;
const rawIndex = Number(state.hpMaxVolume ?? 0);
const index = Number.isFinite(rawIndex)
? Math.max(0, Math.min(10, Math.trunc(rawIndex)))
: 0;
// 每档降低 5dB,即 10 个设备音量单位。
return dbToVolume(-5 * index);
}
/**
* 耳机输出(协议索引 2)且用户设置了耳机最大音量(低于 0dB)时,返回该上限的 dB 值(负数);
* 其他输出或未设置(0 档)返回 null。
*/
export function getHeadphoneMaxVolumeDb(
state: Pick<DeviceState, "output" | "hpMaxVolume"> | null | undefined,
): number | null {
if (state?.output !== 2) return null;
const rawIndex = Number(state.hpMaxVolume ?? 0);
if (!Number.isFinite(rawIndex) || Math.trunc(rawIndex) <= 0) return null;
return volumeToDb(getHomeVolumeMax(state));
}
export function decodeCustomBase64(encoded: string): string {
let translated = "";
for (let i = 0; i < encoded.length; i++) {
@@ -95,8 +121,18 @@ export interface DeviceState {
dacGain: number;
dacArc: number;
dacImpedance: number;
/** Headphone max volume: 0 = 0dB, 1 = -5dB … 10 = -50dB (5dB steps). */
hpMaxVolume: number;
/** Headphone startup volume: 0 = default, 1 = -20dB … 7 = -50dB (5dB steps). */
hpStartupVolume: number;
/** Headphone amp working mode: 0 = Class AB, 1 = Class A. */
hpAmpMode: number;
dacVolumeDirect: number;
analogGain: number;
/** Analog input port: 0 = balanced, 1 = unbalanced. */
analog_input: number;
/** Analog input bypass: 0 = off, 1 = on. */
analog_bypass: number;
effect_enable: number;
effect_value: number;
width_enable: number;
@@ -582,8 +618,13 @@ export const MOCK_DEVICE_STATE: DeviceState = {
dacGain: 0,
dacArc: 0,
dacImpedance: 0,
hpMaxVolume: 0,
hpStartupVolume: 0,
hpAmpMode: 0,
dacVolumeDirect: 0,
analogGain: 0,
analog_input: 0,
analog_bypass: 0,
effect_enable: 0,
effect_value: 50,
width_enable: 0,
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Geben Sie das Gerät in der URL an, z.B. ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "Das maximale Kopfhörervolumen ist eingestellt. Maximum: {db}dB",
"audioSet": "Audioeinstellung",
"systemSet": "System",
"power": "Stromversorgung",
@@ -387,13 +388,59 @@
}
},
"dac": {
"sectionHpSettings": "Kopfhörer-Einstellungen",
"hpMaxVolume": {
"label": "Max. Kopfhörerlautstärke",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Kopfhörer-Startlautstärke",
"options": [
{ "index": 0, "label": "Standard" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Kopfhörerverstärker-Modus",
"options": [
{ "index": 0, "label": "Class AB" },
{ "index": 1, "label": "Class A" }
]
},
"sectionBalanceSensitivity": "Balance & Empfindlichkeit",
"sectionAnalogInput": "Analogeingang",
"sectionOutput": "Ausgangsmerkmale",
"sectionIisSettings": "IIS-Einstellungen",
"sectionSystemInterface": "System & Schnittstelle",
"balance": "Links-Rechts-Balance",
"sensitivity": "VU-Meter Empfindlichkeit",
"analogInputGain": "Analoge Eingangsverstärkung",
"analogInput": {
"label": "Eingang",
"options": [
{ "index": 0, "label": "Symmetrisch" },
{ "index": 1, "label": "Asymmetrisch" }
]
},
"analogBypass": "Analogeingang-Bypass",
"autoImpedanceDetection": "Automatische Impedanz-Erkennung",
"dacVolumeDirect": {
"label": "Vorverstärker-Lautstärke-Durchleitung",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Specify the device in the URL, e.g. ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "Headphone max volume is set. Maximum: {db}dB",
"audioSet": "Audio setting",
"systemSet": "System",
"power": "Power",
@@ -387,13 +388,59 @@
}
},
"dac": {
"sectionHpSettings": "Headphone settings",
"hpMaxVolume": {
"label": "Max headphone volume",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Headphone startup volume",
"options": [
{ "index": 0, "label": "Default" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "HP amp mode",
"options": [
{ "index": 0, "label": "Class AB" },
{ "index": 1, "label": "Class A" }
]
},
"sectionBalanceSensitivity": "Balance & sensitivity",
"sectionAnalogInput": "Analog input",
"sectionOutput": "Output characteristics",
"sectionIisSettings": "IIS settings",
"sectionSystemInterface": "System & interface",
"balance": "Left-right balance",
"sensitivity": "VU Meter Sensitivity",
"analogInputGain": "Analog input gain",
"analogInput": {
"label": "Input port",
"options": [
{ "index": 0, "label": "Balanced" },
{ "index": 1, "label": "Unbalanced" }
]
},
"analogBypass": "Analog input bypass",
"autoImpedanceDetection": "Auto Impedance Detection",
"dacVolumeDirect": {
"label": "Pre-out Volume passthrough",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Especifique el dispositivo en la URL, ej. ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "El volumen máximo de los auriculares está configurado. Máximo: {db}dB",
"audioSet": "Configuración de audio",
"systemSet": "Sistema",
"power": "Alimentación",
@@ -387,13 +388,59 @@
}
},
"dac": {
"sectionHpSettings": "Ajustes de auriculares",
"hpMaxVolume": {
"label": "Volumen máx. de auriculares",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Volumen de inicio de auriculares",
"options": [
{ "index": 0, "label": "Predeterminado" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Modo del amplificador de auriculares",
"options": [
{ "index": 0, "label": "Clase AB" },
{ "index": 1, "label": "Clase A" }
]
},
"sectionBalanceSensitivity": "Balance y sensibilidad",
"sectionAnalogInput": "Entrada analógica",
"sectionOutput": "Características de salida",
"sectionIisSettings": "Configuración IIS",
"sectionSystemInterface": "Sistema e interfaz",
"balance": "Balance izquierda-derecha",
"sensitivity": "Sensibilidad del VU-metro",
"analogInputGain": "Ganancia de entrada analógica",
"analogInput": {
"label": "Puerto de entrada",
"options": [
{ "index": 0, "label": "Balanceada" },
{ "index": 1, "label": "No balanceada" }
]
},
"analogBypass": "Bypass de entrada analógica",
"autoImpedanceDetection": "Detección automática de impedancia",
"dacVolumeDirect": {
"label": "Paso de volumen de pre-salida",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Spécifiez l'appareil dans l'URL, par ex. ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "Le volume maximal du casque est défini. Maximum : {db}dB",
"audioSet": "Réglage audio",
"systemSet": "Système",
"power": "Alimentation",
@@ -387,13 +388,59 @@
}
},
"dac": {
"sectionHpSettings": "Réglages du casque",
"hpMaxVolume": {
"label": "Volume max. du casque",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Volume de démarrage du casque",
"options": [
{ "index": 0, "label": "Par défaut" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Mode de l'ampli casque",
"options": [
{ "index": 0, "label": "Classe AB" },
{ "index": 1, "label": "Classe A" }
]
},
"sectionBalanceSensitivity": "Balance et sensibilité",
"sectionAnalogInput": "Entrée analogique",
"sectionOutput": "Caractéristiques de sortie",
"sectionIisSettings": "Paramètres IIS",
"sectionSystemInterface": "Système et interface",
"balance": "Balance gauche-droite",
"sensitivity": "Sensibilité VU-mètre",
"analogInputGain": "Gain d'entrée analogique",
"analogInput": {
"label": "Port d'entrée",
"options": [
{ "index": 0, "label": "Symétrique" },
{ "index": 1, "label": "Asymétrique" }
]
},
"analogBypass": "Bypass d'entrée analogique",
"autoImpedanceDetection": "Détection automatique d'impédance",
"dacVolumeDirect": {
"label": "Passage de volume pré-sortie",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "URL에 장치를 지정하세요. 예: ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "헤드폰 최대 볼륨이 설정되었습니다. 최댓값: {db}dB",
"audioSet": "오디오 설정",
"systemSet": "시스템",
"power": "전원",
@@ -457,13 +458,59 @@
}
},
"dac": {
"sectionHpSettings": "헤드폰 설정",
"hpMaxVolume": {
"label": "최대 헤드폰 볼륨",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "헤드폰 시작 볼륨",
"options": [
{ "index": 0, "label": "기본" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "헤드폰 앰프 모드",
"options": [
{ "index": 0, "label": "클래스 AB" },
{ "index": 1, "label": "클래스 A" }
]
},
"sectionBalanceSensitivity": "밸런스 및 감도",
"sectionAnalogInput": "아날로그 입력",
"sectionOutput": "출력 특성",
"sectionIisSettings": "IIS 설정",
"sectionSystemInterface": "시스템 및 인터페이스",
"balance": "좌우 밸런스",
"sensitivity": "VU 미터 감도",
"analogInputGain": "아날로그 입력 게인",
"analogInput": {
"label": "입력 포트",
"options": [
{ "index": 0, "label": "밸런스" },
{ "index": 1, "label": "언밸런스" }
]
},
"analogBypass": "아날로그 입력 바이패스",
"autoImpedanceDetection": "자동 임피던스 감지",
"dacVolumeDirect": {
"label": "프리아웃 볼륨 패스스루",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Określ urządzenie w adresie URL, np. ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "Ustawiono maksymalną głośność słuchawek. Maksimum: {db}dB",
"audioSet": "Ustawienia dźwięku",
"systemSet": "System",
"power": "Zasilanie",
@@ -457,13 +458,59 @@
}
},
"dac": {
"sectionHpSettings": "Ustawienia słuchawek",
"hpMaxVolume": {
"label": "Maksymalna głośność słuchawek",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Głośność startowa słuchawek",
"options": [
{ "index": 0, "label": "Domyślna" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Tryb wzmacniacza słuchawek",
"options": [
{ "index": 0, "label": "Klasa AB" },
{ "index": 1, "label": "Klasa A" }
]
},
"sectionBalanceSensitivity": "Równowaga i czułość",
"sectionAnalogInput": "Wejście analogowe",
"sectionOutput": "Charakterystyka wyjścia",
"sectionIisSettings": "Ustawienia IIS",
"sectionSystemInterface": "System i interfejs",
"balance": "Równowaga lewo-prawo",
"sensitivity": "Czułość miernika VU",
"analogInputGain": "Wzmocnienie wejścia analogowego",
"analogInput": {
"label": "Port wejściowy",
"options": [
{ "index": 0, "label": "Zbalansowane" },
{ "index": 1, "label": "Niezbalansowane" }
]
},
"analogBypass": "Bypass wejścia analogowego",
"autoImpedanceDetection": "Automatyczne wykrywanie impedancji",
"dacVolumeDirect": {
"label": "Przekazywanie głośności wyjścia przedwzmacniacza",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Especifique o dispositivo no URL, por exemplo ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "O volume máximo dos auscultadores está definido. Máximo: {db}dB",
"audioSet": "Definições de áudio",
"systemSet": "Sistema",
"power": "Energia",
@@ -457,13 +458,59 @@
}
},
"dac": {
"sectionHpSettings": "Definições de auscultadores",
"hpMaxVolume": {
"label": "Volume máximo dos auscultadores",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Volume de arranque dos auscultadores",
"options": [
{ "index": 0, "label": "Predefinido" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Modo do amplificador dos auscultadores",
"options": [
{ "index": 0, "label": "Classe AB" },
{ "index": 1, "label": "Classe A" }
]
},
"sectionBalanceSensitivity": "Equilíbrio e sensibilidade",
"sectionAnalogInput": "Entrada analógica",
"sectionOutput": "Características de saída",
"sectionIisSettings": "Definições IIS",
"sectionSystemInterface": "Sistema e interface",
"balance": "Equilíbrio esquerda-direita",
"sensitivity": "Sensibilidade do medidor VU",
"analogInputGain": "Ganho de entrada analógica",
"analogInput": {
"label": "Porta de entrada",
"options": [
{ "index": 0, "label": "Balanceada" },
{ "index": 1, "label": "Não balanceada" }
]
},
"analogBypass": "Bypass de entrada analógica",
"autoImpedanceDetection": "Deteção automática de impedância",
"dacVolumeDirect": {
"label": "Bypass de volume de pré-saída",
+47
View File
@@ -8,6 +8,7 @@
"failedHint": "Hãy chỉ định thiết bị trong URL, ví dụ ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "Đã đặt âm lượng tai nghe tối đa. Tối đa: {db}dB",
"audioSet": "Cài đặt âm thanh",
"systemSet": "Hệ thống",
"power": "Nguồn",
@@ -457,13 +458,59 @@
}
},
"dac": {
"sectionHpSettings": "Cài đặt tai nghe",
"hpMaxVolume": {
"label": "Âm lượng tai nghe tối đa",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "Âm lượng khởi động tai nghe",
"options": [
{ "index": 0, "label": "Mặc định" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "Chế độ ampli tai nghe",
"options": [
{ "index": 0, "label": "Class AB" },
{ "index": 1, "label": "Class A" }
]
},
"sectionBalanceSensitivity": "Cân bằng & độ nhạy",
"sectionAnalogInput": "Đầu vào analog",
"sectionOutput": "Đặc tính đầu ra",
"sectionIisSettings": "Cài đặt IIS",
"sectionSystemInterface": "Hệ thống & giao diện",
"balance": "Cân bằng trái-phải",
"sensitivity": "Độ nhạy đồng hồ VU",
"analogInputGain": "Độ lợi đầu vào analog",
"analogInput": {
"label": "Cổng đầu vào",
"options": [
{ "index": 0, "label": "Cân bằng" },
{ "index": 1, "label": "Không cân bằng" }
]
},
"analogBypass": "Bypass đầu vào analog",
"autoImpedanceDetection": "Tự động phát hiện trở kháng",
"dacVolumeDirect": {
"label": "Passthrough âm lượng Pre-out",
+49 -2
View File
@@ -8,6 +8,7 @@
"failedHint": "透過網址列指定裝置,例如 ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "已設定耳機最大音量,最大音量為:{db}dB",
"audioSet": "音訊設定",
"systemSet": "系統設定",
"power": "電源",
@@ -56,9 +57,9 @@
"pageTitle": "輸入/輸出",
"inputSection": "輸入",
"outputSection": "輸出",
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "IIS", "類比RCA", "本地輸入"],
"inputOptions": ["USB-B", "USB-C", "同軸", "光纖", "藍牙", "IIS", "模擬", "本地輸入"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳機"],
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "類比RCA" },
"input": { "coaxial": "同軸", "optical": "光纖", "bluetooth": "藍牙", "rca": "模擬" },
"output": { "headset": "耳機" }
},
"effect": {
@@ -381,13 +382,59 @@
}
},
"dac": {
"sectionHpSettings": "耳機設定",
"hpMaxVolume": {
"label": "耳機最大音量",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "耳機啟動音量",
"options": [
{ "index": 0, "label": "預設" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "耳放工作模式",
"options": [
{ "index": 0, "label": "甲乙類" },
{ "index": 1, "label": "甲類" }
]
},
"sectionBalanceSensitivity": "平衡與靈敏度",
"sectionAnalogInput": "模擬輸入",
"sectionOutput": "輸出特性",
"sectionIisSettings": "IIS設定",
"sectionSystemInterface": "系統與介面",
"balance": "左右平衡",
"sensitivity": "VU表靈敏度",
"analogInputGain": "模擬輸入增益",
"analogInput": {
"label": "輸入口",
"options": [
{ "index": 0, "label": "平衡" },
{ "index": 1, "label": "非平衡" }
]
},
"analogBypass": "模擬輸入旁路",
"autoImpedanceDetection": "自動檢測耳機阻抗",
"dacVolumeDirect": {
"label": "前級輸出音量直通模式",
+49 -2
View File
@@ -8,6 +8,7 @@
"failedHint": "通过地址栏指定设备,例如 ?ip={ip}"
},
"home": {
"hpMaxVolumeHint": "已设置耳机最大音量,最大音量为:{db}dB",
"audioSet": "音频设置",
"systemSet": "系统设置",
"power": "电源",
@@ -56,9 +57,9 @@
"pageTitle": "输入输出",
"inputSection": "输入",
"outputSection": "输出",
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "模拟RCA", "本地输入"],
"inputOptions": ["USB-B", "USB-C", "同轴", "光纤", "蓝牙", "IIS", "模拟", "本地输入"],
"outputOptions": ["XLR", "RCA", "XLR/RCA", "耳机"],
"input": { "coaxial": "同轴", "optical": "光纤", "bluetooth": "蓝牙", "rca": "模拟RCA" },
"input": { "coaxial": "同轴", "optical": "光纤", "bluetooth": "蓝牙", "rca": "模拟" },
"output": { "headset": "耳机" }
},
"effect": {
@@ -381,13 +382,59 @@
}
},
"dac": {
"sectionHpSettings": "耳机设定",
"hpMaxVolume": {
"label": "耳机最大音量",
"options": [
{ "index": 0, "label": "0dB" },
{ "index": 1, "label": "-5dB" },
{ "index": 2, "label": "-10dB" },
{ "index": 3, "label": "-15dB" },
{ "index": 4, "label": "-20dB" },
{ "index": 5, "label": "-25dB" },
{ "index": 6, "label": "-30dB" },
{ "index": 7, "label": "-35dB" },
{ "index": 8, "label": "-40dB" },
{ "index": 9, "label": "-45dB" },
{ "index": 10, "label": "-50dB" }
]
},
"hpStartupVolume": {
"label": "耳机启动音量",
"options": [
{ "index": 0, "label": "默认" },
{ "index": 1, "label": "-20dB" },
{ "index": 2, "label": "-25dB" },
{ "index": 3, "label": "-30dB" },
{ "index": 4, "label": "-35dB" },
{ "index": 5, "label": "-40dB" },
{ "index": 6, "label": "-45dB" },
{ "index": 7, "label": "-50dB" }
]
},
"hpAmpMode": {
"label": "耳放工作模式",
"options": [
{ "index": 0, "label": "甲乙类" },
{ "index": 1, "label": "甲类" }
]
},
"sectionBalanceSensitivity": "平衡与灵敏度",
"sectionAnalogInput": "模拟输入",
"sectionOutput": "输出特性",
"sectionIisSettings": "IIS设置",
"sectionSystemInterface": "系统与接口",
"balance": "左右平衡",
"sensitivity": "vu表灵敏度",
"analogInputGain": "模拟输入增益",
"analogInput": {
"label": "输入口",
"options": [
{ "index": 0, "label": "平衡" },
{ "index": 1, "label": "非平衡" }
]
},
"analogBypass": "模拟输入旁路",
"autoImpedanceDetection": "自动检测耳机阻抗",
"dacVolumeDirect": {
"label": "前级输出音量直通模式",
+78 -15
View File
@@ -2,8 +2,10 @@
AUDIO PAGE — Audio Settings
Design: Reference luxsin_x8_音频设置.png
Sections:
- 平衡与灵敏度: 左右平衡, VU灵敏度, 模拟输入增益 sliders
- 输出特性: 滤波特性, 耳机增益, 音量幅度
- 平衡与灵敏度: 左右平衡, VU灵敏度 sliders
- 模拟输入: 输入口, 模拟输入旁路, 模拟输入增益
- 输出特性: 滤波特性, 前级输出音量直通模式, ARC模式, 音量幅度
- 耳机设定: 耳机增益, 自动检测耳机阻抗, 耳机最大音量, 耳机启动音量, 耳放工作模式
- IIS设置: IIS静音电平, IIS模式
- 系统与接口: 开机音量, XLR极性
All list rows → navigate to /select page
@@ -110,15 +112,22 @@ function ListRow({ label, value, onClick, toggle, checked, onToggle }: {
type LocaleDac = {
dac?: {
sectionBalanceSensitivity?: string;
sectionAnalogInput?: string;
sectionOutput?: string;
sectionHpSettings?: string;
sectionIisSettings?: string;
sectionSystemInterface?: string;
balance?: string;
sensitivity?: string;
analogInputGain?: string;
analogInput?: { label?: string; options?: Array<{ index: number; label: string }> };
analogBypass?: string;
autoImpedanceDetection?: string;
filters?: { label?: string; options?: Array<{ index: number; label: string }> };
dacGain?: { label?: string; options?: Array<{ index: number; label: string }> };
hpMaxVolume?: { label?: string; options?: Array<{ index: number; label: string }> };
hpStartupVolume?: { label?: string; options?: Array<{ index: number; label: string }> };
hpAmpMode?: { label?: string; options?: Array<{ index: number; label: string }> };
dacVolumeDirect?: { label?: string; options?: Array<{ index: number; label: string }> };
dacArc?: { label?: string; options?: Array<{ index: number; label: string }> };
volumeStep?: string;
@@ -225,7 +234,12 @@ export default function AudioPage() {
const gainIdx = ds?.dacGain ?? 0;
const autoImpedanceOn = (ds?.dacImpedance ?? 0) === 1;
const hpMaxVolumeIdx = Math.min(Math.max(ds?.hpMaxVolume ?? 0, 0), 10);
const hpStartupVolumeIdx = Math.min(Math.max(ds?.hpStartupVolume ?? 0, 0), 7);
const hpAmpModeIdx = Math.min(Math.max(ds?.hpAmpMode ?? 0, 0), 1);
const dacVolumeDirectIdx = ds?.dacVolumeDirect ?? 0;
const analogInputIdx = Math.min(Math.max(ds?.analog_input ?? 0, 0), 1);
const analogBypassOn = (ds?.analog_bypass ?? 0) === 1;
const dacArcIdx = ds?.dacArc ?? 0;
const stepIdx = ds?.soundStep ?? 1;
const filterIdx = ds?.pcm ?? 0;
@@ -241,6 +255,14 @@ export default function AudioPage() {
const FILTER_OPTIONS = optionLabels(dacText.filters?.options, ["快速滚降", "慢速滚降", "短延迟快速滚降", "短延迟慢速滚降", "超慢速滚降", "低离散短延迟"]);
const GAIN_OPTIONS = optionLabels(dacText.dacGain?.options, ["低", "中", "高"]);
const HP_MAX_VOLUME_OPTIONS = optionLabels(dacText.hpMaxVolume?.options, [
"0dB", "-5dB", "-10dB", "-15dB", "-20dB", "-25dB", "-30dB", "-35dB", "-40dB", "-45dB", "-50dB",
]);
const HP_STARTUP_VOLUME_OPTIONS = optionLabels(dacText.hpStartupVolume?.options, [
"默认", "-20dB", "-25dB", "-30dB", "-35dB", "-40dB", "-45dB", "-50dB",
]);
const HP_AMP_MODE_OPTIONS = optionLabels(dacText.hpAmpMode?.options, ["甲乙类", "甲类"]);
const ANALOG_INPUT_OPTIONS = optionLabels(dacText.analogInput?.options, ["平衡", "非平衡"]);
const DAC_VOLUME_DIRECT_OPTIONS = optionLabels(dacText.dacVolumeDirect?.options, ["关闭", "0dB", "-12dB"]);
const DAC_ARC_OPTIONS = optionLabels(dacText.dacArc?.options, ["ARC", "EARC"]);
const XLR_OPTIONS = optionLabels(dacText.xlr?.options, ["正常", "反转"]);
@@ -318,8 +340,23 @@ export default function AudioPage() {
}}
/>
</div>
<div className="border-t border-white/[0.06]" />
<div>
</div>
{/* ── 模拟输入 ── */}
<SectionHeader title={dacText.sectionAnalogInput ?? "模拟输入"} />
<div className="ios-list-group">
<ListRow
label={dacText.analogInput?.label ?? "输入口"}
value={ANALOG_INPUT_OPTIONS[analogInputIdx] ?? ANALOG_INPUT_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.analogInput?.label ?? "输入口", ANALOG_INPUT_OPTIONS, analogInputIdx, "analogInput")}
/>
<ListRow
label={dacText.analogBypass ?? "模拟输入旁路"}
toggle
checked={analogBypassOn}
onToggle={(v) => updateSetting({ analog_bypass: v ? 1 : 0 })}
/>
<div className="px-4 py-3">
<div className="flex items-center justify-between">
<span className="text-[16px] text-white">{dacText.analogInputGain ?? "模拟输入增益"}</span>
<span className="text-[16px] font-semibold" style={{ color: "#00FFF6" }}>
@@ -354,17 +391,6 @@ export default function AudioPage() {
onClick={() => goSelect(dacText.filters?.label ?? "选择滤波特性", FILTER_OPTIONS, filterIdx, "filterCharacteristic")}
/>
)}
<ListRow
label={dacText.dacGain?.label ?? "耳机增益"}
value={GAIN_OPTIONS[gainIdx] ?? GAIN_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.dacGain?.label ?? "选择耳机增益", GAIN_OPTIONS, gainIdx, "dacGain")}
/>
<ListRow
label={dacText.autoImpedanceDetection ?? "自动检测耳机阻抗"}
toggle
checked={autoImpedanceOn}
onToggle={(v) => updateSetting({ dacImpedance: v ? 1 : 0 })}
/>
<ListRow
label={dacText.dacVolumeDirect?.label ?? "前级输出音量直通模式"}
value={DAC_VOLUME_DIRECT_OPTIONS[dacVolumeDirectIdx] ?? DAC_VOLUME_DIRECT_OPTIONS[0] ?? "—"}
@@ -398,6 +424,43 @@ export default function AudioPage() {
/>
</div>
{/* ── 耳机设定 ── */}
<SectionHeader title={dacText.sectionHpSettings ?? "耳机设定"} />
<div className="ios-list-group">
<ListRow
label={dacText.dacGain?.label ?? "耳机增益"}
value={GAIN_OPTIONS[gainIdx] ?? GAIN_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.dacGain?.label ?? "选择耳机增益", GAIN_OPTIONS, gainIdx, "dacGain")}
/>
<ListRow
label={dacText.autoImpedanceDetection ?? "自动检测耳机阻抗"}
toggle
checked={autoImpedanceOn}
onToggle={(v) => updateSetting({ dacImpedance: v ? 1 : 0 })}
/>
<ListRow
label={dacText.hpMaxVolume?.label ?? "耳机最大音量"}
value={HP_MAX_VOLUME_OPTIONS[hpMaxVolumeIdx] ?? HP_MAX_VOLUME_OPTIONS[0] ?? "—"}
onClick={() =>
goSelect(dacText.hpMaxVolume?.label ?? "耳机最大音量", HP_MAX_VOLUME_OPTIONS, hpMaxVolumeIdx, "hpMaxVolume")
}
/>
<ListRow
label={dacText.hpStartupVolume?.label ?? "耳机启动音量"}
value={HP_STARTUP_VOLUME_OPTIONS[hpStartupVolumeIdx] ?? HP_STARTUP_VOLUME_OPTIONS[0] ?? "—"}
onClick={() =>
goSelect(dacText.hpStartupVolume?.label ?? "耳机启动音量", HP_STARTUP_VOLUME_OPTIONS, hpStartupVolumeIdx, "hpStartupVolume")
}
/>
<ListRow
label={dacText.hpAmpMode?.label ?? "耳放工作模式"}
value={HP_AMP_MODE_OPTIONS[hpAmpModeIdx] ?? HP_AMP_MODE_OPTIONS[0] ?? "—"}
onClick={() =>
goSelect(dacText.hpAmpMode?.label ?? "耳放工作模式", HP_AMP_MODE_OPTIONS, hpAmpModeIdx, "hpAmpMode")
}
/>
</div>
{/* ── IIS 设置 ── */}
<SectionHeader title={dacText.sectionIisSettings ?? "IIS设置"} />
<div className="ios-list-group">
+39 -16
View File
@@ -40,6 +40,8 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
getHeadphoneMaxVolumeDb,
getHomeVolumeMax,
INPUT_BLUETOOTH_INDEX,
INPUT_LABELS,
isHomeVolumeLockedByPassthrough,
@@ -247,7 +249,8 @@ export default function Home() {
return null;
}, [volumeConfirm, homeText]);
const [localVol, setLocalVol] = useState(deviceState?.volume ?? 100);
const maxVolume = getHomeVolumeMax(deviceState);
const [localVol, setLocalVol] = useState(Math.min(maxVolume, deviceState?.volume ?? 100));
const isDragging = useRef(false);
const knobDraggingRef = useRef(false);
const knobActivePointerIdRef = useRef<number | null>(null);
@@ -257,9 +260,9 @@ export default function Home() {
useEffect(() => {
if (!isDragging.current && deviceState?.volume !== undefined) {
setLocalVol(deviceState.volume);
setLocalVol(Math.min(maxVolume, deviceState.volume));
}
}, [deviceState?.volume]);
}, [deviceState?.volume, maxVolume]);
useEffect(() => {
knobLastVolRef.current = localVol;
@@ -450,16 +453,26 @@ export default function Home() {
const vuLabel = `${homeText.vu ?? "VU表"}${(ds.vu ?? 0) + 1}`;
const bypassOn = (ds.dsp_enable ?? 0) === 0;
// 耳机输出且已设置耳机最大音量时,提示当前上限,避免用户误以为还能加到 0dB。
const hpMaxVolumeDb = getHeadphoneMaxVolumeDb(ds);
const hpMaxVolumeHint =
hpMaxVolumeDb === null
? null
: (homeText.hpMaxVolumeHint ?? "已设置耳机最大音量,最大音量为:{db}dB").replace(
"{db}",
String(hpMaxVolumeDb),
);
// Home 的 volume 取值为 0..200,对应 dB = (v - 200) / 2
// 因此 1 个 volume 单位 = 0.5dB
// AudioPage 的 soundStep:0->0.5dB, 1->1dB, 2->2dB, 3->3dB
// 换算为 volume 单位:0.5/1/2/3 dB -> 1/2/4/6
const fineVolumeStep = [1, 2, 4, 6][ds.soundStep ?? 1] ?? 1;
// Slider fill % (0-200 → 0-100%)
const fillPct = (localVol / 200) * 100;
// Knob angle (map 0..200 -> -135..+135 degrees)
const knobAngle = -135 + (localVol / 200) * 270;
// Slider fill % (0-maxVolume → 0-100%)
const fillPct = (localVol / maxVolume) * 100;
// Knob angle (map 0..maxVolume -> -135..+135 degrees)
const knobAngle = -135 + (localVol / maxVolume) * 270;
const pointAngle = (clientX: number, clientY: number, rect: DOMRect) => {
const cx = rect.left + rect.width / 2;
@@ -494,9 +507,9 @@ export default function Home() {
const rect = el.getBoundingClientRect();
const a = pointAngle(e.clientX, e.clientY, rect);
const delta = normalizeDelta(a - knobStartAngleRef.current);
// 270deg travel -> full scale; 1 rad maps to ~200/(1.5*pi) volume steps
const stepsPerRad = 200 / (Math.PI * 1.5);
const next = Math.max(0, Math.min(200, Math.round(knobStartVolRef.current + delta * stepsPerRad)));
// 270deg travel -> full scale; 1 rad maps to ~maxVolume/(1.5*pi) volume steps
const stepsPerRad = maxVolume / (Math.PI * 1.5);
const next = Math.max(0, Math.min(maxVolume, Math.round(knobStartVolRef.current + delta * stepsPerRad)));
setLocalVol(next);
knobLastVolRef.current = next;
};
@@ -700,7 +713,7 @@ export default function Home() {
}} />
<ThumbOnlyRangeSlider
min={0}
max={200}
max={maxVolume}
value={localVol}
disabled={volumePassthroughActive}
className={volumePassthroughActive ? "opacity-50" : undefined}
@@ -774,7 +787,7 @@ export default function Home() {
}}
onClick={() => {
if (blockVolumeAdjust()) return;
const next = Math.max(0, Math.min(200, localVol - fineVolumeStep));
const next = Math.max(0, Math.min(maxVolume, localVol - fineVolumeStep));
setLocalVol(next);
applyVolume(next);
}}
@@ -797,7 +810,7 @@ export default function Home() {
<button
type="button"
aria-label="Increase volume"
disabled={volumePassthroughActive || localVol >= 200}
disabled={volumePassthroughActive || localVol >= maxVolume}
className="w-10 h-10 rounded-full flex items-center justify-center transition-all active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed"
style={{
background: "rgba(44,44,46,0.6)",
@@ -806,7 +819,7 @@ export default function Home() {
}}
onClick={() => {
if (blockVolumeAdjust()) return;
const next = Math.max(0, Math.min(200, localVol + fineVolumeStep));
const next = Math.max(0, Math.min(maxVolume, localVol + fineVolumeStep));
setLocalVol(next);
applyVolume(next);
}}
@@ -817,6 +830,16 @@ export default function Home() {
</button>
</div>
{hpMaxVolumeHint && (
<p
className="mt-2 text-center text-[12px] leading-relaxed"
style={{ color: "rgba(255,255,255,0.5)" }}
role="status"
>
{hpMaxVolumeHint}
</p>
)}
{/* Bluetooth info — only when input source is Bluetooth */}
{ds.input === INPUT_BLUETOOTH_INDEX && ds.bt_status !== 0 && (
<div className="mt-3 px-3 py-3 text-center">
@@ -986,8 +1009,8 @@ export default function Home() {
setLocalVol(0);
applyVolume(0);
} else if (volumeConfirm === "max") {
setLocalVol(200);
applyVolume(200);
setLocalVol(maxVolume);
applyVolume(maxVolume);
}
setVolumeConfirm(null);
}}
+10
View File
@@ -125,6 +125,16 @@ const KEY_TO_SETTING: Record<string, string> = {
dacImpedance: "dacImpedance",
hpMaxVolume: "hpMaxVolume",
hpStartupVolume: "hpStartupVolume",
hpAmpMode: "hpAmpMode",
analogInput: "analog_input",
analogBypass: "analog_bypass",
dacVolumeDirect: "dacVolumeDirect",
dacArc: "dacArc",