如何创建可以在频率之间平稳过渡的正弦波发生器


27

我能够编写一个用于音频的基本正弦波发生器,但是我希望它能够从一个频率平稳过渡到另一个频率。如果我只是停止产生一个频率并立即切换到另一个频率,则信号将出现中断,并且会听到“喀哒”声。

我的问题是,有什么好的算法可以产生一个始于例如250Hz的波,然后过渡到300Hz,而不会引起任何喀哒声。如果算法包括可选的滑行/滑音时间,那就更好了。

我可以想到一些可能的方法,例如过采样后跟低通滤波器,或者使用波表,但是我确信这是一个足够普遍的问题,因此有一种标准的解决方法。


2
您为什么不只在过渡期内使用线性频率过渡。例如,您需要从时间t0的频率f0过渡到时间t1的频率f1,那么为什么不引入转换频率f(t)= f0 *(1-q)+ f1 * q,其中q =(t -t0)/(t1-t0),然后产生信号A(t)= sin(2 * Pi * f(t)* t)?
mbaitoff 2012年

Answers:


24

我过去使用的一种方法是维护一个相位累加器,该累加器用作波形查找表的索引。在每个采样间隔处将相位增量值添加到累加器:

phase_index += phase_delta

要更改频率,您可以更改每个采样点添加到相位累加器的相位增量,例如

phase_delta = N * f / Fs

哪里:

phase_delta is the number of LUT samples to increment
freq is the desired output frequency
Fs is the sample rate

这样即使在动态更改phase_delta(例如,频率变化,FM等)的情况下,也可以确保输出波形是连续的。

为了使频率变化(滑音)更平滑,您可以在适当数量的采样间隔内将phase_delta值在其旧值和新值之间倾斜,而不仅仅是立即更改它。

请注意,phase_indexphase_delta都具有整数和小数部分,即它们必须是浮点或定点。phase_index(模数表大小)的整数部分用作波形LUT的索引,并且小数部分可以可选地用于相邻LUT值之间的插值,以获得更高质量的输出和/或更小的LUT大小。


谢谢,我希望答案可能涉及LUT。我正在考虑使用一个包含一个1Hz波形(即Fs条目)的LUT。是否有经验法则来控制LUT的最佳尺寸?

4
它取决于多种因素:您要寻找的SNR,是纯正弦波还是更复杂的波形,是否打算在相邻LUT条目之间进行插值或只是截断等等。这还取决于您是否要拥有一个象限表并自行处理索引算法和符号反转,或者拥有一个完整的四个象限表。我个人将以一个1024点(NB:2 ^ N适用于模索引)开始,没有内插的四个象限表,因为这非常简单,并且对于16位“消费者”音频应能提供良好的结果。
Paul R

1
好答案,保罗。关于该主题还有一个类似的问题。更多信息总是有帮助的。
詹森R

4
考察这种方法的另一种方法是模拟压控振荡器(VCO)。VCO的输出频率取决于输入电压(通常是输入电压的线性函数),但是即使输入电压瞬时切换,输出信号也具有连续的相位。输出是,其中φ 是一个连续
ϕŤ=0Ťω0+ķXτdτ
ϕŤ的时间的函数,而输出频率的相位的导数,并且等于,其中ω 0是静止频率。
ω0+ķXŤ
ω0
Dilip Sarwate

1
我有同样的问题,感谢累加器的想法(我使用的是直接计算,由于近似值而无法使用):jsfiddle.net/sebpiq/p3ND5/12
sebpiq 2012年

12

产生正弦波的最佳方法之一是使用具有复数递归更新的复数相量。即

ž[ñ+1个]=ž[ñ]Ω

其中,z [n]是相量,,用ω为以弧度为单位的振荡器和的角频率Ñ样本索引。z [ n ]的实部和虚部均为正弦波,它们的相位相差90度。如果同时需要正弦和余弦,则非常方便。单个样本计算仅需要4的倍数和4的加法,比包含sin()cos()或查找表的任何内容便宜很多。潜在的问题是,由于数值精度问题,振幅可能会随时间漂移。但是,有一个相当简单的方法可以修复该问题。假设z [ nΩ=经验值Ĵωωñž[ñ]。我们知道 z [ n ]应该具有单位大小,即 ž[ñ]=一种+Ĵbž[ñ]

一种一种+bb=1个

因此,我们仍然可以每隔一段时间检查一次,并进行相应的更正。确切的更正是

ž[ñ]=ž[ñ]一种一种+bb

这是一个尴尬的计算,但由于是非常接近的团结可以近似的1 / 一种一种+bb与泰勒展开周围条款X=1,我们得到1个/XX=1个

1个X3-X2

因此校正简化为

ž[ñ]=ž[ñ]3-一种2-b22

每隔几百个样本进行一次简单的校正将使振荡器永远保持稳定。

为了连续改变频率,需要相应地更新乘数W。即使乘法器不连续地变化,也将保持连续的振荡器功能。如果需要频率斜坡上升,则可以将更新细分为几个步骤,也可以使用相同的振荡器算法来更新乘法器本身(因为它也是单位增益复数相量)。


感谢您提供此答案,这可能会让我花一点时间来理解足够多的知识,从而可以将其转换为现实世界中的代码,但这似乎是一个有趣的尝试。
Mark Heath

2
我实现了在golang该解决方案以供参考:github.com/rmichela/Acoustico/blob/...
瑞安MICHELA

不幸的是,这是一个很好的解决方案,只有在使用固定时基的情况下,它才能很好地工作。如果不是,则需要计算正弦和余弦以计算正确的复数旋转。
卡梅隆·塔克林德

2

从此站点

为了创建从一个频率到另一个频率或从一个振幅到另一个振幅的平滑过渡,必须使用附加部分修改不完整的正弦波,以便在while循环的每次迭代之后生成的波在x轴处结束。

听起来应该可行。

(实际上,如果它们在过渡时都在x轴上同步,则我认为没有必要进行逐步过渡。)


1
ω00ω1个0

2

我同意使用相位累加器的先前建议。本质上,控制输入是每步或每个时钟周期(或每个中断等)的相位超前量,因此更改该值可以更改频率而不会出现相位不连续的情况。然后,通过LUT或仅通过sinθ或cosθ的计算,从累积的相位值中确定波幅。

这实际上就是通常所说的数控振荡器(NCO)或直接数字合成器(DDS)。用这些术语进行网络搜索可能会产生比您想了解的更好的理论和实践知识。

如果需要的话,通过控制相位超前值的变化率,添加一个附加的累加器也可以允许在频率之间进行无缝转换,如您所建议的那样。有时称为数字差分分析仪或DDA。


很好的附加信息。埃里克,很高兴在这里见到你。我们可以使用算法部长。
詹森·R

1

一阶,您应该调整新频率正弦波的起始相位,使其与第一转换样本点上前一个正弦波的相位相同。计算第一个频率,并将其相位用于第二个频率。

第二个选择可能是在几个样本上将d_phase从一个频率的d_phase斜坡过渡到下一个频率。这将清理一阶导数的连续性并提供滑动。

第三种选择可能是在d_phase斜率上使用平滑窗口,例如余弦。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.