【转载翻译】音频从零开始:产生第一个声音(Golang)

问数寻理 · · 981 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

翻译自:https://dylanmeeus.github.io/posts/audio-from-scratch-pt1/

在这篇文章中,我们将使用Go从头开始以二进制格式创建声音。这篇文章的最终结果是播放一定频率、采样率和持续时间的声音。我们还会应用指数衰减,这样声音就会逐渐变小。

在最简单的形式中,声音对计算机可以被认为是一种简单的数字编码波。在声音到达你的耳朵之前,它会经过一个数字到模拟转换器,基本上就是把数字信号转换成你的耳机/扬声器的电流。

第一步,我们试着用go创建一个正弦波。我们可以用math.sin (x)来生成它并将x作为弧度传递。我们必须在一定范围内迭代才能得到正弦波。为了保持在音频节目领域,“点”的数量,我们将绘制到正弦波是我们的样本。(如果你想跳过,这篇文章的所有代码在github上:https://github.com/DylanMeeus/MediumCode/blob/master/Audio)

const nsamps = 50 // samples to generate 

func generate() 

 tau = math.Pi * 2 

 var angle float64 = tau / nsamps 

 for i := 0; i < nsamps; i++ { 

 samp = math.Sin(angle * float64(i)) 

 fmt.Printf("%.8f\n", samp) 

 } 

}

注意,我们将示例打印到stdout,我们可以将此输出通过管道传输到一个文件(go run main.go > out.txt) 。这个文件的输出如下所示:

-0.00000000 

-0.12533323 

-0.24868989 

-0.36812455 

-0.48175367 

-0.58778525

. .很难看出这里发生了什么。但是使用gnuplot,我们可以更容易地可视化这个文件。在gnuplot,运行:

plot "out.txt" with lines

这看起来像一个完美连续的正弦波,但这就是gnuplot“用线”来显示它的方式。如果我们画条形图,我们会看到稍微不同的结果。( plot “out.txt” with boxes )

既然我们可以产生正弦波,我们就有了发声的基本知识。尽管这只是浮点数,我们可以把它变成一些可播放的原始音频文件。

第二步:产生声音

要把正弦波变成真正的声音,我们需要引入一些东西。

样本率

首先,以一定的采样率来存储声音。采样率告诉你每秒有多少采样用于你的声音编码。cd质量的记录有44100赫兹的采样率,允许频率高达22.05KHz。考虑到人耳听到声音20 hz 20 khz之间,这是很多(假设你只是针对人类听众)。虽然其他格式是可能的,如48Khz的dvd视频质量或96KHz的dvd音频质量,我们将坚持目前的cd质量。正如您将看到的那样——更改这一点是很简单的。你们可以自己尝试一下看看是否能听到不同的声音。所以我们不使用nsamps = 50我们至少需要44100个样本。为了调整声音的持续时间,我们还将为此添加一个变量。

const ( 

    Duration = 2 

    SampleRate = 44100 

)

频率

接下来,我们将引入一个频率。目前,我们将使用频率的440Hz被定义为“音高标准”。这是一个高于中间c的音符A的标准调音,为了不偏离我们产生音乐的目标,如果你好奇我们为什么使用这个频率,请查看这个维基页面。加上这个,我们将再次扩展我们的观点:

const ( 

 Duration = 2 

 SampleRate = 44100 

 Frequency = 440 // Pitch Standard 

)

存储声音

我们现在有了生成声音的基本要素,但是我们漏掉了一个至关重要的部分。我们如何存储这些数据,以便我们的计算机能将其解释为声音?我们在第1步中生成的浮点数确实可以使用,但是我们必须将它们存储为二进制表示。这里一个棘手的部分是,你必须以你的计算机能够读取的方式存储它们——这意味着你必须在BigEndian机器上使用BigEndian,否则就只能使用LittleEndian。在linux系统上,这可以通过您的终端发现(macOS上可能有相同的命令,但不需要验证!)

dylan@devuan:~$ lscpu | grep "Byte Order" Byte Order: Little Endian


代码!

现在我们知道该做什么了,并且设置好了常数,让我们修改生成函数来把它们联系在一起。声音将被存储在一个名为“out”的文件中。在你的机器上。(为简洁起见,我已经删除了错误处理!)

func generate() { 

 nsamps := Duration * SampleRate

var angle float64 = tau / float64(nsamps) 

 file := "out.bin" 

 f, _ := os.Create(file) 

 for i := 0; i < nsamps; i++ { 

 sample := math.Sin(angle * Frequency * float64(i)) 

 var buf [8]byte 

 binary.LittleEndian.PutUint32(buf[:], math.Float32bits(float32(sample))) 

 bw,_ := f.Write(buf[:]) 

 fmt.Printf("\rWrote: %v bytes to %s", bw, file) 

 } 

}

使用ffplay,我们现在可以播放这个文件,尽管我们需要指定我们的采样率和格式。指定我们的显示模式,我们也可以可视化的声音正在播放:

ffplay -f f32le -ar 44100 -showmode 1 out.bin

或者,您也可以使用Audacity将我们的二进制文件作为“原始音频文件”导入。只要确保你选择单声道和正确的编码。这是如何创建的音高标准。虽然一个小小的改进是在接近结尾的时候篡改声音。这比有一个恒定的信号感觉更“自然”。为了实现这一点,我们可以在信号的末端引入指数衰减。扩展1:指数衰减我们不需要添加很多就能得到指数衰减。我们想让我们的信号淡出,所以我们将定义一个开始和结束“振幅”来产生衰减因子。接下来,在每次迭代中,我们将通过将信号乘以一个衰减因子来修改信号的实际振幅。在函数的顶部,我们将定义这些变量:

func generate() { 

 var ( 

 start float64 = 1.0

 end float64 = 1.0e-4 

 ) 

 nsamps = Duration * SampleRate

decayfac := math.Pow(end/start, 1.0/float64(nsamps)) ..

一旦我们设置好了它们,在我们生成wave的循环中,我们可以在每次迭代中修改样本

sample := math.Sin(angle * Frequency * float64(i)) 

sample *= start 

start *= decayfac

当我们把这些放在一起,我们的函数变成:

func generate() { 

 var ( 

 start float64 = 1.0

end float64 = 1.0e-4 

 ) 

nsamps := Duration * SampleRate 

 var angle float64 = tau / float64(nsamps) 

 file := "out.bin" 

 f, _ := os.Create(file)

decayfac := math.Pow(end/start, 1.0/float64(nsamps)) 

 for i := 0; i < nsamps; i++ { 

 sample := math.Sin(angle * Frequency * float64(i)) 

 sample *= start 

 start *= decayfac 

 var buf [8]byte 

 binary.LittleEndian.PutUint32(buf[:], math.Float32bits(float32(sample))) 

 bw, _ := f.Write(buf[:]) 

 fmt.Printf("\rWrote: %v bytes to %s", bw, file) 

 } 

}

现在如果我们播放这个声音,我们会从这篇文章顶部的视频中得到声音。所有代码都在GitHub上:


https://github.com/DylanMeeus/MediumCode/blob/master/Audio/FirstSound/main.go


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:问数寻理

查看原文:【转载翻译】音频从零开始:产生第一个声音(Golang)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

981 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传