art work


A blog.

Amplitude modulation Ingo

The simplest form of amplitude modulation: turn up the volume. That is on "macro" scale. On a very small scale you can wiggle the amplitude of a single wave form. In between those you can change the envelope of a series of wave forms. I'll show a short exploration by example. The scene files are added to for download.

But first,

A word of warning


Experiments with sound synthesis can create extremely loud and nasty sounds.

Don't use headphones! Turn your amps down! Build attenuators into your scripts!

Check the resulting waveform in an audio editor before listening. Audacity or Ocenaudio for example. Both support multiple platforms.

Modulate a sine with a sine

I'm going to omit the volume knob and the pan pot and start with a sine wave oscillator modulating a sine sine wave oscillator. One oscillator is the carrier, the other the modulator. In the example the amplitude of a 35 Hz sine wave is changed 300 times per second. The output of the modulator goes into the Amp parameter of the carrier.

#macro Instrument(Tick, SRate)
  // SinOsc(Freq, Phase, Amp, Tick, SampleRate)
  #local S0 = SinOsc(300, 0, 0.5, Tick, SRate); //Modulator
  #local Sample = SinOsc( 35, 0,  S0, Tick, SRate); //Carrier
Wave form amplitude modulation 35, 300 Hz

Modulate a sine with a spline

In the above image of a wave form we can connect all the maxima with a curve. The same for the minima. This results in the envelope of a wave form. If we'd do this with just a sine wave we'd get two horizontal lines. Amplitude modulation modifies the envelope. With a spline we can draw an envelope and apply it to an existing wave form.

Wave form amplitude modulation 35, 300 Hz
#declare WV = function{
  spline {
   -0.10, <0,0>
    0.00, <0,0>
    0.40, <0,1>
    0.50, <0,0.7>
    0.60, <0,1>
    1.0,  <0,0>
    1.1,  <0,0>
#declare WVOsc = function(Freq, Phase, Amp, Tick, SRate){
  Amp * WV(Ramp(Freq, Phase, Tick, SRate)).y  

#macro Instrument(Tick, SRate)
    #declare Sample0 = WVOsc(55, 0, 1, Tick, SRate);
    #declare Sample  = SinOsc(555, 0, Sample0, Tick, SRate);
Spline envelope


ADSR, attack, delay, sustain, release. ADSR is an envelope to mimic what happens in 'real' instruments when you pluck a string, hit a drum or press a piano key. The attack is how fast the energy of the sound rises. Then it drops a bit, the decay. If sound continues a level it is the sustain and when a piano key is released the piano string still makes sound and its intensity decreases.

When there is a delay before the sound starts after pressing a key the envelopes is called DADSR. With an AHDSR at the peak of the attack the level is held steady for a moment before the decay starts.

Nothing is the same

When Pete Thownshend windmills his guitar, no attack is the same. Nor is the release when Janine Jansen plays the violin. BB King's vibrato also introduces an amount of amplitude tremolo. All these ADSR curves should not be seen as fixed. We can wiggle every parameter by using splines for creating the envelope and use some randomness.

Spline envelope



ADSR, modified acceleration of the attack and deceleration of the decay, amplitude tremolo on the release. The spacing between the spheres nicely show the speed variations.

The examples all use linear splines. Curved ones work fine as well but they are a bit harder tot model without overshooting the amplitude at points.

#declare rADSR = function{
  spline {
    0.00, <0,0>    // delay
    0.005, <0,0.5> // attack part 1
    0.04, <0,1.0>  // attack part 2
    0.05, <0,0.7>  // decay part 1
    0.1,  <0,0.4>  // decay part 2
    0.15, <0,0.4>  // sustain
    0.2,  <0,0.3>  // modified release
    0.25, <0,0.35> //       .
    0.3,  <0,0.2>  //       .
    0.35, <0,0.25> //       .
    0.4,  <0,0.1>  //       .
    0.45, <0,0.15> //       .
    0.5,  <0,0>    //       .

A classic implementation

An ADSR made with splines can be triggered with just a pulse and then it does its whole cycle, unless it is retriggered. Normally an ADSR is triggered by a gate. When the gate opens the attack starts, followed by decay and sustain. This until the gate closes, then the release starts. The gate can close before the decay is finished, then the release picks up from that point. To model this state is needed and in POV-Ray that means writing state to file when doing animations.

This method can also be modelled with splines or spline segments, but I found a very nice implementation by Nigel Redmon documented in 5 articles. I rewrote it to a POV-Ray include file. It is in the file for download.

This ADSR offers the possibility to change the curvature of the atack, decay and release. One difference with spline based versions above is that the decay time is for a fall from max to zero and not to sustain level.

Using the ADSR

The ADSR needs a gate, so I created a simple one for now. It takes Beats Per Minute (BPM) as input for the timing. The output is either 0, closed, or 1, open. The duty cycle is adjustable between [0,1] -> [0,100%]

#declare Gator = function(BPM, DutyCycle, Phase, Amp, Tick, SRate){
  select(Ramp(BPM/60, Phase, Tick, SRate) - DutyCycle, 1, 0)

The next step is to initialise the ADSR and set its parameters. Currently every parameter is set by its own macro. Maybe I'll add a macro to set the ADSR parameters at once, maybe even at init.

#declare adsr0 = ADSRinit(SRATE);

ADSRattack(adsr0, Attack)
ADSRdecay(adsr0, Decay)
ADSRrelease(adsr0, Release)
ADSRsustain(adsr0, Sustain)
ADSRtargetRatioA(adsr0, Ratio_A)
ADSRtargetRatioDR(adsr0, Ratio_DR)

The ADSR takes a gate as an input and then the ADSR can be run to get an output. This has to happen inside the audio sampling loop. These two macros could be combined into a single one.

#for(Tick, 0, Samples)
  #declare Gate = Gator(120, 0.5, 0, 1, Tick, SRate);
  ADSRgate(adsr_a, Gate) 
  #declare A0 = ADSRrun(adsr_a);
  #declare Sample0 = SinOsc(200, 0, 0.8, Tick, SRate);
  #declare Sample = Sample0 * A0;  

I've not included the source for the animation in because I'm not happy with how it is set up. Synchronising sound and vision is ad hoc, it is something I have to give a lot of thought to set up properly.

Spline envelope again

While looking at wave form envelope images I thought, what if I draw two splines. One for the positive part of the signal and one for the negative and use that to modulate the amplitude. This is the result of my first and only attempt:

#declare Envelope = function{
  spline {
    0.0, <0.3,  0.1>
    0.1, <0.7,  0.8>
    0.2, <0.2,  0.4>
    0.3, <0.04, 0.6>
    0.4, <0.6,  0.1>
    0.5, <0.2,  0.4>
    0.6, <0.1,  0.0>
    0.7, <0.4,  0.5>
    0.8, <0.0,  0.3>
    0.9, <0.1,  0.8>
    1.0, <0.3,  0.1>

#declare WVEnv = function(Freq, Phase, Amp, Tick, SRate){
    Amp * Envelope(Ramp(Freq, Phase, Tick, SRate)).y,
    Amp * Envelope(Ramp(Freq, Phase, Tick, SRate)).x
Two spline envelope



With the ADSR I've also touched clocks and gates a bit. Time to explore that direction and look into clocks and sequencers.

There is also one bigger wave form item I have not looked into, frequency/phase modulation.