154 lines
6.5 KiB
TypeScript
154 lines
6.5 KiB
TypeScript
|
|
/* ============================================================
|
||
|
|
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>
|
||
|
|
);
|
||
|
|
}
|