Logo
Section 1 / 5

创建一个正弦波

学习如何创建正弦波并将其转换为音频文件。

导入

创建一个正弦波

学习如何创建正弦波并将其转换为音频文件。

在本节课中,我们将学习如何生成一个正弦波并将其保存为 wav 文件。在此之前,先回顾一些基本理论。

一些定义

频率:频率是指正弦波每秒重复的次数。在本课中,我们将使用 1KHz 的频率。

采样率:大多数现实世界的信号是模拟信号,而计算机是数字设备。因此我们需要一个转换器将模拟信号转换为数字表示。关键点在于采样率,即转换器每秒对模拟信号进行采样的次数。

虽然我们现在完全在数字环境中操作,采样率的重要性相对降低,但它对于正弦波公式仍然必不可少。我们将使用 48000 这个值,这是专业音频设备常用的采样率。

正弦波公式:

y(t)=A∗sin(2∗pi∗f∗t)y(t) = A * sin(2 * pi * f * t)

y(t)y(t) 是我们要计算的 x 轴样本对应的 y 轴值。A 是振幅。我们稍后再讨论这一点。π\pi 是常数 π=3.14159\pi = 3.14159。f 是频率。t 是我们的样本。由于需要将其转换为数字形式,我们将 t 除以采样率。

振幅:通常人们会随机选择一个 A 值,比如 11。但这对我们不适用。我们生成的正弦波是浮点数,虽然这足以绘制图形,但在写入文件时无法正常工作。这是因为我们处理的是整数。查看 WAV 文件格式,它们是以 16 位短整型(short integer)存储的。如果写入浮点数,将无法正确表示。

为解决这个问题,我们必须将浮点数转换为定点数。可以通过乘以一个固定常数来实现。如何确定这个常数?一个有符号 16 位数的最大值是
3276732767(即 215−12^{15} - 1)。

最高位用于表示符号,剩下 15 位。我们把 2 的 15 次方减去 1,因为计算机从 0 开始计数。

如果我们希望达到满量程音频,应乘以 32767。但此时,要使音量达到满量程的一半,就需要使用 16000 的振幅。

绘制音频信号

我们已经设置好了所声明的变量。

在下方代码框“绘制频率波形”中,请注意第 18 行的高亮代码。

我们可以用普通的 for 循环来编写这段代码,但我们想展示列表推导式的强大功能,因为这是 Python 最强大的特性之一。

如果理解得当,下面这行代码其实很简单。让我们逐步拆解一下。最好同时打开源代码以便对照。

1)第一点是公式被放在 [] 中,这意味着最终结果会被转换成一个列表。

1[ for x in range(num_samples)]

2)range() 函数生成从 0 到 num_samples 的一系列数字。本质上,我们将遍历所有样本数量,用变量 x 从 0 到 48000。

1[np.sin(2 * np.pi * frequency * x/sampling_rate)]

3)这表示对每个生成的 x 值,都代入正弦波公式进行计算。

12 * pi * f * t

所以再看一遍第 18 行代码:

它表示在 0 到 num_samples 的范围内生成 x,对每个 x 值计算其正弦值。我们可以把这个值看作 y 轴的数值。所有这些值都会被放入一个列表中。

现在我们可以绘制出这个正弦波了。

1import numpy as np 2import wave 3import struct 4import matplotlib.pyplot as plt 5 6 7def create_wav(): 8 # 频率是波形每秒重复的次数 9 frequency = 1000 10 11 num_samples = 48000 12 13 # 模数转换器的采样率 14 sampling_rate = 48000.0 15 16 amplitude = 16000 17 18 sine_wave = [np.sin(2 * np.pi * frequency * x/sampling_rate) for x in range(num_samples)] 19 20 # 绘制图形 21 x = range(num_samples) 22 plt.plot(x[:300], sine_wave[:300]) 23 plt.show() 24 25 26 27if __name__ == "__main__": 28 create_wav() 29 plt.savefig('output/plot.png')

创建 wav 文件

现在我们已经看到正弦波以图形方式呈现,接下来就是将其写入文件。

为此,我们将使用 Python 内置的 wave 库。首先设置参数。

1)nframes 是帧数或样本数。

2)comptype 与 compname 表示数据未压缩。

3)nchannels 是声道数,这里为 1。

