liveBook · Manning

Envelope

As explained in Learn Haskell by Example

An envelope is a time-varying contour that shapes a characteristic of a synthesized sound, such as its amplitude.

An envelope is a time-varying contour applied to a synthesized signal. An ADSR envelope shapes amplitude through attack, decay, sustain, and release stages, changing the sound’s character. section 10.2.2 section 10.3.2

Build an ADSR contour from four time-based components:

  • Increase the attack curve linearly to full volume over the attack duration. section 10.2.2
  • Taper the decay curve toward the sustain level over the decay duration. section 10.2.2
  • Repeat the sustain level indefinitely so the contour can cover an arbitrarily long signal. section 10.2.2
  • Reverse a release curve, take the required number of samples, and multiply the curves with the original signal. section 10.2.2

Applying an ADSR envelope

Use `adsr (ADSR 0.1 0.2 0.5 0.1) (tone tri 550 1)` and write the resulting signal to `adsrSignal.wav`. The parameters specify attack, decay, sustain, and release values; an audio editor reveals the resulting contour.

Listing 10.8 A function to apply an ADSR envelope to a signal
import Util.Types

adsr :: ADSR -> Signal -> Signal
adsr (ADSR a d s r) signal =
  zipWith3                          #1
    (\adsCurve rCurve sample ->
 adsCurve * rCurve * sample)     #1
    (att ++ dec ++ sus)       #2
    rel
    signal
  where
    attackSamples = fromIntegral $ samplesPerSecond a    #3
    decaySamples = fromIntegral $ samplesPerSecond d     #3
    releaseSamples = fromIntegral $ samplesPerSecond r   #3

    att = map (/ attackSamples) [0.0 .. attackSamples]   #4
    dec =
      reverse $    #5
        map                                              #6
          (\x -> ((x / decaySamples) * (1 - s)) + s)     #6
          [0.0 .. decaySamples - 1]                      #6
    sus = repeat s             #7
    rel =
      reverse $                            #8
        take                                #9
          (length signal)                  
          (map (/ releaseSamples) #9
 [0.0 .. releaseSamples] ++ repeat 1.0)  #10

Inspect the implementation to see how `zipWith3` combines attack-decay-sustain values, release values, and signal samples, while infinite sustain guarantees coverage. section 10.2.2

Oscillators pair a waveform with ADSR parameters, producing tones whose contours can differ markedly across piano, ocarina, violin, pluck, and bass examples. section 10.3.2

How Learn Haskell by Example covers it

How deep it goes
Explains ADSR construction through sampled curves, infinite lists, list combination, reversal, and signal multiplication.
Examples
Uses a triangle-wave WAV, an audio-editor contour, and piano, ocarina, violin, pluck, and bass oscillator settings.
What you can do after
Build it from scratch by implementing and applying an ADSR envelope to generated Haskell signals.
How it connects
Connects ADSR to signals, oscillators, waveforms, infinite lists, and tremolo modulation.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.