美文网首页
使用Go播放音频:ADSR

使用Go播放音频:ADSR

作者: 豆腐匠 | 来源:发表于2020-11-18 17:05 被阅读0次

    原文地址: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迭代变量传递给函数,但我认为将其展开更清晰)。

    相关文章

      网友评论

          本文标题:使用Go播放音频:ADSR

          本文链接:https://www.haomeiwen.com/subject/pnbciktx.html