Introduction 开场白

今天要讨论的是 Flanger 和 Chorus 这两个音效,它们也是基于 Delay 实现的,并且在实现和原理上,它们又有很多相似的地方。

我们还是老规矩,先分别介绍这两种音效的具体效果,分析其实现细节,并最后给出两者的异同。

Flanger 镶边音效

首先是 flanger,中文译为“镶边”,原意指的是开盘式录音机的边缘

为了产生 flanger 音效,可以用两个开盘录音机同时播放同一段磁带。如果两个录音机播放时完全一致的,那么输出的音频只是简单的加强了原音频。为了产生 flanger 特效,我们可以轻轻按压其中一个录音机,降低其播放速率从而降低其音调,此外还会导致两个音频在时间上有稍稍差异。当我们松开按压,那么原先音调和时间差异会逐渐消失,同时我们去轻轻按压另一台录音机,这样循环反复,就产生 flanger 音效。

flanger 也被称为 “飞行”音效,它听起来就像呼啸而过的火车,像腾空而起的喷气式飞机,让我们来听一听。可以明显的感觉到 flanger 音效像是有一个飞机在脑袋上盘旋。

原始音频

flanger

说到 flanger 的产生的原理,那我们就不得不提相长干涉(Constructive Interference)相消干涉(Destructive Interference)

简单来说,假设有个 sine 信号,通过 delay 然后与原始音频相加,如果两个信号在相位上完全一致,那么输出音频的幅度是输入的两倍,这就是相长干涉;如果两个信号相位刚好相抵,那么输出静音信号,这就是相消干涉。

通常一个音频具有很多很多频率,经过 delay 并与原始信号相加,那么会导致有些频率相互抵消(频率响应表现为“波谷”状),有些频率相互增强(频率响应上表现为“波峰”状)。“波峰”和“波谷”在 LFO 的作用下,发生移动和变化,从而最终产生了 flanger 音效。可以结合 flanger 在开盘录音机中操作理解这个过程:循环反复轻压录音机其实就是产生周期性 delay 的过程,这个行为与 LFO 一致。

Basic Flanger

这里给出 basic flanger 的差分方程和块状图
y[n]=x[n]+gx[n−M[n]]y[n] = x[n] + gx[n - M[n]] y[n]=x[n]+gx[n−M[n]]

其中

  • ggg 为 feedback,也被称为 depth
  • M[n]M[n]M[n]为延迟,由 LFO 控制

basic flanger 的结构也被称为 前馈梳妆滤波器,因为它的频谱响应像一个梳子一样。通过传递方程可以解释:
Y(z)=X(z)+gz−M[n]X(z)H(z)=Y(z)X(z)=1+gz−M[n]\begin{aligned} Y(z) &= X(z) + gz^{-M[n]}X(z) \\ H(z) &= \frac{Y(z)}{X(z)} = 1 + gz^{-M[n]} \end{aligned} Y(z)H(z)​=X(z)+gz−M[n]X(z)=X(z)Y(z)​=1+gz−M[n]​

令 z=ejωz = e^{j\omega}z=ejω,其中ω∈[0,π]\omega \in [0, \pi]ω∈[0,π],得到幅度响应
H(ejω)=1+ge−jωM[n]∣H(ejω)∣=1+2gcos⁡(ωM[n])+g2\begin{aligned} H(e^{j\omega}) &= 1+ge^{-j\omega M[n]} \\ \vert H(e^{j\omega}) \vert &= \sqrt{1+2g\cos(\omega M[n]) + g^2} \end{aligned} H(ejω)∣H(ejω)∣​=1+ge−jωM[n]=1+2gcos(ωM[n])+g2​​

可以看到幅度响应 ∣H(ejω)∣\vert H(e^{j\omega}) \vert∣H(ejω)∣ 是 cos 周期函数,在 ω∈[0,π]\omega \in [0, \pi]ω∈[0,π] 下有 M/2M/2M/2 个 “波峰"和"波谷”

"波峰"的位置在:
ωp=2πp/M\omega_p = 2\pi p/M ωp​=2πp/M

”波谷“的位置在:
ωn=(2n+1)π/M\omega_n = (2n+1)\pi/M ωn​=(2n+1)π/M

“波峰"与"波峰”、"波谷"与"波谷"之间的频率间隔是 fs/Mf_s/Mfs​/M

