This commit is contained in:
yangy
2026-05-26 18:46:41 +08:00
parent 0c9e42b91c
commit 47ea7d7b56
13 changed files with 1370 additions and 121 deletions
+96 -25
View File
@@ -2,9 +2,8 @@
AUDIO PAGE — Audio Settings
Design: Reference luxsin_x8_音频设置.png
Sections:
- 平衡与灵敏度: 左右平衡 slider, VU灵敏度 slider
- 平衡与灵敏度: 左右平衡, VU灵敏度, 模拟输入增益 sliders
- 输出特性: 滤波特性, 耳机增益, 音量幅度
- IIS设置: IIS静音电平, IIS模式
- 系统与接口: 开机音量, XLR极性
All list rows → navigate to /select page
============================================================ */
@@ -70,7 +69,31 @@ function SectionHeader({ title }: { title: string }) {
);
}
function ListRow({ label, value, onClick }: { label: string; value: string; onClick?: () => void }) {
function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
return (
<label className="ios-toggle" onClick={(e) => e.stopPropagation()}>
<input type="checkbox" checked={checked} onChange={(e) => onChange(e.target.checked)} />
<span className="ios-toggle-track"><span className="ios-toggle-thumb" /></span>
</label>
);
}
function ListRow({ label, value, onClick, toggle, checked, onToggle }: {
label: string;
value?: string;
onClick?: () => void;
toggle?: boolean;
checked?: boolean;
onToggle?: (v: boolean) => void;
}) {
if (toggle) {
return (
<div className="ios-list-row">
<span className="flex-1 text-[16px] text-white">{label}</span>
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
</div>
);
}
return (
<button className="ios-list-row w-full text-left active:bg-white/5 transition-colors" onClick={onClick}>
<span className="flex-1 text-[16px] text-white">{label}</span>
@@ -84,17 +107,18 @@ type LocaleDac = {
dac?: {
sectionBalanceSensitivity?: string;
sectionOutput?: string;
sectionIisSettings?: string;
sectionSystemInterface?: string;
balance?: string;
sensitivity?: string;
analogInputGain?: string;
autoImpedanceDetection?: string;
filters?: { label?: string; options?: Array<{ index: number; label: string }> };
dacGain?: { 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;
bootVolume?: string | { label?: string; options?: Array<{ index: number; label: string }> };
xlr?: { label?: string; options?: Array<{ index: number; label: string }> };
mutePolar?: { label?: string; options?: Array<{ index: number; label: string }> };
IISMode?: { label?: string; options?: Array<{ index: number; label: string }> };
};
home?: { audioSet?: string };
};
@@ -166,15 +190,22 @@ export default function AudioPage() {
const balanceVal = ds?.balance !== undefined ? ds.balance / 10 : 0;
// vuSensor 从接口获取的是放大 2 倍的值,需要除以 2 显示(例如:20 → +10dB)
const vuSensVal = vuSensor !== undefined ? vuSensor / 2 : 0;
// analogGain 从接口获取的是放大 2 倍的值,需要除以 2 显示(例如:-6 → -3dB)
const analogGainVal = ds?.analogGain !== undefined ? ds.analogGain / 2 : 0;
const bootSoundFromApi = readBootSoundFromState(ds);
const [localBootSound, setLocalBootSound] = useState(bootSoundFromApi);
const [localBalance, setLocalBalance] = useState(balanceVal);
const [localVuSens, setLocalVuSens] = useState(0);
const [localAnalogGain, setLocalAnalogGain] = useState(analogGainVal);
const isBalanceDragging = useRef(false);
const isVuDragging = useRef(false);
const isAnalogGainDragging = useRef(false);
useEffect(() => { if (!isBalanceDragging.current) setLocalBalance(balanceVal); }, [balanceVal]);
useEffect(() => { if (!isVuDragging.current) setLocalVuSens(vuSensVal); }, [vuSensVal]);
useEffect(() => {
if (!isAnalogGainDragging.current) setLocalAnalogGain(analogGainVal);
}, [analogGainVal]);
useEffect(() => {
setLocalBootSound(readBootSoundFromState(ds));
}, [ds?.bootSound]);
@@ -192,11 +223,12 @@ export default function AudioPage() {
}, [location, isConnected, api, refresh]);
const gainIdx = ds?.dacGain ?? 0;
const autoImpedanceOn = (ds?.dacImpedance ?? 0) === 1;
const dacVolumeDirectIdx = ds?.dacVolumeDirect ?? 0;
const dacArcIdx = ds?.dacArc ?? 0;
const stepIdx = ds?.soundStep ?? 1;
const filterIdx = ds?.pcm ?? 0;
const xlrIdx = ds?.xlr ?? 0;
const mutePolarIdx = (ds as ({ hdmimutepolar?: number } | null))?.hdmimutepolar ?? 0;
const iisModeIdx = (ds as ({ hdmiType?: number } | null))?.hdmiType ?? 0;
const bootSoundVal = normalizeBootSound(localBootSound);
const firmwareVersion = parseFirmwareVersion(ds?.version);
const bootSoundMaxIdx = getBootSoundMaxIndex(firmwareVersion);
@@ -205,9 +237,9 @@ export default function AudioPage() {
const FILTER_OPTIONS = optionLabels(dacText.filters?.options, ["快速滚降", "慢速滚降", "短延迟快速滚降", "短延迟慢速滚降", "去重强调", "非过采样(NOS"]);
const GAIN_OPTIONS = optionLabels(dacText.dacGain?.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, ["正常", "反转"]);
const MUTE_POLAR_OPTIONS = optionLabels(dacText.mutePolar?.options, ["低电平", "高电平"]);
const IIS_MODE_OPTIONS = optionLabels(dacText.IISMode?.options, ["模式1", "模式2", "模式3", "模式4", "模式5", "模式6", "模式7", "模式8"]);
const goSelect = (title: string, options: string[], selected: number, key: string) => {
navigateToSelect(title, options, selected, "/audio", key);
@@ -277,6 +309,30 @@ export default function AudioPage() {
}}
/>
</div>
<div className="border-t border-white/[0.06]" />
<div>
<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" }}>
{localAnalogGain >= 0 ? `+${localAnalogGain.toFixed(1)}` : localAnalogGain.toFixed(1)} dB
</span>
</div>
<CyanSlider
value={localAnalogGain}
min={-12}
max={12}
step={0.5}
onChange={(v) => {
isAnalogGainDragging.current = true;
setLocalAnalogGain(v);
}}
onRelease={(v) => {
isAnalogGainDragging.current = false;
setLocalAnalogGain(v);
updateSetting({ analogGain: Math.round(v * 2) });
}}
/>
</div>
</div>
{/* ── 输出特性 ── */}
@@ -292,6 +348,36 @@ export default function AudioPage() {
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] ?? "—"}
onClick={() =>
goSelect(
dacText.dacVolumeDirect?.label ?? "前级输出音量直通模式",
DAC_VOLUME_DIRECT_OPTIONS,
dacVolumeDirectIdx,
"dacVolumeDirect",
)
}
/>
<ListRow
label={dacText.dacArc?.label ?? "ARC模式"}
value={DAC_ARC_OPTIONS[dacArcIdx] ?? DAC_ARC_OPTIONS[0] ?? "—"}
onClick={() =>
goSelect(
dacText.dacArc?.label ?? "ARC模式",
DAC_ARC_OPTIONS,
dacArcIdx,
"dacArc",
)
}
/>
<ListRow
label={dacText.volumeStep ?? "音量幅度"}
value={STEP_OPTIONS[stepIdx] ?? STEP_OPTIONS[0]}
@@ -299,21 +385,6 @@ export default function AudioPage() {
/>
</div>
{/* ── IIS 设置 ── */}
<SectionHeader title={dacText.sectionIisSettings ?? "IIS设置"} />
<div className="ios-list-group">
<ListRow
label={dacText.mutePolar?.label ?? "IIS 静音电平"}
value={MUTE_POLAR_OPTIONS[mutePolarIdx] ?? MUTE_POLAR_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.mutePolar?.label ?? "选择 IIS 静音电平", MUTE_POLAR_OPTIONS, mutePolarIdx, "mutePolar")}
/>
<ListRow
label={dacText.IISMode?.label ?? "IIS 模式"}
value={IIS_MODE_OPTIONS[iisModeIdx] ?? IIS_MODE_OPTIONS[0] ?? "—"}
onClick={() => goSelect(dacText.IISMode?.label ?? "选择 IIS 模式", IIS_MODE_OPTIONS, iisModeIdx, "IISMode")}
/>
</div>
{/* ── 系统与接口 ── */}
<SectionHeader title={dacText.sectionSystemInterface ?? "系统与接口"} />
<div className="ios-list-group">