我能够编写一个用于音频的基本正弦波发生器,但是我希望它能够从一个频率平稳过渡到另一个频率。如果我只停止生成一个频率并立即切换到另一个频率,则信号将出现中断,并且会听到“喀哒”声。它开始于例如250Hz,然后过渡到300Hz,而没有引入任何喀嗒声。如果该算法包括可选的滑行/滑音时间,那就更好了。

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

评论

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

#1 楼

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

phase_index += phase_delta


其中:

phase_delta = N * f / Fs


即使更改phase_delta,这也可以确保输出波形是连续的动态地,例如用于频率变化,FM等。

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

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

评论


$ \ begingroup $
谢谢,我希望答案可能涉及LUT。我当时正在考虑使用一个包含一个1Hz波形(即Fs条目)的LUT。是否有经验法则来控制LUT的最佳尺寸?
$ \ endgroup $
–马克·希思(Mark Heath)
2011-12-16 10:13

$ \ begingroup $
它取决于多种因素:您要寻找的SNR,是纯正弦波还是更复杂的波形,是否打算在相邻LUT项之间进行插值或只是截断等等。这还取决于您是否只会有一个象限表并自己处理索引算法和符号反转,或者有一个完整的四个象限表。我个人将以1024点开始(NB:2 ^ N对于模索引),没有内插的四象限表,因为这非常简单,并且应该给出良好的结果,例如16位“消费者”音频。
$ \ endgroup $
– Paul R
2011-12-16 10:42

$ \ begingroup $
保罗,好的答案。关于该主题还有一个类似的问题。更多信息总是有帮助的。
$ \ endgroup $
–Jason R
2011-12-16 15:01

$ \ begingroup $
查看此方法的另一种方法是对压控振荡器(VCO)的仿真。 VCO的输出频率取决于输入电压(通常是输入电压的线性函数),但是即使输入电压瞬时切换,输出信号也具有连续的相位。输出为$$ \ sin(\ phi(t))= \ sin \ left(\ int_0 ^ {t} \ omega_0 + k \ cdot x(\ tau)\ mathrm d \ tau \ right)$$其中$ \ phi(t)$是时间的连续函数,而输出频率是相位的导数,等于$$ \ omega_0 + k \ cdot x(t)$$,其中$ \ omega_0 $是静态频率。
$ \ endgroup $
– Dilip Sarwate
2011-12-16 16:54

$ \ begingroup $
我有同样的问题,感谢累加器的想法(我使用的是直接计算,由于近似而无法使用):jsfiddle.net/sebpiq/p3ND5/12
$ \ endgroup $
–sebpiq
2012年3月31日18:36

#2 楼

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

$$ z [n + 1] = z [n] \ Omega $$

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

$$ a \ cdot a + b \ cdot b = 1 $$

所以我们可以不时检查一下是否仍然存在,并进行相应的更正。确切的更正为

$$ z'[n] = \ frac {z [n]} {\ sqrt {a \ cdot a + b \ cdot b}} $$

这是一个尴尬的计算,但是由于$ a \ cdot a + b \ cdot b $非常接近于单位,因此您可以近似$ 1 / \ sqrt {x} $项,并在$ x = 1 $附近进行泰勒展开然后我们得到

$$ \ frac {1} {\ sqrt {x}} \ cong \ frac {3-x} {2} $$

简化为

$$ z'[n] = z [n] \ frac {3-a ^ 2-b ^ 2} {2} $$

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

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

评论


$ \ begingroup $
感谢您的回答,可能会花一些时间让我了解足够多的知识,从而可以将其转换为现实世界的代码,但这似乎是一个有趣的尝试。
$ \ endgroup $
–马克·希思(Mark Heath)
2012年1月6日18:54

$ \ begingroup $
我在golang中实现了该解决方案以供参考:github.com/rmichela/Acoustico/blob/…
$ \ endgroup $
–瑞安·米歇拉(Ryan Michela)
17 Mar 12 '17 at 8:12

$ \ begingroup $
这是一个很好的解决方案,不幸的是,只有在使用固定时基的情况下,它才能很好地工作。如果不是,则需要计算正弦和余弦以计算正确的复数旋转。
$ \ endgroup $
– Cameron Tacklind
19年11月16日在21:51

$ \ begingroup $
一个美丽的解决方案和解释肯定值得更多的赞扬。我想知道是否有人知道实现此振荡器的任何C / C ++库?我发现的所有库都使用波表或其他直接正弦近似。
$ \ endgroup $
–西罗林顿
19/12/9在19:38



$ \ begingroup $
仍然对实现此目标的库感兴趣。对于其他人可能有用的是,有一种与之密切相关的数值稳定的方法被称为“修改的耦合形式”,该方法由Gordon,1995年描述(一种用于VLSI应用的正弦生成算法),在此也非常简要地进行了总结: ccrma.stanford.edu/~jos/pasp/Digital_Sinusoid_Generators.html
$ \ endgroup $
–西罗林顿
20年8月26日在1:02



#3 楼

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

本质上,这就是通常所说的数控振荡器(NCO)或直接数字合成器(DDS)。在这些术语上进行网络搜索可能会产生比您想使它们更好地工作的理论和实践更多的信息。

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

评论


$ \ begingroup $
很好的附加信息。埃里克,很高兴在这里见到你。我们可以使用算法部长。
$ \ endgroup $
–Jason R
2011年12月31日下午1:29

#4 楼

来自此站点:


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


听起来应该可以。

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

评论


$ \ begingroup $
这就是说,等待频率为$ \ omega_0 $的当前正弦曲线完成一个周期并到达$ 0 $,然后切换为频率为\\ omega_1 $的另一个正弦曲线。这样可以有效地保持相位的连续性,并且对于音频应用来说可能是可以的,在音频应用中,所需的切换时间(现在)与实现的切换时间(当我的正弦曲线完成一个周期)之间的延迟只有几毫秒或几微秒。但是,这种差异可能会给其他应用程序带来麻烦。只要记住一个正弦曲线在一个周期中是$ 0 $两次,并确保选择正确的一个!
$ \ endgroup $
– Dilip Sarwate
2011-12-17 2:48

#5 楼

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

第二个选项可能是在多个样本上将d_phase从一个频率的d相位倾斜到另一个频率。这将清除一阶导数的连续性并提供滑动。

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