因此幅度响应看起来就像一个梳子一样,例如下面这张 M[n]=6M[n]=6M[n]=6 的幅度响应:

当 M[n]M[n]M[n] 由 LFO 控制着不断变化时,“波峰”和“波谷”不断发生变化,这就产生了 flanger 音效

Flanger with Feedback 反馈Flanger

根据带反馈的 flanger 的块状图,我们可以写出其差分方程为:
y[n]=x[n]+gffd[n]whered[n]=x[n−M]+gfbd[n−M]y[n] = x[n] + g_{ff}d[n] \quad \text{where} \quad d[n] = x[n-M] + g_{fb}d[n-M] y[n]=x[n]+gff​d[n]whered[n]=x[n−M]+gfb​d[n−M]
转换为只与 y[n]y[n]y[n] 和 x[n]x[n]x[n] 相关的差分方程:
y[n−M]=x[n−M]+gffd[n−M]d[n]=x[n−M]+gfbgff(y[n−M]−x[n−M])y[n]=x[n]+gfby[n−M]+(gff−gfb)x[n−M]\begin{aligned} y[n-M] &= x[n-M] + g_{ff}d[n-M] \\ d[n] &= x[n-M] + \frac{g_{fb}}{g_{ff}}(y[n-M] - x[n-M]) \\ y[n] &= x[n] + g_{fb}y[n-M] + (g_{ff}-g_{fb})x[n-M] \end{aligned} y[n−M]d[n]y[n]​=x[n−M]+gff​d[n−M]=x[n−M]+gff​gfb​​(y[n−M]−x[n−M])=x[n]+gfb​y[n−M]+(gff​−gfb​)x[n−M]​

其传递方程为:
H(z)=Y(z)X(z)=zM+gff−gfbzM−gfbH(z) = \frac{Y(z)}{X(z)} = \frac{z^M+g_{ff}-g_{fb}}{z^{M}-g_{fb}} H(z)=X(z)Y(z)​=zM−gfb​zM+gff​−gfb​​

可以看到,当 gfb=0g_{fb}=0gfb​=0 时,其传递方程与 basic flanger 一致。当 gfb<1g_{fb} < 1gfb​<1 时所有极点在单位圆内,是稳定的。

接下来直接上具体实现代码

