fifo算法_混响算法和原理

a57c1e2a70a4e14df5f3ff79f618974c.png

1.引言

a、什么是混响

混响主要用于唱卡拉OK,增加话筒声音的延时,产生适量的回声,使唱歌的声音更圆润更优美,歌声不那么“干”。

08d22cf40884f4d4029dc7931d7fe68e.png

什么是回声

回声:在一个方向的延迟反射

混响:在多个方向的多次延迟反射

b、混响算法的发展史

硬件混响设备

一般原理就是现场采集ir(impulse response) 如练声房,录音棚,音乐厅等

898fecd99445d5e5da813b3e0207fb91.png

软件混响

发展到后期,因为数字信号处理的发展,和可编程门阵列芯片产生,就习惯将提前采集好的ir通过算法做卷积运算。

算法混响就此产生,后期又通过很多人员的努力产生了房间声学的模拟算法。

所以在软件混响里面,基本有两种一种是ir,另一种房间声学的模拟算法。

我们能看到的像freeverb3这两种都有,而sox等其他一般只看到后面一种。目前能看到的如下:

回声类:echo ,echos

ir类:model1 model2 model3

schroeder类: 简易schroeder,复杂schroeder,schroeder优化版moorer

2、混响算法及推导

a、观察房间声音的模型

692b2c4719c82b1ca5575b148027e9da.png

我们看下上面的图,假设我们在一个房间里,说话者和听者所在的空间声音应该是这个样子的如图。听者听到的包括直达的信号外还有很多反射声音。这里我们关注两点一个是房间的大小,另外一个是反射强度

当如果这个房间非常大,那么基本上听不到反射。

同样的道理,一个房子的材质会影响反射强度,如金属和木质两种不同材质做的房子,反射效果显然是不一样的。

b、建立初步数学模型。

假设说话者说出的信号是x[n]

听者某时刻接收到的信号是y[n],那么y[n]包含那些内容呢?

y[n] 应该是 x[n] + 反射1 + 反射2 .......

8f08c813a670033ae1f8430c9061edf6.png

反射怎么表示,它应该是x[n] 的延时我们假设延时m ,难么反射1 应该是 x[n -m] ,但是我们还应该考虑反射时的衰减,就是上面所说的房子的反射效果,假设是a,则反射1 应该表示成 x[n -m]*a;

所以,y[n] = x[n] + a*x[n -m] + a^2*x[n - 2m] + a^3*x[n - 3m]......

380af908da6726d313d017f3f880689f.png

求和我们简化下,利用差分或者z变化得到差分方程:

y[n] = ay[n - m] + x[n];

03e579c18a7fd54c3793831ea76635af.png

这是个梳妆滤波器

3a14109259af4239308bcfd344068ef4.png

它的频谱特性

7b2a94fced861e2c2ac467419c65ecca.png

由此我们就可以根据这个公式写出自己的算法。按道理这样应该就可以了,

但是这个算法存在,回声密度比较低的问题会出现金属质感,听起来不舒服,不自然。

c、对初步数学模型的优化-----全通

怎么办呢?怎么去解决这种梳状滤波器带来的频谱不平坦,和回声密度不够,一些做信号处理的专家立马想到了,使用全通系统去改善它。

因为我们知道全通系统,有很好的群延时,所以可以增加回声的密度。全通表达式如下:

y[n] = -gx[n] + x[n-m] +gy[n-m];

它的频谱特性

e28d9df3195d8abaeadaf96249c4d287.png

它的幅度谱 和 相位谱

ebbc65ab3bda60ad45d9df3e9bca5d2c.png

他的零极点都是单位元对称的。

频谱平坦

相位延迟

d、对初步数学模型的优化-----Schroeder混合模型的提出

这样增加了全通以后好多,但是还是不够。这个时候有个叫Schroeder的人提出了一个很吊的方案。称之为Schroeder混响模型。

f3ae7c9abbae3bb142de5228a2501251.png

这种模型得到很好的工业生产。

e、对初步数学模型的再优化-----Moorer对Schroeder的优化

但是总有更吊的人在后面。

Moorer对Schroeder的混响提出三点点改善意见,

第一:需要考虑信号在空间传播高频衰减因素,加入低通滤波器使高频衰减

第二:后面的全通应该嵌套使用,这样会有更好的群延时,

第三:可以增加前级反馈,形成早反射,增加混响效果。

