生成可重复而无相位跳变的正弦波样本

问题描述 投票:0回答:1

我需要生成正弦波样本以将其作为音频播放。频率范围为 200 至 2000Hz,步长为 1Hz。这是嵌入式项目的一部分,我想花尽可能少的时间来生成音频样本。我的解决方案是生成一个周期的样本并重复播放它们,直到需要更改音调频率,此时我将生成新的样本。

use std::f32::consts::TAU;

const SAMPLE_RATE: u32 = 8000;
const BUFFER_SIZE: usize = 100;

fn main() {
    let mut values = Vec::new();

    let mut buffer = [0f32; BUFFER_SIZE];

    let mut tone_generator = ToneGenerator { phase: 0.0 };

    let count = tone_generator.generate_samples(&mut buffer, 495, 0.0);
    values.extend_from_slice(&buffer[0..count]); // Pretend this is pushing the samples to a FIFO
    values.extend_from_slice(&buffer[0..count]); // Repeat the same values
    values.extend_from_slice(&buffer[0..count]); // Again
    let count = tone_generator.generate_samples(&mut buffer, 496, 0.0); // New samples
    values.extend_from_slice(&buffer[0..count]); // Etc.
    values.extend_from_slice(&buffer[0..count]);
    let count = tone_generator.generate_samples(&mut buffer, 497, 0.0);
    values.extend_from_slice(&buffer[0..count]);
    values.extend_from_slice(&buffer[0..count]);

    // Plot all the samples
    let mut plot = plotly::Plot::new();
    let ts = values
        .iter()
        .enumerate()
        .map(|(ts, _)| ts as f32 / SAMPLE_RATE as f32)
        .collect::<Vec<_>>();
    let trace = plotly::Scatter::new(ts, values.clone())
        .name("sin")
        .mode(plotly::common::Mode::LinesMarkers);
    plot.add_trace(trace);
    plot.show();
}

struct ToneGenerator {
    phase: f32,
}

impl ToneGenerator {
    fn generate_samples<'a>(
        &'a mut self,
        buffer: &'a mut [f32; BUFFER_SIZE],
        frequency: u32,
        volume_db: f32,
    ) -> usize {
        let delta = frequency as f32 * TAU / SAMPLE_RATE as f32;
        let amplitude = libm::powf(10.0, volume_db / 20.).min(1.0);

        let mut count = 0;

        for value in buffer.iter_mut() {
            *value = self.phase.sin() * amplitude;
            count += 1;

            self.phase += delta;
            if self.phase > TAU {
                self.phase -= TAU;
                break;
            }
        }

        count
    }
}

问题是,当我以 1Hz 为步长增加音调频率时,步长之间声音的变化不是恒定的。我认为有两个原因:

  1. 单个周期的音频样本数量取决于音调频率。当频率的变化导致样本数量发生变化时,感知到的音调(以及音量)变化比样本数量保持不变时大得多。
  2. 对于某些频率,重复采样时会出现显着的相位跳跃,如上面代码的图中所示:

有没有一种方法可以在频率不变的情况下解决这些问题而不需要生成新的样本?对我来说,这两个要求似乎是矛盾的,但也许有一种技术可以做到这一点?

rust signal-processing
1个回答
0
投票

如果以特定周期(此处由样本计数确定)重复波形,则它将仅包含基频(周期的倒数)倍数的频率。做其他事是不可能的;信号数学告诉我们这一点。 (考虑信号的傅里叶变换是什么。)

因此,为了对预生成的波进行更精确的频率选择,必须将它们做得更长——特别是,为了填补“N个样本”和“N+1个样本”周期之间的间隙,您需要生成具有 (N × 2) + 1 个样本的信号,其中包含两个波周期。这不是冗余数据,因为中间部分的样本与两端不同。

或者相反,要使用更少的存储空间,您可以通过重新采样(在存储的样本之间进行插值)来更改播放期间单个波形的频率 - 但我相信这相当于使用正弦查找表生成正弦波.

另一种通用策略是通过旋转单位向量(或等效的复数)来生成波。

  1. 根据频率,计算单个样本的相位变化的旋转矩阵。
  2. 存储向量(x 和 y,或 i 和 j,或 sin 和 cos),初始等于 (0, 1)
  3. 要生成下一个样本,请将向量乘以旋转矩阵,然后将向量中的两个数字之一(任一)作为输出样本。
  4. 偶尔通过标准化向量(将其除以长度)来纠正数值误差。

这需要计算归一化的平方根,但除了初始化外,不需要计算正弦或余弦。

© www.soinside.com 2019 - 2024. All rights reserved.