(max) amplitude modulation, drawing your lfo waveform

amplitude modulation, ring modulation

Revisiting our synthesis terminology, both amplitude modulation (AM) and ring modulation (RM) synthesis involve multiplying the amplitude of two signals together. The important difference is that RM involves two bipolar signals (oscillators moving between +/-1), and AM requires that one of the signals be unipolar (oscillating between 0. and 1.). With RM, any adjustment to either signal affects the resulting signal amplitude. If your modulating signal is 0. amplitude, the result of the modulation is 0. amplitude (anything times 0. = 0.). Creating an effective lfo modulation effect requires the ability to control the depth of the modulator signal independent of the output signal amplitude. To achieve that, you must offset the amplitude of the modulating signal by subtracting it’s amplitude from 1.

lfo amplitude modulation

also uses <WaveformSynthWorking2~.maxpat> (used for previous synths)

<BasicWaveformPitchSynthAM.maxpat> implements simple AM synthesis at LFO rates in the [p ampMod] subpatcher. AM synthesis can be implemented in the parent patcher (without any changes to the synth subpatcher) because it addresses the total of all notes being played, unlike pitch modulation. Pitch modulation had to be implemented in the synth subpatcher, since it affects each note playing in an individual way based on the conversion of MIDI note to frequency.

[p ampMod] has four inlets: audio signal to be modulated, lfo rate, lfo depth, and lfo waveform select. lfo rate and depth expect an input range of 0 – 127 (from a MIDI CC), and then scales that input to a frequency range of 0 – 50, and a depth of 0 – 1. You can easily adjust the input and the scaling to whatever you like. My assumption is that this is easiest to control from an external MIDI controller, although I didn’t hook up MIDI CC’s to do that.

Adjusting the amplitude offset of the modulating signal represents the major work being done in the [p ampMod] subpatcher. When a message the subpatcher gets a message to adjust lfo depth (amplitude), it takes the scaled amplitude and subtracts it from 1. to get the offset amplitude to add back to the modulating signal. The lfo depth/amplitude is multiplied to the output of the lfo oscillator, and then the result of that operation has the offset added to it.

Consider the following examples. If the lfo depth is 0 (zero), 1 – 0 = 1 for the offset. The result of the depth multiplied with the lfo signal will be 0, but with the offset of 1 added to the modulation signal before it is multiplied with the input signal, the result is the input signal unchanged. (input signal * 1 = input signal). If the lfo depth is .5, the lfo output will be .5, and the offset of .5 will be added to the modulated signal before multiplication. The output max amplitude of the AM operation is always the same as the input amplitude. Whatever amplitude is reduced through the lfo depth is restored by the offset.

drawable waveforms

I’ve added one more feature to the [p ampMod] subpatcher. Inside that patcher is the [p ampLFOscillators] subpatcher. It has the same basic features of the previous pitch modulation subpatcher, in that it is basically a waveform selector. I’ve added a breakpoint function editor [function] paired with [line~], which can act as an oscillator. When [line~] finishes outputting the ramp signal that is described by all pairs of breakpoints and times, it sends a bang out its right outlet. The bang message can be sent back to retrigger the function editor to output its ramp coordinates again, creating a repeating function waveform that you specified in the editor.

To make the process responsive to changes in the lfo frequency rate, you must do a little conversion from audio to message rate, and then convert frequency to period (T) and multiply by 1000 to convert to milliseconds. The process with objects:

  • the incoming lfo frequency at audio rate comes into a [number~] object.
  • the [number~] object is set to permit signal monitoring mode only. It will read the audio signal and send it as a float out the right outlet at the interval specified in the inspector (last property in the list). The output is the current frequency expressed as a float.
  • the frequency is sent to a reverse divide object [!/ 1.], with 1. divided by the frequency equalling the corresponding Period (T), or the length of time in seconds it takes to complete one cycle at the specified frequency.
  • the Period is multiplied by 1000 to get the Period in milliseconds.
  • the Period in milliseconds combined with a setdomain prefeix [prepend setdomain] and sent to the function editor. The function editor is now adjusted so that it’s length (domain) is the same as one cycle at the specified lfo frequency.

You can draw any arbitrary shape in the function editor, but you must remember that you must have breakpoint values at the very beginning and very end so that the length of the function described matches the domain of the editor. I’ve saved two shapes as a 1/10th saw and 1/2 saw. You can do more, and you can add inputs to select the different presets.

true sawtooth and triangle waveforms

We’ve been using anti-aliased sawtooth, triangle and square waves for audio-rate synthesis, and we’ve been limited to sawtooth waves that go up with saw~. For LFO purposes we don’t have to worry about aliasing, so we can use truer waveforms, which Max provides. I’m holding off on [train~] for pulse/square waves, but I’m using [triangle~] and [phasor~]. [triangle~] actually can provide sawtooth-like waves because you can adjust where the max point falls within half of the period. Using 0.5 creates a true triangle waveform. [phasor~] outputs a rising ramp from 0 going up to 1, and is so named because it is often used to drive oscillators or audio buffers by specifying the phase location. In fact, [triangle~] expects a phase signal to drive it, and [phasor~] operating at the lfo frequency provides it. For sawtooth waves, the output of the first [phasor~] in the subpatcher is subtracted from 1, reversing the signal to go down from 1 to 0. The second [phasor~] provides an up signal. The [umenu] in the parent patcher includes the options for saw down and saw up.

why does it sound like the AM lfo doubles in speed?

When the lfo depth goes above .5 you will start to notice an apparent doubling of the tremolo rate (the lfo rate). This doubling happens as the offset is reduced from the modulating signal, and the modulating signal oscillates above and below 0 amplitude. Each positive AND negative deviation produces an audible “bump” that is heard as distinct. There is a way to fix this issue, but I’ll leave it to you. (and I can go over it in class)


Leave a Reply