使用Go播放音频:ADSR

原文地址:https://dylanmeeus.github.io/posts/audio-from-scratch-pt10/

到目前为止,我们已将所有内容添加到库中,几乎可以生成小曲调了。目前在开始和停止的时候,还缺失一种听起来更“自然”的声音。

在本文中,我们将为“Attack, Decay, Sustain, Release”实现一种称为“ ADSR”的包络。顺序播放时,音符听起来会更加自然。

要了解为什么需要这样做,请听一下在帧周围没有ADSR包络的情况下生成的声音。

如果你想阅读生成此代码的(不太漂亮的)代码,请查看此github gist

ADSR

上升(Attack),衰减(Decay),保持(Sustain)和释放(Release)包络是一种常见的包络类型。从示意图上可以表示如下(来自维基百科):

维基百科ADSR示意图

当我们将此包络应用于信号时,信号的幅度将根据我们处于ADSR包络的相位而变化。在图像中可以看到,振幅在起Attack骤中上升,在降低一点之前达到峰值。减小后,达到Sustain幅度,它将一直保持直到释放音符为止,Release后Decay直至为零。

对于我们的参数,三个与时间有关:

  • Attack(上升时间)
  • Decay(下降到维持水平的时间)
  • Release(从苏丹到零衰减的时间)

因此,Sustain参数不是指时间,而是指我们将保持的振幅。

将此原理图转换为代码,我们得到:

func ADSR(maxamp, duration, attacktime, decaytime, sus, releasetime, controlrate float64, currentframe int) float64 {
    dur := duration * controlrate
    at := attacktime * controlrate
    dt := decaytime * controlrate
    rt := releasetime * controlrate
    cnt := float64(currentframe)

    amp := 0.0
    if cnt < dur {
        if cnt <= at {
            // attack
            amp = cnt * (maxamp / at)
        } else if cnt <= (at + dt) {
            // decay
            amp = ((sus-maxamp)/dt)*(cnt-at) + maxamp
        } else if cnt <= dur-rt {
            // sustain
            amp = sus
        } else if cnt > (dur - rt) {
            // release
            amp = -(sus/rt)*(cnt-(dur-rt)) + sus
        }
    }

    return amp
}

在原理图中找不到的此功能中的一个参数是控制速率。控制速率将用于将持续时间(以秒为单位)转换为帧数。控制率可以只是采样率,但不一定是这种情况。一种这样的用例是子音频调制,其中调制振荡器在20Hz以下运行。你可以 在这篇文章中进一步了解。

应用

要将ADSR包络应用于信号(例如,使用我们创建的振荡器生成的信号),我们必须遍历每一帧,将当前帧传递给ADSR函数,并使用结果修改帧的幅度。例如,这是GoAudio中包含的完整示例程序 。

package main

import (
    "flag"
    "fmt"

    synth "github.com/DylanMeeus/GoAudio/synthesizer"
    "github.com/DylanMeeus/GoAudio/wave"
)

func main() {
    flag.Parse()
    osc, err := synth.NewOscillator(44100, synth.SINE)
    if err != nil {
        panic(err)
    }

    sr := 44100
    duration := sr * 10

    frames := []wave.Frame{}
    var adsrtime int
    for i := 0; i < duration; i++ {
        value := synth.ADSR(1, 10, 1, 1, 0.7, 5, float64(sr), adsrtime)
        adsrtime++
        frames = append(frames, wave.Frame(value*osc.Tick(440)))
    }

    wfmt := wave.NewWaveFmt(1, 1, sr, 16, nil)
    wave.WriteFrames(frames, wfmt, "output.wav")
    fmt.Println("done writing to output.wav")
}

在此示例中,请注意,我们的控制率与采样率相同,并且adrtime与我们处理的帧一起增加。(因此,我们可以将i迭代变量传递给函数,但我认为将其展开更清晰)。

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

推荐阅读更多精彩内容