Audio From Scratch With Go: ADSR
source link: https://dylanmeeus.github.io/posts/audio-from-scratch-pt10/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Audio From Scratch With Go: ADSR
With everything that we have added to our library so far we are almost capable of generated small tunes. One thing that’s missing to make it sound more ‘natural’ is a way for the notes to start and stop.
In this post we will implement a type of envelope called “ADSR”, for “Attack, Decay, Sustain, Release”. Which will make the notes sound more natural as they are played in sequence.
To see why we need this, listen to this sound generated without an ADSR envelope around the generated frames.
If you want to read the (not pretty) code that generated this, check out this github gist.
The Attack, Decay, Sustain and Release envelope is a common type of envelope. Schematically this can be represented as below (from wikipedia):
As we apply this envelope to a signal, the signal will change in amplitude depending on which phase we are in of the ADSR envelope. In the image it is visible that the amplitude rises during the attack step, reaches a peak amplitude before decreasing a bit. After decreasing it reached the sustain amplitude, where it will stay until the note is released, and after release it decays until the ampltide is zero.
For our parameters, three will relate to time:
- attack (time to rise)
- decay (time to fall to sustain level)
- release (time to decay from sutan to zero)
So the Sustain parameter does not refer to time, but rather to the amplitude we will maintain.
Turning this schematic into code, we get :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
}
One parameter in this function that you don’t find in the schematic is the need for a control rate. The control rate will be used to turn a duration in seconds into an amount of frames. The control rate could just be sample rate but this does not necessarily have to be the case. One such use-case us sub-audio modulation, whereby the modulating oscillator is running below 20Hz. You can check this chapter for a bit more on that.
Application
To apply the ADSR envelope to a signal, for example to one that was generating using the oscillator we created we have to iterate over each frame, pass the current frame to the ADSR function, and modify the amplitude of the frame with the result. For example, this is the complete example program included in GoAudio.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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")
}
In this example, notice that our control rate is the same as our sample rate, and the adsrtime
increases together with the frames that we have processed. (We could thus pass the i
iterating
variable to the function, but I thought making it explit was clearer).
Resources
If you liked this and want to know when I write new posts, the best way to keep up to date is by following me on twitter.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK