A raw tone starts and stops abruptly; an envelope shapes its loudness over time so it swells and releases like a real instrument. Today you build an attack-sustain-release envelope from linear segments.
Shape a signal with a piecewise-linear attack, sustain, and release envelope.
An envelope controls how a sound’s loudness changes from the moment it begins to
the moment it fades - the difference between a plucked string and a bowed one. The
classic model is ADSR (attack, decay, sustain, release); the lite version here
keeps three linear segments: an attack ramp from 0 up to full level, a
sustain hold at full level, and a release ramp back down to 0. (Adding a
decay from the attack peak down to a lower sustain level is the same linear-segment
idea, one more piece.)
It is the fade from lesson 22 generalized: instead of one ramp, a per-sample
multiplier that rises, holds, then falls. With attack 2 and release 2 over six
samples, the factors are 0, 0.5, 1, 1, 0.5, 0, so a constant 1000 becomes
[0, 500, 1000, 1000, 500, 0] - a little hill. Round each scaled sample. Because the
envelope never exceeds 1, it only ever attenuates, so no clipping. This is what
turns the bare oscillator tones into something that sounds shaped rather than
switched on.
func envelope(sig []int, attack, release int) []int {n := len(sig)out := make([]int, n)for i, s := range sig {var f float64switch {case i < attack: f = float64(i) / float64(attack) // 0 -> 1case i >= n-release: f = 1 - float64(i-(n-release)+1)/float64(release) // 1 -> 0default: f = 1 // sustain}out[i] = int(math.Round(float64(s) * f))}return out}