下面是框图。下一章我们将会对这个原理一步步对应分析算法的实现。

198a9a72acf1579ff9584087814ce6c4.png

从梳状滤波器一直到最终的模型,进过很多人的努力。还是很不容易。

现在各个手机或者其他混响设备上用基本都是这种模型,当然有些软件它自己还会增加一些自己的eq,

我们最终看下界面结构,

如下:

444e033a352ed8717cc597a143de7f10.png

app软件:

ddf7326310fffb57e3bca561885b096e.png

各大软件如唱吧一般,不会直接给那么多参数给你调。考虑用户的体验,一般会给出一些选项。什么大厅,礼堂,演唱会等等。

3838e2b330070c8631b0d65db3fa5d83.png

声音或者Audiacity展示。

3、算法的实现

了解了上面的原理后,算法的实现就变得简单易懂了。

当然我们崇尚拿来主义sox

混响算法,在诸多开源中都有实现,例如sox,freeverb,Tonic等等开源框架,都有。我们今天拿sox的进行分析。

下载sox后直接在/src/reverb.c就可以看到完整的混响算法。我们根据上面讲的一部部分析。

这个c文件和h文件很长,我们抽出需要的来看。

梳状滤波

static 

comb_process 函数就是梳状滤波器的结构,其实

*p->ptr = *input + p->store * *feedback;

这个表达是就是y[n] = ay[n - m] + x[n]; 而上面的一局话是一个标准的低通滤波器。

这个低通滤波器就moorer提出的需要对高频部分进行衰减。

p->store = output + (p->store - output) * *hf_damping;

y[n] -gy[n] = (1-g)x[n]

并且如下在主函数中进行了 7 次的do while 并联。

size_t 

全通滤波

static 

allpass_process 就是上章描述的全通,而*p->ptr = *input + output * .5 和 output - *input;这句话并非标准的全通模型,其H(z)如下

9b7f7e2ad96fe464dabb455fb4e3b883.png

差分方程如下

y[n] = -x[n] + (1+g)x[n-m] + gy[n - m]

刚好对应这两句代码,另外allpass_process的input是指针形式它传进来的是后面自己return的内容,这样就形成了一个的输出给到下一个输入,形成moorer提出的嵌套。

代码如下

i = array_length(allpass_lengths) - 1; do out = allpass_process(p->allpass + i, &out); while (i--);

早反射

根据moorer的说法,早反射对声音效果是很重要的,在sox中它使用fifo队列来组织数据流,当没有满足早反射时间时fifo填充的是0.代码如下

static 

如上代码,将需要延迟的参数pre_delay_ms (单位ms),转换成采样个数,在给到fifo_write进行写0操作。

有时间看看我们自己简化了以后的。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FIFO算法和LRU算法都是为了解决缓存淘汰问题而设计的。FIFO算法是一种按照元素插入顺序排序的数据结构,元素在数据结构中按照先进先出的顺序进行存储和访问。在实现缓存时,可以根据实际情况选择使用FIFO算法或LRU算法,以达到最佳的缓存淘汰效果。 LRU(Least Recently Used)算法是一种常见的缓存淘汰算法,它的基本思想是“最近最少使用”。在缓存中,最近使用的数据会被优先淘汰,而最近未使用的数据则需要被淘汰以腾出缓存空间。为了实现这一功能,LRU算法通过定期淘汰最近最少使用的数据来保持缓存的最佳状态。LRU算法的核心思想是使用一个缓存数组,数组中的每个元素都存储了一个键值对。每个键值对表示缓存中的一个数据项,包含数据项的键和值。 FIFO算法和LRU算法的区别在于,FIFO算法是按照元素插入顺序排序的,而LRU算法是按照元素最近使用的时间排序的。因此,当缓存中的数据项被频繁访问时,LRU算法的效果会更好,因为它会优先保留最近使用的数据项,而FIFO算法则无法做到这一点。 以下是一个使用Python实现LRU算法的例子: ```python from collections import OrderedDict class LRUCache: def __init__(self, capacity: int): self.cache = OrderedDict() self.capacity = capacity def get(self, key: int) -> int: if key not in self.cache: return -1 self.cache.move_to_end(key) return self.cache[key] def put(self, key: int, value: int) -> None: if key in self.cache: self.cache.move_to_end(key) self.cache[key] = value if len(self.cache) > self.capacity: self.cache.popitem(last=False) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值