本地音乐播放器
本地音乐播放器
Cancel

本地音乐播放器

选择一个音乐文件夹,浏览器会在本机深度优先遍历出所有支持的歌曲,形成一个播放列表,可随时切换、随机或循环播放,并用实时频带可视化展示正在播放的左右声道频谱。支持 FLAC / ALAC / APE / WAV / AIFF / DXD / MP3 / AAC / M4A / WMA / OGG / OPUS / MP2 / DSF / DFF / AC-3 / DTS 共 17 种格式;浏览器原生放不了的(APE / DSF / DFF / WMA / AC-3 / DTS / ALAC / DXD)会自动调用内置 ffmpeg(WebAssembly)转码后播放。全程本地处理,文件不上传。

—
专辑
—
曲风
—
格式
—
大小
—
时长
—
路径
—
0:00 0:00
暂无歌词

    3D 时频立体视图

    同一份傅里叶数据,用三维空间表达:X 轴 = 频率、Z 轴 = 时间、Y 轴 = 傅里叶幅值。每一层色带就是「某一时刻的整条频谱曲线」,鼠标可自由拖拽旋转 —— 正对 X 轴看是频域快照的堆叠,转到侧面看是时域演化,俯视则是一张时频热力图。

    三根轴的精确映射

    轴含义取值范围精确映射
    X频率FMIN → FMAX(随采样率与 fftSize 现算:48 kHz + fftSize 4096 → 11.7 Hz → 24 kHz)f = FMIN × (FMAX / FMIN)^x,其中 FMIN = 采样率 ÷ fftSize(可分辨下限)、FMAX = 采样率 ÷ 2(Nyquist,采样率极限)
    Z时间现在 → 约 5 秒前96 层 × 20 Hz 落帧;由每帧记录的真实播放时刻决定,不按固定帧率推算
    Y傅里叶幅值默认 0 → −54 dBFS(勾选「弱音可见」切到 −8 → −78 dBFS)以 dBFS 标注(0 = 满量程):dB = AnalyserNode 读数 + 14.1(实测标定偏移)

    为什么 Y 轴全是负数? dB 是比值,0 dBFS 的定义就是”满量程” —— 数字音频里超过它就意味着削顶,所以合法信号一律 ≤ 0。刻度上的 0 dBFS 是理论天花板(实际母带峰值通常在 −1 dBFS 上下);要让刻度”正负都有”,只能换一个参考点,例如广播惯例把 0 设在 −20 dBFS 的对齐电平上(那样节目就落在 0 附近、上下都有数)—— 那是另一种标法,需要的话可以加。

    数据来源(与原 2D 频谱同源)

    • FFT:AnalyserNode,fftSize = 4096 → 2048 个 bin;bin 间隔 sampleRate / fftSize(48 kHz 下 11.7 Hz,44.1 kHz 下 10.8 Hz,按实际采样率现算,不写死)。
    • 频带压缩:对数分布压成 64 带,直接由 FFT bin 归并(每带至少覆盖 1 个 bin,与 2D 条同一套「单调推进」规则)。
    • 声道:L / R 分开绘制 —— L 暖色(红→金)、R 冷色(蓝→青),与 2D 频谱条同一套色语;两路沿时间轴错开半个层距,互不遮挡。
    • 电平:getByteFrequencyData 的 0–255,不做 A 计权,dB 窗口默认 −66 … −12(面板上「弱音可见」可切到 −90 … −20)。
    • 平滑:非对称(攻击 0.72 / 释放 0.12),瞬态跟得上、回落又稳。
    • 采集节流:渲染循环按 rAF(≈60 fps)跑,但按 20 Hz 上限落帧 —— 48 层约覆盖 2.4 秒。实际帧率略低时跨度会稍长(实测 ≈2.7 s),这没关系:时间轴刻度标的是每帧的真实时间戳,不是按标称帧率推算。
    • 共享采集:与原 2D 频谱条共用同一个采集循环(spectrum.js 的单次采集 + 多订阅),不重复读 FFT。

    为什么能做到「频域和时域一点不差」

    1. 频率轴不写死采样率:每个频带的中心频率一律用 bin × sampleRate / fftSize 现算,48 k / 44.1 k / 96 k 的文件都严格对得上。
    2. 时间轴用真实时间戳:环形缓冲的每一帧都记录捕获时刻的播放时间,而不是假设「每 1/20 秒一帧」。即使掉帧,时间轴也不会错位。
    3. 悬停读数同时给两个值:平滑后的 dB(画面上真正看到的高度)与该帧原始瞬时的 dB(未平滑读数)并排显示,避免”标注和画面不一致”。
    4. 标出底层 bin 区间:读数里给出该频带覆盖的原始 FFT bin 范围,可一路回溯到最底层数据。
    5. 频率标的是实际值,不是轴位:低频段的频带比 FFT bin 还窄,对数轴上的标称位置会与真实数据相差很大(实测:轴位 84 Hz 的那一带,实际读到的是 bin 17 = 205 Hz)。因此读数以覆盖 bin 的中心频率为准,与轴位差异超过 12% 时同时把轴位标出来。这也是 X 轴下限取 采样率 ÷ fftSize(可分辨下限)而不是 0 的原因 —— 0 Hz 是直流(bin 0),对数轴上放不下,而且它通常只是直流偏移不是声音。
    6. 校准说明:AnalyserNode 的 dB 读数比真实 dBFS 低约 14 dB(Blackman 窗相干增益 + FFT 归一化所致,实测标定值)。若要以 dBFS 标注,把读数 +14 dB 即可 —— 这是实测量出来的,不是理论估计。

    数值标注(详细)

    • X 轴刻度:按当前频率范围自动筛选(48 kHz + fftSize 4096 时是 20 / 50 / 100 / 200 / 500 Hz / 1 k / 2 k / 5 k / 10 k / 20 k),并在轴两端标出可分辨下限与 Nyquist(采样率上限)。
    • Y 轴刻度:−12 / −20 / −30 / −40 / −50 / −60 / −66 dB,每刻度对应一条水平网格线。
    • Z 轴刻度:0 / −0.6 / −1.2 / −1.8 / −2.4 s,每刻度对应一条地面横向网格线。
    • 悬停读数:频率 · 频带序号 · 电平(平滑)· 电平(原始瞬时)· 相对现在的时间 · 该帧真实播放时刻 · 覆盖的 FFT bin 区间。
    • 实时极值:角落常驻显示当前帧的「峰值频率 + 峰值 dB」。

    交互

    按住拖动 = 旋转(pointer events,手机触摸同样可用,松手还有一点惯性);滚轮 = 缩放;悬停 = 读出该点数值(两声道分别给);双击 = 复位视角。

    面板上的控件(效果预设是”整装”入口,配色 / 样式 / 叠加层是三个独立维度用来微调,共 6 配色 × 6 样式 × 2³ 叠加 = 288 种组合;手动改任一项,预设会自动切到「自定义」。选择都记在 localStorage):

    • 效果(10 个预设,一次套用「样式 + 叠加层(+ 推荐配色)」):经典瀑布 / 线框山脊 / 柱林 / 立体方块 / 点云 / 粒子流 / 测量视图(填充+峰值轨迹)/ 舞台倒影(填充+倒影)/ 霓虹柱林(柱林+辉光)/ 极光帘幕(点云+倒影+辉光,配暗场霓虹最佳)。
    • 配色:六套方案自由切换 —— 暖冷对撞(默认)/ 跟随卡片(用卡片渐变那四色沿频率轴做多段渐变铺满色带,L 正序 / R 倒序,所以同一频率处左右声道颜色天然不同;无封面时用内置默认渐变)/ 封面吸色(从封面四色里挑色相距离最大的一对给 L / R,封面接近单色时自动退回暖冷对撞)/ 双色扁平 / 莫兰迪柔彩 / 暗场霓虹(深色舞台)。
    • 样式:六种主体样式 —— 填充色带(默认)/ 线框网格(骨架;脊线每 3 层一条、纵向线每 4 个频带一条,两个方向都稀疏才是网格,全画会糊成刷毛)/ 立体柱林(每频带一根竖柱)/ 立体方块(每频带一个实心块,最像 3D 柱状图)/ 点云 / 粒子流(点沿时间方向拉出短线段)。
    • 五个叠加层,可任意组合:网格地板(Tron / retrowave 那种:网格延伸到数据盒之外,越靠前越亮、远处淡出,并用 R 声道那侧的颜色——和 L 的暖色互补,网格不会和数据糊在一起)/ 网格笼(地板 / 天花板 / 四面墙全铺网格,形成三维笼子)/ 倒影(同一层镜像到地面以下,α 压到 26%)/ 辉光(脊线用「粗半透明打底 + 细亮线」两层 —— 不用 shadowBlur,否则 96 条色带会卡死)/ 峰值轨迹(每层的峰值点连成一条线,能看出主频走向)。
    • 弱音可见:把 dB 窗口放宽到 −8…−78 dBFS(analyser 的窗口是 2D 与 3D 共用的,所以 2D 频谱条会同步变化)。

    实现注记:每帧先把各层几何一次性投影进扁平 Float32Array(索引 (层×2+声道)×频带数+带号),再由样式函数取点绘制。这样既支撑了「纵向骨架 / 粒子流 / 镜像 / 峰值轨迹」这些需要跨层取点的效果,又把每帧耗时从 2.5 ms 降到 ≤1 ms(省掉了每帧几万个临时点对象)。

    实现要点(含踩过的坑)

    1. 轨道相机:yaw 绕 Y 轴、pitch 绕 X 轴,透视投影 k = D / (D − Z)。
    2. 缩放必须按包围球半径算,不能用包围盒 —— 用包围盒的话,一旋转包围盒尺寸就在变,画面会”忽大忽小”地呼吸。
    3. 每帧按深度排序、远的先画(画家算法),否则遮挡关系是错的。
    4. 悬停读数:把投影坐标缓存进 Float32Array,在屏幕空间做一次 O(n) 最近点查找 —— 比反投影回世界坐标简单得多,且完全够用。
    5. 性能:48 层填充 + 48 层描边 + 网格线 + 3072 次投影 ≈ 3–6 ms/帧,Canvas 2D 稳定 60 fps。零外部依赖,不需要 WebGL。
    6. 可选增强:点击”钉住”多个读数做对比、按时间剖面切割、Y 轴在线性/对数之间切换。

    工程状态:本视图的设计与精度方案已定稿,代码接入前需先完成 spectrum.js 的「单次采集 + 多订阅」重构(保证原 2D 频谱行为零变化)。