4)sampwidth 是每个样本的字节宽度。

如前所述,WAV 文件通常是 16 位,即每个样本占 2 字节。

1wav_file = wave.open(file, 'w') 2 3wav_file.setparams((nchannels, sampwidth, int(sampling_rate), nframes, comptype, compname))

我们打开文件并设置参数。

1for s in sine_wave: 2 wav_file.writeframes(struct.pack('h', int(s * amplitude)))

这部分可能需要进一步解释。我们正在逐个样本地写入 sine_wave。writeframes 是用于写入正弦波数据的函数。

以上内容都很简单,接下来的代码则更复杂,需要更加专注!

1struct.pack('h', int(s * amplitude))

我们来分步解析:

1int(s * amplitude)

这里的 s 是我们要写入的 sine_wave 的单个样本。我们在此处将其乘以振幅(用于转换为定点数)。虽然可以提前完成,但这一操作在写入文件时最为关键。

目前我们拥有的只是一组数字列表。如果直接写入文件,音频播放器将无法读取。

Struct 是 Python 的一个库,用于将我们的数据打包为二进制格式。

代码中的 h 表示 16 位整数。下面我们通过 IPython 示例来理解打包的作用。

以下以 0.5 为例。我们计算 0.5 的正弦值,并通过乘以 16000 转换为定点数。如果我们直接写入文件,只会写入字符串 "7664",这是错误的。所以我们来看看 struct 的作用:

1struct.pack('h', 7664)

在下方可运行代码组件中运行代码并观察结果。

\x 符号表示该数值为十六进制。可以看到,struct 已将数字 7664 转换为两个十六进制值:0xf0 和 0x1d。

为什么使用 0xf0 0x1d?

因为将 7664 转换为十六进制正好是 0xf01d。

为什么是两个值?

因为我们使用的是 16 位整数,无法用一个字节容纳,因此 struct 将其拆分为两个部分。

现在数值已转为十六进制,其他程序(包括我们的音频播放器)都可以正确读取。

回到我们的代码:

请查看下方代码块中的第 31–32 行。

这将把我们的正弦波样本写入文件 test.wav,并以 16 位音频格式打包。

运行以下代码,查看输出结果。

1import numpy as np 2import wave 3import struct 4import matplotlib.pyplot as plt 5 6 7def create_wav(): 8 # 频率是波形每秒重复的次数 9 frequency = 1000 10 11 num_samples = 48000 12 13 # 模数转换器的采样率 14 sampling_rate = 48000.0 15 16 amplitude = 16000 17 file = "output/test.wav" 18 19 sine_wave = [np.sin(2 * np.pi * frequency * x/sampling_rate) for x in range(num_samples)] 20 21 22 nframes = num_samples 23 comptype = "NONE" 24 compname = "not compressed" 25 nchannels = 1 26 sampwidth = 2 27 28 wav_file = wave.open(file, 'w') 29 wav_file.setparams((nchannels, sampwidth, int(sampling_rate), nframes, comptype, compname)) 30 31 for s in sine_wave: 32 wav_file.writeframes(struct.pack('h', int(s * amplitude))) 33 34 wav_file.close() 35 36if __name__ == "__main__": 37 create_wav()

运行后,output 目录下会生成 test.wav 文件。

使用任意音频播放器(例如 Windows Media Player、VLC 等)播放该文件,应该能听到一段短暂的音调。

使用 Audacity

现在我们需要验证音调的频率是否正确。我们将使用 Audacity,一款开源音频编辑软件。Audacity 的一个有趣功能是可以分析音频文件的频率。

打开文件后,你会看到如下图像。

...
Test.wav 文件在 Audacity 中打开

当音频文件在 Audacity 中打开时,可能需要多次放大才能看清频率波形。有时甚至需要放大两次才能完整显示图形。

我们看到的是一个正弦波。

注意:波形最高点约为 0.5,而最大值为 1.0。记住,我们已将 sine_wave 乘以 16000,这对应于满量程的频率图。

现在来查找频率。

可以使用“频谱图”功能查看当前图形。

...
我们的 Test.wav 文件频谱图

可以看到峰值出现在约 1000 Hz 处,这正是我们创建 wav 文件时设定的频率。

结论

计算正弦波的频率