【音效处理】Vibrato 简介

系列文章目录



一、Vibrato 是什么

“vibrato” 一词指的是,在一个音的音高上小的、准周期性的变化。“vibrato” 一词在不同领域有着不同的翻译,例如在小提琴中,“vibrato” 意旨 “揉弦”,是一种小提琴演奏技巧(参考小提琴手腕揉弦教学 | 英文教学中文字幕 【Penny Teaching】);在歌唱圈中,“vibrato” 指的是颤音(参考 最通俗易懂的颤音教学!你学废了吗?)。维基百科 vibrato 词条中同样也举了唱歌小提琴的例子,有兴趣可以听听看,提升对 vibrato 感性的认识。

下图是 440hz 正弦波经过 vibrato 音效后的结果,从频谱上能看到音高在周期性的变化。
sin440-vibrato-output
下面的视频是 Libaa - Vibrato 的效果演示,一个夸张的 Vibrato 会产生相当滑稽的结果:

vibrato

vibrato_video

通过上述的例子,你应该对 vibrato 有了一些基本认识:它一种关于音调变化的音效。


二、Vibrato 原理

2.1 Time-varying delay line

vibrato 实现原理并不复杂,人们通常用一个 delay line 就能够完成 vibrato 算法。请回忆一下之前在【音效处理】Delay/Echo 简介 提到的 Delay 算法,Delay 算法延迟 D D D 个采样,其中 D D D 是一个固定的数字,而在 vibrato 算法中,延迟的时长是一个关于时间 t t t 的函数 D t D_t Dt,也就是说延迟时间是一个不停变化的数字的,我们称为 “Time-varying delay line(可变延迟线)”。

下图为 Vibrato 与 Delay 的块状图,Vibrato 实现中通常用 LFO 来生成 D t D_t Dt,此外 Vibrato 输出只有“湿”信号,没有 feedback 信号;而 Delay 算法中 D D D 是固定的,且输出时对 “干”和“湿”信号做了 mix。
在这里插入图片描述

2.2 多普勒效应

为什么一个随时间变化的延迟 D t D_t Dt 会造成音调的变化呢?背后的原理其实就是多普勒效应。关于多普勒效应的介绍请参考多普勒效应 - 知乎,本文不再过多赘述。

多普勒效应在生活中非常常见,类比到 Vibrato 音效中,你可以这么想象:有一个喇叭播放着音频,你站在原地,喇叭处于运动状态,它时而靠近你时而远离你,做着周期性的运动。当喇叭靠近你时,距离,声音从发出到你耳朵的延迟 D D D 小;当喇叭远离你时,距离,声音从发出到你耳朵的延迟 D D D 大。这意味着 D D D 是一个随时间变化的值,也就是上面提到的 D t D_t Dt多普勒效应造成了音高的变化

接下来讨论多普勒效应与 vibrato 之间的关系。首先,多普勒频移公式如下:
ω l = ω s 1 + v l s c 1 − v s l c (1) \omega_{l}=\omega_{s} \frac{1+\frac{v_{l s}}{c}}{1-\frac{v_{s l}}{c}} \tag{1} ωl=ωs1cvsl1+cvls(1)
其中 ω s \omega_{s} ωs 是声源静止状态下发生音频的频率, ω l \omega_{l} ωl 是 Listener 接受到的频率, v l s v_{l s} vls 表示 Listener 相对于声源方向的传播介质(假设是空气)的速度, v s l v_{s l} vsl表示声源相对于 Listener 方向的传播介质的速度, c c c表示声速。在 多普勒效应(一维匀速运动) 有具体的习题,可以参考参考加深理解。

Vibrato 差分方程为:
y ( t ) = x ( t − D t ) y(t) = x(t - D_t) y(t)=x(tDt)
其中 D t D_t Dt 为单位为秒的时变延迟,在离散实现中, D t D_t Dt 不是一个整数, x ( t − D t ) x(t-D_t) x(tDt) 可以采用插值法等技术来近似到任意的精度(参考 Libaa - DelayLine::getInterpolation 方法)。

简单起见,让我们来研究 x ( t ) x(t) x(t)复正弦信号的情况,此时 x ( t ) x(t) x(t) 可表示为:
x ( t ) = e j ω s t x(t)=e^{j \omega_{s} t} x(t)=ejωst
此时输出信号为:
y ( t ) = x ( t − D t ) = e j ω s ⋅ ( t − D t ) y(t)=x\left(t-D_{t}\right)=e^{j \omega_{s} \cdot\left(t-D_{t}\right)} y(t)=x(tDt)=ejωs(tDt)
该信号的瞬时相位为:
θ ( t ) = ∠ y ( t ) = ω s ⋅ ( t − D t ) \theta(t)=\angle y(t)=\omega_{s} \cdot\left(t-D_{t}\right) θ(t)=y(t)=ωs(tDt)
然后通过微分得出瞬时频率:
ω l = ω s ( 1 − D ˙ t ) (2) \omega_{l}=\omega_{s}\left(1-\dot{D}_{t}\right) \tag{2} ωl=ωs(1D˙t)(2)
其中 ω l \omega_l ωl表示输出频率, D ˙ t ≜ Δ d D t \dot{D}_{t} \triangleq \frac{\Delta}{d} D_{t} D˙tdΔDt 表示延迟 D t D_t Dt 的时间导数。