void Flanger::processBlock(AudioBuffer<float> &buffer)
{const int num_channels = buffer.getNumChannels();const int num_samples = buffer.getNumSamples();float phase = 0.0f;float channel0EndPhase = phase_;assert(static_cast<size_t>(num_channels) <= dlines_.getNumLines());for(int c = 0; c < num_channels; ++c){phase = phase_;if(stereo != 0 && c != 0){phase = fmodf(phase + 0.25f, 1.0f);}float* channel_data = buffer.getWritePointer(c);auto* dline = dlines_.getDelayLine(c);for(int i = 0; i < num_samples; ++i){const float in = channel_data[i];// get delay from lfofloat delay_second = min_delay + sweep_width*lfo_.lfo(phase, LFO::WaveformType::kWaveformSine);float delay_sample = delay_second * static_cast<float>(getSampleRate());// get interpolation delay valuefloat interpolation_val = dline->getInterpolation(delay_sample);channel_data[i] = in + depth * interpolation_val;// push input to delay linedline->push(in + (interpolation_val * feedback));// update phasephase += lfo_freq*invert_sample_rate_;if(phase >= 1.0f){phase -= 1.0f;}}// use channel 0 only keep the phase in sync between call processBlock()if(c == 0){channel0EndPhase = phase;}}phase_ = channel0EndPhase;
}

里面有一个细节需要说明下,就是立体声模式

当开启 stereo 模式时,我们要制造一种伪立体声效果。所谓“伪立体声”就是左右声道有区别,但是听者无法具体分辨其方位。立体声产生的原因:声音传到左右耳的时间差、频率差、音量差。在我们的实现中,左右声道中 LFO 的 phase 有差异,导致 delay 差异,从而产生了伪立体声。

Chorus 合唱音效

接下来要介绍的是 Chorus 合唱音效。Flanger 和 chorus 都是基于 delay 的音效,在实现上几乎是一样的。

两者最大的区别在于 delay 的长度:chorus 用的 delay 更长,通常在 20-30ms 或者以上,而 flager 通常小于 10ms,远低于人类听觉对回声的分辨极限(大概是 50-70 ms)

在音乐中,当几个音高和音色相近的声音独立演奏时,就会出现合唱音效。这种音效十分常见,例如一群人在唱同一首歌,或者在同时拉小提琴,他们总是会在音高或者时间上表现出轻微的差异即使他们在齐声演奏。这些细微的变化让声音更加丰富、更加明亮,这也是为什么大型弦乐团演奏听起来非常震撼的原因之一。chorus 音效正是模拟了这些时间和音高差,让即使只有一个乐器的音源听起来像是有几个乐器一起演奏一样。

我们来听听chorus音效效果

原始音频

chorus

Basic Chorus

这里给出 basic chorus 的差分方程和块状图

y[n]=x[n]+gx[n−M[n]]y[n] = x[n] + gx[n - M[n]] y[n]=x[n]+gx[n−M[n]]

可以看到,basic chorus 和 basic flanger 的结构是一样的,它们最主要的区别在于 delay 的长度:chorus 的 delay 长度通常在 20-30ms,而 flager 通常在 10ms 一下

Basic chorus和 basic flanger 一致,它们的幅度响应就像一个梳子一样:
∣H(ejω)∣=1+2gcos⁡(ωM[n])+g2\begin{aligned} \vert H(e^{j\omega}) \vert &= \sqrt{1+2g\cos(\omega M[n]) + g^2} \end{aligned} ∣H(ejω)∣​=1+2gcos(ωM[n])+g2​​

我们假设 chorus 的 delay = 30ms,那么在采样率 48kHz 下,M=1440M = 1440M=1440,因此两个波峰之间的间隔为 fs/M=33.3Hzf_s/M = 33.3 Hzfs​/M=33.3Hz,也就说每 33.3 Hz 出现一个波峰。对于我们的听觉系统而言,33.3Hz 的频率差太小了,几乎无法分辨。

下面直接上代码

void Chorus::processBlock(AudioBuffer<float> &buffer)
{const auto num_channels = buffer.getNumChannels();const auto num_samples = buffer.getNumSamples();float ph = 0.0f;for(size_t c = 0; c < num_channels; ++c){auto* channel_data = buffer.getWritePointer(c);auto* dline = dlines_.getDelayLine(c);ph = phase_;for(size_t i = 0; i < num_samples; ++i){const float in = channel_data[i];float weight = 0.0f;float phase_offset = 0.0f;for(int j  = 0; j < num_voices - 1; ++j){if(stereo != 0 && num_voices > 2){weight = (float)(j) / (float)(num_voices-2);if(c != 0){weight = 1.0f - weight;}} else{weight = 1.0f;}if(weight != 0.0f){// get delay from lfofloat delay_second = min_delay + sweep_width*lfo_.lfo(fmodf(ph+phase_offset,1.0f), LFO::WaveformType::kWaveformSine);float delay_sample = delay_second * static_cast<float>(getSampleRate());// get interpolation valuefloat interpolation_val = dline->getInterpolation(delay_sample);channel_data[i] = in + weight * depth * interpolation_val;}if(num_voices < 3){phase_offset += 0.25f;} else{phase_offset += 1.0f / (float)(num_voices - 1);}}dline->push(in);// update phaseph += lfo_freq*invert_sample_rate_;if(ph >= 1.0f){ph -= 1.0f;}}}phase_ = ph;
}

其中比较复杂的是伪立体声那部分,这部分实现中,包括了频率差和音量差,其中phase_offset控制频率差,weight控制音量差。

总结

chorus 和 flanger 在结构上基本相同。主要区别在于参数的选择上:chorus 使用的延迟时长比 flanger 更长,而且通常具有更大的扫频宽度。这些参数共同导致了,原声与延迟副本之间更大的分离感和更多的音高调制。

另一个结构上区别是,flanger 可以利用反馈产生更为强烈的效果,而这一点在 chorus 中几乎没有。另一方面,chorus 会使用一个以上的延迟副本,而 flanger 通常只使用一个副本。

音频特效专栏

  • 音频特效:Delay 和 Vibrato
  • 音频特效:Flanger 和 Chorus

音频特效:Flanger 和 Chorus相关推荐

  1. 音频特效:Delay 和 Vibrato

    Delay line 延迟线 今天我们将讨论 Delay 和 Vibrato 两种音频特效的技术原理和实现细节. Delay 和 Vibrato 都是基于 Delay line 实现的.Delay l ...

  2. 数字音频特效的软件实现项目

    最近主持数字音频特效的软件实现项目,主要工作有相关的理论学习研究,算法的仿真,软件的实现,最后要移植到相应的嵌入式平台上,并做相关的算法优化工作.这是一很有挑战性的工作,希望做出性能与SRS, Pla ...

  3. 音频特效滤镜 via Media Foundation Transform (MFT)

    音频特效滤镜 via Media Foundation Transform 音频特效定义 Media Foundation Transform IMFTransform::GetInputStream ...

  4. ae教程 (五)滤镜特效 (四)音频特效

    示例4:音频特效 其实这个在平时不常使用,这里之所以介绍这个示例,是因为这里包含的效果动画都需要了解 新建合成,导入一段音乐,将该文件拖入时间线中 新建一个黑色纯色层添加音频波形效果,具体参数如图所示 ...

  5. 会声会影x7 添加音频特效功能

    有时因为效果的要求,对于音频我们需要对其进行处理,今天小编就带大家一起一起学习如何用会声会影x7 添加音频特效. 操作步骤: 1.将素材导入到视频轨中 2.展开"选项"面板,切换至 ...

  6. 音频特效插件包:MAudioPlugins for Mac

    maudioplugins mac破解版是一款非常强大的音频特效插件包,目前包含上百种音频特效插件,包括动态处理器,均衡器,调制效果,包括合唱和相位器,混响,立体声工具和分析仪. MAudioPlug ...

  7. XAudio2音频特效

    欢迎关注公众号可以查看更多完整文章 An audio effect is an object that takes incoming audio data, and performs some ope ...

  8. 音频特效生成与算法 1

    14|音效三剑客:变调.均衡器.混响 <名侦探柯南>里的变声器在现实中能否实现?百万调音师能让本来唱歌跑调的人的歌声变得好听,这到底是用了什么神奇的方法?现在介绍的音频中的音效,就是为了实 ...

  9. 音频频谱特效 jaVa_分析音频波形、添加音频特效

    4).使用: window.audio = new mo.Audio({ src: 'http://ossweb-img.qq.com/images/audio/motion/audio4.mp3', ...

最新文章

  1. 安卓v7支持包下的ListView替代品————RecyclerView
  2. crontab 案例
  3. ABAP Netweaver Webcontent path的determine逻辑
  4. java 正则表达式验证邮箱格式是否合规 以及 正则表达式元字符
  5. 深入并发包-ConcurrentHashMap
  6. Hadoop 大数据平台架构与实践
  7. C#EF中,使用类似于SQL中的% 模糊查询
  8. android java tga转png_java 里.tga图片怎么用啊,是不是要导入一个包,真的找不到资源,希望来位大神帮帮我 小弟在此拜谢!...
  9. DNSPod揭6省断网重大事故内幕:网游私服恶斗
  10. 给你揭密一个爆款文案套路,各行各业,谁用谁火
  11. 戴尔硬盘保护增强套件_戴尔4路机架式服务器R940XA原创图集,一部B级车的价值...
  12. [蓝桥杯]基础练习 特殊回文数
  13. 实时查看Linux IO复用情况
  14. 计算机控制手机源码,Total Control电脑控制手机助手
  15. 专利申请过程中的重要文件总结
  16. 『纪念册 · 转专业任务』
  17. S-LIME阅读笔记(有实验代码)
  18. 检索式问答系统baseline
  19. 2_嵌入式软件开发简介
  20. PHP 获取本月与上个月的第一天和最后一天

热门文章

  1. java基础知识点(6)——循环语句for-while
  2. 详解spring框架入门到精通
  3. 二叉树层序遍历递归与非递归_二叉树的遍历「递归、非递归」以及自己的感受
  4. mysql 存储过程 模糊查询_mysql 分页创建存储过程并实现模糊查询
  5. 修改pom文件_自动化测试基础篇:Selenium 框架设计(POM)
  6. 导致网站服务器负担过重,利用httpd.ini实现图片和文件的防盗链
  7. python exe enter退出,Python程序退出处理程序(atexit)
  8. main方法_十个经典的 Java main 方法面试题
  9. pcb设计单点接地示意图_4种PCB设计中的接地方式解析
  10. 计算机excel图表考试题库,2016年职称计算机考试Excel操作题库