声音合成理论/振荡器和波表
一个振荡器是具有基频和峰值幅度的重复波形,它是如今大多数流行合成技术的基石。除了振荡器的频率或音高及其幅度外,最重要的特征之一是其波形的形状。图 5.1 中的时间域波形显示了四个最常用的振荡器波形。虽然可以使用各种独特的形状,但这四种形状各自服务于一系列功能,适用于一系列不同的合成技术;从正弦波的平滑、纯净的声音到锯齿波的富含泛音的嗡嗡声。
振荡器通常由键盘合成器或 MIDI 协议设备控制。按下键会产生 MIDI 音符值,该值将转换为振荡器接受为输入的频率值 (Hz),并且波形周期将根据指定的频率相应地重复。从此处,声音可以在合成器或程序中以各种方式处理或操作,以进一步丰富或修改声音。
请注意,以下图表渲染不正确。x 轴应放在较低的位置,每个信号都应在 π 弧度处穿过 y=0。
如前所述,正弦波可以被认为是最基本的声音构建块。生成产生这种波形的振荡器的最佳方法是利用系统中内置的库或函数。许多编程语言都有标准数学库,其中表示了许多三角函数。正弦波的一个周期是 弧度长,峰值幅度为 ,如图 图 5.2 所示。在数字系统中,生成的波将是一系列以采样率均匀间隔的值。
以每秒 44100 个周期的采样率,以及 1 秒的所需周期长度,需要 44100 个样本才能从 0 到。换句话说,我们可以从周期长度确定每周期步数
其中是采样率。因此,每一步将以以下弧度量进行
其中 ,在第二个结果中,是频率方面相同的结果。其重要性在于它可以扩展成一种算法,该算法适用于生成用户指定频率和振幅的正弦波,实际上是最简单的合成器!正弦波可以通过重复将相位值增加一定量来生成,以达到每秒所需的 长度循环,以采样率。该值可以传递给正弦函数以创建输出值,介于用户指定的峰值振幅之间。
Input: Peak amplitude (A), Frequency (f) Output: Amplitude value (y) y = A * sin(phase) phase = phase + ((2 * pi * f) / samplerate) if phase > (2 * pi) then phase = phase - (2 * pi)
关于此算法最重要的是,当相位值超过 时,它将减去一个完整周期。这样做是为了确保函数“回绕”到正确的位置,而不是直接返回到 0;如果相位增量 *越过* 并重置为 0,则会出现不希望有的不连续性,导致振荡声音的谐波失真。
方波
[edit | edit source]方波不能轻易地从数学函数库中生成,但算法非常直接,因为它是由直线段构建的。与正弦波不同,方波在其 *基频* 以上具有许多谐波,并且具有更明亮、更尖锐的音色。在检查了许多不同的波形之后,人们会开始意识到,具有陡峭边缘和/或突然变化和不连续性的波形通常富含谐波。
(请注意,以下方波、锯齿波和三角波函数是“天真的”;它们等效于在对理想数学函数进行带限处理之前对它们进行采样。换句话说,奈奎斯特频率以上的所有谐波都将被混叠回可听范围。当将其中一个波形扫到高频时,这一点最为明显。混叠的谐波将上下移动频谱,在背景中发出“收音机调谐”的声音。一种更好的方法是使用加法合成或类似 MinBLEPs 的方法来生成音频波形。一个正确带限的波形在接近不连续点时将有“锯齿”,而不是分段直线。)
方波的构建方式与正弦波非常相似,我们使用相同的方法,通过一个具有相位变量的模式循环,并在超过 弧度后重置。
Input: Peak amplitude (A), Frequency (f) Output: Amplitude value (y) if phase < pi then y = A else y = -A phase = phase + ((2 * pi * f) / samplerate) if phase > (2 * pi) then phase = phase - (2 * pi)
很明显,它不依赖于外部函数,方波可以通过简单的算术来定义,因为它本质上是在每个周期内在两个值之间切换。可以通过引入一个新的变量来扩展它,该变量控制在周期中初始值切换到其符号值的点;这种波形被称为 *脉冲波*。脉冲波与方波类似,但能够调制切换点提供了更大的声音潜力。
锯齿波
[edit | edit source]锯齿波的声音更像方波,尽管它具有谐波衰减和适当的“嗡嗡”音色。它由对角线斜线段构成,因此算法中需要直线梯度方程。数学形式为
其中 代表振幅, 是相位。这可以并入算法形式,如下所示
Input: Peak amplitude (A), Frequency (f) Output: Amplitude value (y) y = A - (A / pi * phase) phase = phase + ((2 * pi * f) / samplerate) if phase > (2 * pi) then phase = phase - (2 * pi)
三角波
[edit | edit source]三角波与锯齿波有许多几何相似之处,只是它有两个斜线段。代数略微复杂,程序员可能希望考虑将直线生成合并到一个新函数中,以方便阅读。三角波仅包含基频的奇数谐波,并且比方波或锯齿波具有更柔和的音色,更接近正弦波。两个线段的数学形式为
对于 到 弧度
对于 到 弧度
然后,该算法与之前的示例类似,但将梯度方程合并到其中。在本示例中提供的算法中,很明显可以设计出各种不同的波形,但也要认识到这些形状只能描述为数学函数。由于更复杂的数学表达式,复杂形状可能会变得非常费力,因为需要更大的处理能力。
Input: Peak amplitude (A), Frequency (f) Output: Amplitude value (y) if phase < pi then y = -A + (2 * A / pi) * phase else y = 3A - (2 * A / pi) * phase phase = phase + ((2 * pi * f) / samplerate) if phase > (2 * pi) then phase = phase - (2 * pi)
波表
[edit | edit source]可能存在一种情况,或者人们可能希望摆脱使用数学公式或线段定义振荡波形的限制或复杂性。如前所述,这可能是对处理能力的担忧,或者仅仅是通过直观的图形界面指定形状会更容易。在这样的情况下,音乐家和工程师可能会使用波表作为他们的源振荡器。波表在数字合成应用中很受欢迎,因为访问内存块在计算上比使用数学运算计算值更快。
波表本质上是一个包含N个值的数组,值 1 到 N 代表振荡器的一个完整周期。每个值代表周期中某个点的幅度。波表通常以图形方式显示,用户可以选择绘制他们所需的波形,因此它代表了一个非常强大的工具。还可以加载预先录制好的波形;但请注意,波表振荡器只是一个波形周期的参考表,它与采样器不同。波表与其关联的读指针一起,以所需的速率循环遍历表,并按顺序输出每个幅度值,以便将波形重新创建为数字值的流。当指针到达表数组中的最后一个值时,它将重置为指向 1 并开始一个新的周期。
使用波表
[edit | edit source]波表的大小和系统的采样率决定了波表振荡器的基频。如果我们有一个包含1024个单独值的波表,并且采样率为44.1 kHz,则需要
秒才能完成一个周期。如前所示,频率可以从 中确定,从而得出基频为
因此,很明显,为了改变振荡器的频率,我们必须改变波表的大小或系统的采样率。这两种方法都存在一些实际问题。
- 改变波表大小意味着切换到具有相同更新波形的不同大小的波表。这将需要数十个、数百个甚至数千个单独的波表,每个音调一个,这显然是完全低效且占用内存的。
- 数字系统,尤其是将多个合成或录制信号组合在一起的混音器,被设计为以固定的采样率工作,突然改变它再次效率低下并且非常难以编程。
- 以可接受的精度播放高频所需的采样率变得非常高,对系统提出了很高的要求。
最实用且使用最广泛的以不同频率播放波表振荡器的方法之一是改变读指针遍历表的“步长”大小。与之前的示例一样,我们的 1024 值波表在输出表中的每个点时具有 43.5 Hz 的基频。现在,如果我们每隔5个值遍历表,我们将拥有
由此可以得出用于计算给定频率f所需的步长大小S的通用公式
其中N是波表的大小,而 是采样率。需要注意的是,由于步长正在改变,读指针可能不会准确地落在最后一个表值N上,因此它必须像之前部分中函数生成的波形一样“环绕”。这可以通过从当前指针值中减去表的大小来实现,如果它超过 N;其算法形式可以很容易地从上面的示例中获得。
频率精度和插值
[edit | edit source]我们必须考虑到,某些频率值可能会产生具有小数部分的步长;也就是说,它不是整数,而是一个有理数。在这种情况下,我们发现读指针将尝试步进到波表数组中不存在的位置,因为数组的每个成员都具有整数值索引。位置 50 可能存在一个值,但位置 50.5 呢?如果我们希望播放使用小数步长的频率,我们必须考虑如何适应它。
- 截断和舍入。通过删除小数点后的分数,我们将步长缩减为整数——这就是截断。例如,1.3 变为 1,4.98 变为 4。舍入类似,但选择最接近的整数——3.49 变为 3,8.67 变为 9。对于简单的舍入,如果小数点后的值小于 5,我们向下舍入(截断),否则我们向上舍入到下一个整数。舍入可以在处理器中免费支持,或者可以通过将 0.5 加到原始值然后截断为整数来完成。对于波表合成,截断和舍入之间的唯一区别是输出中恒定的 0.5 个样本相位偏移。由于这不可检测——既不是改进也不是损害——截断和舍入之间的决定归结为哪一个更方便或更快。
- 线性插值。这是在步长位置周围的两个整数值之间绘制一条直线,并使用这两个点的值生成在它们之间插值的振幅值的方法。这是一个计算量更大的过程,但会带来更高的精度。
- 高阶插值。将线性插值视为一阶插值(并将截断和舍入视为零阶插值),存在许多常用的高阶形式——三次埃尔米特、拉格朗日等等。正如线性插值需要两个点进行计算一样,高阶需要更多的波表点用于计算,但会产生更准确、更低失真的结果。Sinc 插值可以任意接近完美,但以计算时间为代价。
通过增加波表的大小,上述过程的精度会更高,并将更接近理想的、预期的曲线。当然,更大的波表大小会导致更大的内存需求。一些波表合成器硬件设计更喜欢 2 的幂的表大小(128、256、512、1024、2048 等),因为利用了数字内存构建方式(二进制)的捷径。