Files
controller-v2/client/src/pages/AudioPage.tsx
T

154 lines
6.5 KiB
TypeScript
Raw Normal View History

/* ============================================================
AUDIO PAGE — Audio Settings
Design: Reference luxsin_x8_音频设置.png
Sections:
- 平衡与灵敏度: 左右平衡 slider, VU灵敏度 slider
- 输出特性: 滤波特性, 耳机增益, 音量幅度
- 系统与接口: XLR极性, DAC增益, DAC阻抗
All list rows → navigate to /select page
============================================================ */
import { useDevice } from "@/contexts/DeviceContext";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useLocation } from "wouter";
import { useState, useEffect, useRef } from "react";
import BottomNav from "@/components/BottomNav";
import { navigateToSelect } from "./SelectPage";
function CyanSlider({ value, min, max, step = 1, onChange }: {
value: number; min: number; max: number; step?: number; onChange: (v: number) => void;
}) {
const fillPct = ((value - min) / (max - min)) * 100;
return (
<div className="relative flex items-center w-full mt-2">
<div className="absolute left-0 h-[4px] rounded-full pointer-events-none"
style={{ width: `${fillPct}%`, background: "#00FFF6", boxShadow: "0 0 6px rgba(0,255,246,0.5)" }} />
<input type="range" min={min} max={max} step={step} value={value}
className="cyan-slider relative z-10"
onChange={(e) => onChange(Number(e.target.value))} />
</div>
);
}
function SectionHeader({ title }: { title: string }) {
return (
<div className="px-1 pt-5 pb-2">
<span className="text-[14px] font-semibold" style={{ color: "#00FFF6" }}>{title}</span>
</div>
);
}
function ListRow({ label, value, onClick }: { label: string; value: string; onClick?: () => void }) {
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>
<span className="ios-row-value">{value}</span>
<ChevronRight size={16} className="ios-chevron ml-1" />
</button>
);
}
const FILTER_OPTIONS = ["短延迟快速滚降", "短延迟慢速滚降", "快速滚降", "慢速滚降", "超慢速滚降"];
const GAIN_OPTIONS = ["低", "中", "高"];
const STEP_OPTIONS = ["0.5dB", "1dB", "2dB"];
const XLR_OPTIONS = ["正常", "反转"];
const DAC_GAIN_OPTIONS = ["0dB", "+3dB", "+6dB"];
const DAC_IMP_OPTIONS = ["32Ω", "150Ω", "300Ω"];
export default function AudioPage() {
const [, setLocation] = useLocation();
const { deviceState, updateSetting } = useDevice();
const ds = deviceState;
const balanceVal = ds?.balance ?? 0;
const [localBalance, setLocalBalance] = useState(balanceVal);
const [localVuSens, setLocalVuSens] = useState(12);
const [filterIdx] = useState(0);
const isDragging = useRef(false);
useEffect(() => { if (!isDragging.current) setLocalBalance(balanceVal); }, [balanceVal]);
const gainIdx = ds?.analogGain ?? 0;
const stepIdx = ds?.soundStep ?? 1;
const xlrIdx = ds?.xlr ?? 0;
const dacGainIdx = ds?.dacGain ?? 0;
const dacImpIdx = ds?.dacImpedance ?? 0;
const goSelect = (title: string, options: string[], selected: number, key: string) => {
navigateToSelect(title, options, selected, "/audio", key);
setLocation("/select");
};
return (
<div className="min-h-screen bg-black">
<div className="page-header">
<button onClick={() => setLocation("/")} className="mr-4 text-white/60 active:text-white transition-colors">
<ChevronLeft size={24} />
</button>
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">音频设置</h1>
<div className="w-8" />
</div>
<div className="px-4 pb-32">
{/* ── 平衡与灵敏度 ── */}
<SectionHeader title="平衡与灵敏度" />
<div className="ios-list-group px-4 py-3 space-y-4">
<div>
<div className="flex items-center justify-between">
<span className="text-[16px] text-white">左右平衡</span>
<span className="text-[16px] font-semibold" style={{ color: "#00FFF6" }}>
{localBalance >= 0 ? `+${localBalance.toFixed(1)}` : localBalance.toFixed(1)} dB
</span>
</div>
<CyanSlider value={localBalance} min={-10} max={10} step={0.1}
onChange={(v) => {
isDragging.current = true;
setLocalBalance(v);
updateSetting({ balance: v });
setTimeout(() => { isDragging.current = false; }, 500);
}} />
</div>
<div className="border-t border-white/[0.06]" />
<div>
<div className="flex items-center justify-between">
<span className="text-[16px] text-white">VU 表灵敏度</span>
<span className="text-[16px] font-semibold" style={{ color: "#00FFF6" }}>
{localVuSens} dB
</span>
</div>
<CyanSlider value={localVuSens} min={0} max={24}
onChange={(v) => {
isDragging.current = true;
setLocalVuSens(v);
setTimeout(() => { isDragging.current = false; }, 500);
}} />
</div>
</div>
{/* ── 输出特性 ── */}
<SectionHeader title="输出特性" />
<div className="ios-list-group">
<ListRow label="滤波特性" value={FILTER_OPTIONS[filterIdx]}
onClick={() => goSelect("选择滤波特性", FILTER_OPTIONS, filterIdx, "filterCharacteristic")} />
<ListRow label="耳机增益" value={GAIN_OPTIONS[gainIdx]}
onClick={() => goSelect("选择耳机增益", GAIN_OPTIONS, gainIdx, "analogGain")} />
<ListRow label="音量幅度" value={STEP_OPTIONS[stepIdx]}
onClick={() => goSelect("选择音量幅度", STEP_OPTIONS, stepIdx, "soundStep")} />
</div>
{/* ── 系统与接口 ── */}
<SectionHeader title="系统与接口" />
<div className="ios-list-group">
<ListRow label="XLR 端口极性" value={XLR_OPTIONS[xlrIdx]}
onClick={() => goSelect("选择 XLR 极性", XLR_OPTIONS, xlrIdx, "xlr")} />
<ListRow label="DAC 增益" value={DAC_GAIN_OPTIONS[dacGainIdx]}
onClick={() => goSelect("选择 DAC 增益", DAC_GAIN_OPTIONS, dacGainIdx, "dacGain")} />
<ListRow label="DAC 阻抗" value={DAC_IMP_OPTIONS[dacImpIdx]}
onClick={() => goSelect("选择 DAC 阻抗", DAC_IMP_OPTIONS, dacImpIdx, "dacImpedance")} />
</div>
</div>
<BottomNav />
</div>
);
}