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

翻译自: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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356