## ADDED Requirements ### Requirement: FFT 频谱变换 系统 SHALL 提供 `FFTAnalyzer` 工具类,接收 1024 个 PCM 时域样本(`number[]`),通过 Cooley-Tukey 基 2 快速傅立叶变换算法计算出 512 个频率 bin 的幅值数组。 #### Scenario: 正常 FFT 计算 - **WHEN** 调用 `FFTAnalyzer.analyze(pcmData: number[], sampleRate: number)` 且 `pcmData` 长度为 1024 - **THEN** 系统 SHALL 返回 `SpectrumData` 对象,包含 512 个频率 bin 的归一化幅值(0.0 ~ 1.0 范围) #### Scenario: 输入数据为空 - **WHEN** 调用 `FFTAnalyzer.analyze(pcmData, sampleRate)` 且 `pcmData` 长度为 0 - **THEN** 系统 SHALL 返回全零的 `SpectrumData` 对象 ### Requirement: Hann 窗函数预处理 FFT 计算前 SHALL 对输入 PCM 数据应用 Hann 窗函数,以减少频谱泄漏。 #### Scenario: 窗函数应用 - **WHEN** 1024 个 PCM 样本进入 FFT 分析流程 - **THEN** 系统 SHALL 将每个样本乘以对应位置的 Hann 窗系数 `0.5 * (1 - cos(2π * n / N))`,然后再执行 FFT ### Requirement: 对数频率映射与频段聚合 系统 SHALL 将 512 个线性频率 bin 按对数刻度映射并聚合为 32 个可视化频段。 #### Scenario: 频段聚合输出 - **WHEN** FFT 计算完成后调用 `SpectrumData.getBands(bandCount: number)` - **THEN** 系统 SHALL 返回 `number[]`,长度等于 `bandCount`,每个元素为对应频率范围内所有 bin 幅值的平均值,归一化到 0.0 ~ 1.0 #### Scenario: 低频到高频覆盖 - **WHEN** 采样率为 44100Hz,请求 32 个频段 - **THEN** 频段 SHALL 按对数分布覆盖约 32Hz ~ 16000Hz 范围,低频区每个频段覆盖较窄的频率范围,高频区每个频段覆盖较宽的频率范围 ### Requirement: 指数平滑处理 系统 SHALL 对连续帧的频谱数据进行指数移动平均平滑,避免视觉跳变。 #### Scenario: 平滑衰减 - **WHEN** 新一帧频谱数据到达 - **THEN** 每个频段的输出值 SHALL 计算为 `output = newValue * smoothUp + previousOutput * (1 - smoothUp)`(上升时),或 `output = newValue * smoothDown + previousOutput * (1 - smoothDown)`(下降时),其中上升系数约 0.7,下降系数约 0.3,实现快速响应、缓慢衰减的视觉效果 #### Scenario: 首帧无历史数据 - **WHEN** 第一帧频谱数据到达(无历史数据) - **THEN** 系统 SHALL 直接使用当前帧数据作为输出,不进行平滑 ### Requirement: 总体能量值计算 `SpectrumData` SHALL 提供 `getEnergy(): number` 方法,返回当前帧所有频段幅值的均方根值,归一化到 0.0 ~ 1.0,代表当前音频的整体响度/能量。 #### Scenario: 静音时能量为零 - **WHEN** 所有 PCM 样本值均为 0 - **THEN** `getEnergy()` SHALL 返回 0.0 #### Scenario: 正常播放时计算能量 - **WHEN** 音频正常播放,频谱数据有效 - **THEN** `getEnergy()` SHALL 返回一个介于 0.0 ~ 1.0 之间的值,反映当前帧的整体响度