仔细观察公式(1)和公式(2)发现当 D ˙ t = − v l s c \dot{D}_{t}=-\frac{v_{l s}}{c} D˙t=cvls 时两个公式可以匹配上:
ω l = ω s ( 1 + v l s c ) = ω s 1 + v l s c 1 (3) \omega_{l}=\omega_{s}\left(1+\frac{v_{l s}}{c}\right)=\omega_{s} \frac{1+\frac{v_{l s}}{c}}{1} \tag{3} ωl=ωs(1+cvls)=ωs11+cvls(3)
此时,我们发现时变延迟最自然地模拟了由移动的 Listener 引起的多普勒频移。


三、Vibrato C/C++ 实现

3.1 从 LFO 中得到延迟

开篇时提到 Vibrato 中时变延迟由 LFO 生成,但由于我们的 LFO 生成的信号范围总在 [-1, 1] 之间,而延迟时间 D t D_t Dt 应该是一个正数,且在具体的某个范围内,例如 0ms ~ 10ms 之间,因此我们需要对 LFO 的输出进行进一步的加工,得到 D t D_t Dt

定义 min_delay_ms 为最小延迟,max_delay_ms 为最大延迟,现在我们的问题转换为:将 [-1, 1] 映射到 [min_delay_msmax_delay_ms] 中。这并不复杂,我们假设 min_delay_ms=2max_delay_ms=10,实现的代码为:

float min_delay_ms = 2.0f;
float max_delay_ms = 10.0f;
float delay_half_range = (max_delay_ms - min_delay_ms) / 2.0f; // 4
float middle_point = (min_delay_ms + delay_half_range); // 6

// lfo_output is in range [-1, 1]
float lfo_output = get_lfo_output(); 

// project [-1, 1] to [2, 10] 
float D = lfo_output*delay_half_range + middle_point;

3.2 算法参数

Vibrato 算法有两个可控制的参数:RateDepth

其中 Rate 表示 LFO 的振荡频率,范围通常在 [0.2, 20] 之间,但这个范围只是一个很主观的数字,你可以根据自己需求进行调整。

Depth 范围在 [0, 1] ,它用于控制最大延迟和最小延迟之间的差(你可以想象,sin 信号被压扁了)。当 Depth = 0.5 时,还是以上面的例子为例,延迟范围被压缩到了 [4, 6],伪代码为:

float depath = 0.5f;
// now it is in [-0.5, 0.5]
float lfo_output = get_lfo_output() * 0.5f; 

// project [-0.5, 0.5] to [4, 6] 
float D = lfo_output*delay_half_range + middle_point;

3.3 C/C++ 实现

Vibrato 可以基于 Libaa - Delay 实现,但请注意 Vibrato 没有 feedback 信号,此外它的输出只有“湿”信号。因此 Delay 算法中 feedbackdry 参数都固定为 0。另外我们还引入 LFO 来生成延迟,具体实现请参考 Libaa - Vibrato

四、总结

本文介绍了 Vibrato 音效,它是一种关于音高变化的音效,人们通常使用 Time-varying delay line 来实现 Vibrato,造成其音高变化的背后原理是多普勒效应。Vibrato 算法有 RateDepth 两个参数,分别控制 LFO 频率和延迟范围,并给出了具体的 C/C++ 实现代码 Libaa - Vibrato

五、参考

Vibrato是一种音乐表达技巧,其通过定期改变音高来给音乐带来一种颤抖的效果。在数码音频处理中,Matlab可以用于实现Vibrato效果。 要实现Vibrato效果,首先需要获取音频信号的波形数据。可以使用Matlab中的audioread函数来加载音频文件,并将其转换为数字信号。然后,我们可以使用Matlab提供的延迟效果函数来添加Vibrato效果。 在Matlab中,可以使用delayseq函数来实现延迟效果。通过将原始音频信号与一个周期性变化的延迟信号相加,就可以实现Vibrato效果。延迟信号可以通过正弦函数或三角函数来实现。 以正弦函数为例,我们可以使用如下的代码来实现Vibrato效果: ```matlab % 加载音频文件 [x, Fs] = audioread('audio.wav'); % 设置Vibrato效果参数 vibratoRate = 5; % 频率,单位为Hz vibratoDepth = 0.005; % 深度,表示延迟信号的振幅 % 计算延迟信号 delayTime = vibratoDepth * sin(2*pi*vibratoRate*(0:length(x)-1)/Fs); % 添加延迟效果 y = x + delayseq(x, Fs, delayTime); % 播放处理后的音频 sound(y, Fs); ``` 在这段代码中,我们首先加载音频文件,然后设置Vibrato效果的参数,其中vibratoRate表示Vibrato的频率,vibratoDepth表示Vibrato的深度。接着,通过计算一个正弦函数作为延迟信号,然后利用delayseq函数将延迟信号加到原始音频信号上,得到处理后的音频。最后,使用Matlab中的sound函数播放处理后的音频。 通过以上步骤,我们可以使用Matlab实现Vibrato效果,将原始音频信号添加颤音效果,从而增加音乐的表达力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值