870920 Menu

SwingCoder之数字音频与DSP编程基础·4

波形合成与波表合成

 所需的常量和变量
const double sampleRate = 44100.00; // 采样率
const frequency = 220.00; // 所生成的音高频率
const double increment = 2.0 * double_Pi / sampleRate * frequency;
double currentPhase = 0.0; // 当前采样的相位弧度
float sample = 0.0f; // 当前采样值

 各种波的采样值计算公式
sample = (float)sin(currentPhase); // 正弦波
sample = (currentPhase <= double_Pi) ? 1.0f : -1.0f; // 方波
sample = float(1.0 – currentPhase / double_Pi); // 降齿波
sample = float(currentPhase / double_Pi – 1.0); // 升齿波
sample = float(currentPhase / double_Pi – 1.0); // 三角波
if (sample < 0) sample = -sample;
sample = 2.0f * sample – 1.0f;

 递增相位。生成当前采样值之后,紧接着调用以下语句,递增相位弧度,并确保在0到2π之间
currentPhase += increment;
const double twoPi = 2.0 * double_Pi;
if (currentPhase >= twoPi) currentPhase -= twoPi;
if (currentPhase < 0.0) currentPhase += twoPi;

反复调用采样值计算和相位递增语句,每次调用均生成一个正弦波采样值。调用的次数为所需的采样数。

 更简单的单行语句生成正弦波采样:
for (int i = 0; i < samplesNum; ++i)
sampleValue = float(sin(2.0 * double_Pi * 音高频率 * i / 采样率));

该公式即:(2π * 音高频率 * 时间点)的正弦值 = 该时间点的振幅值

其中,时间点的计算公式正是:i / 采样率。推导公式是:秒数 * 采样率 = 采样个数

 波表合成(Wavetable Synth)

上面生成每个采样值时,均需调用sin()函数,该函数的参数同样要经过一系列的double运算,效率较低。实际上,固定音高和采样率的正弦波,每个振动周期均由一批相同的振幅值所构成,多个振动周期只不过是重复这些值而已。每秒钟的振动周期为音高频率,采样率 / 音高频率 = 每秒的振动次数(几个周期),由此可推导出单次振动周期包含了几个采样点。将构成单次振动周期的采样点存储到一个数组中,而后用查表法获取对应时间点的采样值,即可生成任意时间长度的所有采样值。这种将单次振动的采样值存储起来,使用时查表获取的技术,即“波表合成”。可提前将波表中的采样值存储到数组中,也可生成波形时,根据给定的音高频率临时存储到数组中。通常,采用事先存储(固定表长),不同的采样率和音高频率,读取时采用“跳增”的方式读取某个时间点所对应的采样值(采样增量)。由于低频的振动周期较长,因此,需要更多的波表值。如果波表中的采样数不够,则使用重复读取某个值的方法。

由于音高频率并非整数倍增加或减少,采样增量实际上是一个分数值(实数),而非整数值。也就是:给出的采样点,对应的波表的索引是一个小数,此时可采用线性插值的方法计算出所需的、表中没有的值。如果要求不高,也可以直接舍弃小数部分,按整数索引获取表中的对应值(可能会引入噪声或失真)。

波表合成这种避免重复、同时又大幅度提升效率的技术不仅适用于合成声音和信号,更可作为一种策略和技巧运用于实际编程的各类算法实现中。

 环形调制(Ring Modulation)

信号中的每个采样值乘以一个正弦波或其他波形信号中对应的采样值,所产生的结果即为环形调制。