内容目录
背景
采集精度不够,这个我开发nes 模拟器 处理apu时候遇到一个问题,虽然没有太多影响,耳朵基本听不出来
比喻我们重采样公式大概是:
per_sample_cycle = fcpu/sample_rate
fcpu = 对应模拟器的采集率 , NTSC 的cpu
sample_rate = 采集频率,假设采集率 44100
per_sampe_cycle = 得到多少cycle 采集一个点
计算的得到:40.5 cycle 采集一个点,但我们指令最小都是一个cycle,那么每次间隔41 cycle,那这样子就不是44100 采集频率,那么会出现误差。
解决办法
相位了累加器,本质就是放大
比喻我们1/40.5 = 我们缩小放到1,这里等价1 分成40.5,1/40.5 每份是多少,1/40.5 这个就是相位宽度,进行转换到1,那么相位宽度是1/40.5。
// NES CPU 频率 (ntsc版本)
const double CPU_FREQ = 1789773.0;
// 目标采样率
const double SAMPLE_RATE = 44100.0;
// 相位增量:每个 CPU cycle 增加多少“采样相位”
const double PHASE_INC = SAMPLE_RATE / CPU_FREQ;
double phase_acc = 0.0; // 相位累加器
// 在每个 CPU cycle 调用一次
void step_cpu_cycle() {
phase_acc += PHASE_INC;
// 当累加器 >= 1,说明该输出一个采样
if (phase_acc >= 1.0) {
phase_acc -= 1.0;
// 这里生成一个采样点
int sample = mix_apu_output(); // 例如混合 APU 各声道
push_to_audio_buffer(sample);
}
}
放大65536
1/40.5 ===> 放大到:65536 / 40.5
// PAL NES 核心参数
#define PAL_CPU_CLOCK 1789773 //
#define SAMPLE_RATE 44100 // 音频采样率
// 相位累加器步长 = CPU频率 * 65536 / 采样率
// 65536 = 2^16,用16位定点数运算,无浮点误差
#define PHASE_STEP (PAL_CPU_CLOCK * 65536 / SAMPLE_RATE)
// 每个 CPU cycle 执行一次
void cpu_cycle_tick() {
// 1. 执行你的CPU/APU逻辑
apu_tick();
// 2. 累加相位步长
phase_accumulator += PHASE_STEP;
// 3. 如果溢出(超过2^16),采集一次音频样本
if (phase_accumulator >= 65536) {
// 采集APU当前音频值
int16_t sample = apu_get_sample();
// 写入音频缓冲区
audio_buffer_push(sample);
// 减去溢出值,保留剩余相位(核心:不丢失精度)
phase_accumulator -= 65536;
}
}
这样子好处不用浮点运算,速度快很多,virtualnes 模拟器貌似用这个,我开始不明白为什么乘65536,主要运算速度快,以前的人开发代码对代码速度要求很高的,因为计算不够快,那么程序就跑不快,现在我们看那时候代码就觉得不理解了。