Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/arduino/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 如何用karplus强算法实现插值延迟线和全通滤波器?_C_Filter_Signal Processing_Delay_Sound Synthesis - Fatal编程技术网

C 如何用karplus强算法实现插值延迟线和全通滤波器?

C 如何用karplus强算法实现插值延迟线和全通滤波器?,c,filter,signal-processing,delay,sound-synthesis,C,Filter,Signal Processing,Delay,Sound Synthesis,好的,我已经用C实现了karplus strong算法。这是一个模拟拨弦声音的简单算法。从长度为n(n=采样频率/所需频率)的环形缓冲区开始,将其通过简单的两点平均滤波器y[n]=(x[n]+x[n-1])/2,输出它,然后将其反馈回延迟线。冲洗并重复。这会随着时间的推移平滑噪音,以创建自然的拨弦声音 但我注意到,对于整数延迟线长度,可以将几个高音与相同的延迟长度相匹配。此外,整数延迟长度不允许平滑变化的音高(如颤音或滑音),我读过几篇关于karplus算法扩展的论文,他们都谈到使用插值延迟线进

好的,我已经用C实现了karplus strong算法。这是一个模拟拨弦声音的简单算法。从长度为n(n=采样频率/所需频率)的环形缓冲区开始,将其通过简单的两点平均滤波器y[n]=(x[n]+x[n-1])/2,输出它,然后将其反馈回延迟线。冲洗并重复。这会随着时间的推移平滑噪音,以创建自然的拨弦声音

但我注意到,对于整数延迟线长度,可以将几个高音与相同的延迟长度相匹配。此外,整数延迟长度不允许平滑变化的音高(如颤音或滑音),我读过几篇关于karplus算法扩展的论文,他们都谈到使用插值延迟线进行分数延迟或使用全通滤波器



我以前实现过插值延迟线,但仅在波形缓冲区不变的波形表上实现。我只是以不同的速度一步一步地经历了延迟。但让我困惑的是,当谈到KS算法时,论文似乎在谈论改变延迟长度,而不仅仅是我通过它的速度。ks算法使事情复杂化,因为我应该不断地将值反馈回延迟线

那么,我将如何着手实施这一点呢?我是把插值反馈回去还是怎么做?我是否完全摆脱了两点平均低通滤波器


全通滤波器是如何工作的?我应该用全通滤波器替换2点平均滤波器吗?使用线性插值法或全通滤波器法,我将如何使用glissando在遥远的音高之间滑动?

数字信号处理算法通常被表示为方框图,这是一种很好的思考方法。在对它们进行编码时,请将每个数据块视为一个具有固定输入和输出的独立单元。我认为你的一些问题来自于试图过早地将系统的各种要素结合起来

这是Karplus Strong的框图

对于延迟块,您需要实现一个分数延迟线。这将包括它自己的低通滤波器,但这是如何实现延迟线的细节。Karplus强效应还需要一个低通滤波器。这些过滤器的特性将有所不同。不要试图合并。顺便说一句,您选择的平均低通滤波器的频率响应很差,会引入“梳状滤波器”效应。您可能需要设计更复杂的FIR或IIR滤波器

那么,我将如何着手实施这一点呢?我是把插值反馈回去还是怎么做?我是否完全摆脱了两点平均低通滤波器

正如方框图所示,您将插值、求和样本反馈回延迟线。在某些情况下,这可能会增加系统的净增益,如果您担心的是这样的话,您可能需要“正常化”延迟的输出,以便它不会失控

实现分数延迟线有很多有效的策略,包括您提到的插值和全通滤波。这样做的想法是,您需要将
read
write
索引保存到延迟行中。延迟线的长度不是内存缓冲区的总长度,而是索引之间的差值乘以延迟线的总长度。使延迟线尽可能大,不要担心调整它的大小

我发现将读写作为一个自由运行的计数器来处理是最方便的,因为这样一来,计数器就不会自动循环或过期

current_delay_length = (write - read) % total_delay_length
current_read_sample = delay_line[read % total_delay_length]
其中,
%
是模数。如果写入和读取计数器是浮点值或设置为定点,则它们也可以包含小数长度。在任何情况下,这使得修改延迟线的长度变得容易。确保实施最小延迟(写入>读取)非常重要

信不信由你,你会改变延迟线的长度,就像固定长度的缓冲器一样,通过改变通过延迟线的速率来改变延迟线的长度。通常,您将稍微调整读取索引。它不应该落在写指针后面超过缓冲区长度,也不应该超前于写指针,否则会出现小故障。但是您可以在写指针之后自由地将读指针移动到任何位置。改变调制方式会得到不同的效果


我强调,像glissando这样的效果来自于延迟线的读写索引是如何操作的,而不是如何实现的。您将从全通滤波器或线性插值延迟线获得类似的声音。例如,更好的分数延迟线将减少混叠噪声,并支持更快速地更改读取指针。

我实现了三种变体,它们都有各自的优缺点,但没有一种像我希望的那样完美。也许有人有更好的算法,想在这里分享

一般来说,我是按照jbarlow描述的那样做的。我使用的环形缓冲区长度为2^x,其中x“足够大”,例如12,这意味着最大延迟长度为2^12=4096个样本,如果渲染为48kHz,这是~12Hz作为最低基频。 二次幂的原因是模可以按位进行,这比实际模便宜得多

// init
int writepointer = 0;

// loop:
writepointer = (writepointer+1) & 0xFFF;
writepointer保持简单,例如从0开始,每次输出样本的增量始终为1

读取指针以一个相对于写入指针的增量开始,每次频率改变时新计算

// init
float delta = samplingrate/frequency;
int readpointer = (writepointer-(int)delta)-1) & 0xFFF;
float frac = delta-(int)delta;
weight_a = frac;
weight_b = (1.0-frac);

// loop:
readpointer = (readpointer + 1) & 0xFFF;
它也会增加1,但通常介于两个整数位置之间。我们使用向下取整的位置存储在整数readpointer中。此样本和下一个样本之间的重量为重量_a