编写基本的混响算法 - 第 1 部分:音频编程简介

1_DSidXYqDzNBfnXJKWRBY9g

混响 是音乐中一种非常强大的效果,它可以突出音频并广泛用于音乐制作。我确信你是个浴室演唱家,也知道到你已经注意到浴室里的声音比你家的其他房间都棒。嗯,这要归功于混响。混响的想法很简单。

混响 的特征是声音发出后三十毫秒内发生的随机、混合重复。

当我之前没有音频编程经验并且对数字音频的工作原理只有非常基本的了解时,我开始研究这个问题。当我解决这个问题时,我在互联网上搜索以了解如何实现这一目标。我在这里的动机是通过参考我在每个步骤中使用的资源来记录我的学习,以便它可以作为任何开始音频编程的人的综合参考帖子。

问题陈述:

给定一个音频文件,对音频应用混响。

假设:

  1. 音频文件将是 WAV 文件(8 位和 16 位 PCM 编码)
  2. 可以控制的混响参数有:延迟、衰减因子、干湿混合百分比

使用的编程语言:Java

完整代码可从我的 Github 获取:https : //github.com/Rishikeshdaoo/Reverberator

为什么只有 WAV 文件?

是为了简化问题。

将混响限制为仅 WAV 文件的原因与音频文件的编码方式有关。我们所知道的各种音频格式,例如: mp3、wav、ogg、flac 基本上是不同的音频以数字方式存储的方式。

在数字上,音频以“ 样本 ”的形式存储。

声音是一个连续时间函数,数字设备只对离散事件起作用。因此,我们需要某种方式将声音表示为离散时间函数。

可以将样本可视化作为从连续可变的模拟波形中以周期性间隔提取的值。采样将模拟波形转换为一系列离散的样本(数字),代表精确时间的波形值。采样将波形从模拟域转换为数字域,更适合计算机处理。

WAV(特别是 8 位和 16 位 PCM)文件采取名为 脉冲编码调制 的编码 。你 可以在这里这里详细了解它。

简而言之,从编程的角度来看,这意味着文件中的每个样本都将采用固定大小(称为“ 帧” )。例如:在 8 位 PCM 编码文件中,每 8 位将组成一个样本。

这使我们的工作变得简单,因为我们可以直接将 WAV 文件读入 Byte 数组; 所有样本帧在数组内按顺序排列

对于其他格式,例如 mp3,编码要复杂得多,并且仅从音频文件中检索样本就需要进行大量编码。

按步骤分解

从读取 WAV 文件到播放混响处理文件的完整程序可以分解为以下步骤作为里程碑:

1_3zpoOM7eUXE6ZsH7-hQiJg

第 3 步第 4 步是程序中最重要的步骤。

第 1 步和第 2 步:读取 WAV 文件并复制到 Byte 数组中:

这两个步骤非常简单。WAV 文件被读入输入流。该数据被复制到一个字节数组中。浏览此处的代码以在 Java 中实现。

第 3 步:将 Byte 数组转换为样本数组:

在这一步中,将存储在字节数组中的音频数据转换为样本数组需要实现的两个主要目标。

其中,我推荐一个很好的资源(这里),我将在解释过程中使用此资源中的段落。在代码中,这种转换是通过调用 SampleDataRetrieval 类中的 unpack 方法发生的。

  1. 连接字节数组:

字节数组包含分割成一行的样本帧。这实际上非常简单,除了称为 endianness 的 东西,它是每个数据包中字节的排序。

字节序:

这是一个图表。此数据包包含十进制值 9999:

1_FwxWVMbse4vC2L8LHsqenA

它们持有相同的二进制值;但是,字节顺序是相反的。

  • big-endian 中 ,更重要的字节具有更大的索引。 即 MSB 将处于最高索引。
  • little-endian 中 ,更重要的字节具有更小的索引。 即 MSB 将位于索引 ‘0’

WAV文件以 little-endian 字节顺序存储,AIFF 文件以 big-endian 字节顺序存储。可以从. AudioFormat.isBigEndian(A Java class that provides audio format specifications) 获得。

  1. 解码有符号和无符号值:

对于 PCM 签名系统:

计算机通过一种称为 二进制补码 的系统来表示有符号数 这有助于表示负数和正数,而无需针对两种不同类型的任何特殊逻辑。现在,当我们尝试从中检索实际数值时,我们需要扩展二进制补码

要获得整数 的二进制补码 负数,你可以用二进制写出数字。然后使用倒数,并在结果中加一。这基本上将 MSB 设置为负值的“1”和正值的“0”。

这意味着如果最高有效位 (MSB) 设置为 1,我们用 1 填充它上面的所有位。 >> 如果设置了符号位,算术右移 ( ) 将自动为我们填充。

1_6TPJwGLgFG-PYhZdNUxBPA
对于 PCM 未签名系统:

无符号系统只是 数字偏移表示

步骤 4:在样本数组上应用混响算法:

在第 3 步之后,我们有一个浮点数组,其中包含每个样本的实际幅度值。这将作为我们混响的输入信号。

第 4 步 是混响的设计和程序实现。这涉及很多详细的讨论,我在这里单独写了一篇文章。另外,请参阅混响实现的源代码

在第 4 步结束时,我们将拥有一个“ 混响” 音频的浮点数组。现在我们需要返回并将其写入 WAV 文件,以便我们可以播放和收听。

第 5 步:将样本数组转换为字节数组并播放音频:

我们已经处理了过程中棘手的部分,只把剩下样本打包回其原始音频规格,就可以准备听我们的混响了!

将浮点数组打包为字节数组的过程,正如你的直观猜测,是反向执行第 3 步。

  1. 对于 PCM 无符号,可将其转换回原始表示。这是通过添加我们之前减去的 偏移量满量程 )来完成的。PCM 签名不需要任何更改,因为符号扩展数不会在进一步计算中引起任何问题。

  2. 根据 little endian 规范,将上述值转换​​为字节 — 用 0xffL 屏蔽数字以从数字中提取 8 位并存储在字节数组元素中。对于 16 PCM,将 MSB 位移 8 位,然后应用掩码。

  3. 音频的播放使用JavaSound API。

它使用混合控制台的比喻来表示 API 的组件。 “混音器” 是声音设备的概念。甲 “线” 是在用于音频I / O的混合控制台条中的一个。我在这个实现中使用了 “剪辑” ,它在播放之前将完整的音频存储在内存中。你可以在此处阅读有关 JavaSound API 的详细信息。请参阅代码注释以逐步详细说明此实现。

我希望这篇文章能帮助到一些人,如有任何疑问或建议欢迎评论。

原文作者 Rishikesh Daoo
原文链接 https://medium.com/the-seekers-project/coding-a-basic-reverb-algorithm-an-introduction-to-audio-programming-d5d90ad58bde