Introduction
Amplitude Modulation (aka ring modulation) is one of the simplest synthesis techniques to implement, but one of the lesser used techniques. AM uses the multiplication of two signals to create new harmonics. The product will contain the sum and difference of all of the harmonics in the original waveforms. This comes from the combinations of the phase change in the original waveforms. If you take the difference of two trigonometric angle sum and difference identities you can see how the new frequencies are derived from the multiplied sines.
cos(\alpha - \beta) = cos(\alpha)cos(\beta) + sin(\alpha)sin(\beta) \\ cos(\alpha + \beta) = cos(\alpha)cos(\beta) - sin(\alpha)sin(\beta) \\ cos(\alpha - \beta) - cos(\alpha + \beta) = 2*sin(\alpha)sin(\beta) \\ sin(\alpha)sin(\beta) = \frac{1}{2}*(cos(\alpha - \beta) - cos(\alpha + \beta))As the frequencies are being shifted with sum and difference, these new harmonics can easily lose any harmonic relation they had in the original signals. For example, if a waveform with related harmonics of 100Hz, 200Hz and 500Hz is modulated by a 96Hz sine, the new harmonics will be at 4Hz, 104Hz, 196Hz, 296Hz, 404Hz and 596Hz. There are no simple harmonic relations in the new tone.
Amplitude Modulation Techniques
AM can be configured in several ways to get more consonant results. The C code below will show how to achieve the following three methods.
- By restricting the frequencies used in AM, a more useful result is obtained. If simple harmonic relations are maintained between the two source waveforms, harmonic relations will be maintained in the product.
- The original waveforms can be reintroduced into the output by adding two parameters. This can be very useful if one of the sources isn’t sinusoidal. y(t) = \alpha*(sin(\omega_1 t)*sin(\omega_2 t) + \beta*sin(\omega_1 t) + \gamma*sin(\omega_2 t))
- A simple harmonic tone generator can be created using feedback and self modulation.
Implementation
AM is usually implemented with a simple multiply and two source signals. Our first C example will create an AM oscillator with internal sqaure and sine wave oscillators. It will also implement gain parameters for the original waveforms.
// the samplerate in Hz float sampleRate = 44100; // initial phase float phaseSin, phaseSqu = 0.0; long tableSize = 8192; long tableMask = 8191; float squareTab[8192]; float sineTab[8192]; void InitTables(void) { // create sineTab from sin() and squareTab from 11 first harmonics of square wave for(i = 0; i < tableSize; i++) { sineTab[i] = sin((6.283185307179586 * i)/tableSize); squareTab[i] = sin((6.283185307179586 * i)/tableSize); squareTab[i] += sin((6.283185307179586 * i * 3.0)/tableSize)/3.0; squareTab[i] += sin((6.283185307179586 * i * 5.0)/tableSize)/5.0; squareTab[i] += sin((6.283185307179586 * i * 7.0)/tableSize)/7.0; squareTab[i] += sin((6.283185307179586 * i * 9.0)/tableSize)/9.0; squareTab[i] += sin((6.283185307179586 * i * 11.0)/tableSize)/11.0; squareTab[i] += sin((6.283185307179586 * i * 13.0)/tableSize)/13.0; squareTab[i] += sin((6.283185307179586 * i * 15.0)/tableSize)/15.0; squareTab[i] += sin((6.283185307179586 * i * 17.0)/tableSize)/17.0; squareTab[i] += sin((6.283185307179586 * i * 19.0)/tableSize)/19.0; squareTab[i] += sin((6.283185307179586 * i * 21.0)/tableSize)/21.0; } } void RingOsc(float *input, float *output, float frequency, float ratio, float ringGain, float sineGain, float squareGain, long samples) { long sample; float phaseIncSin, phaseIncSqu; float sinSamp, squSamp; // calculate for each sample in a block for(sample = 0; sample < samples; sample++) { // get the phase increment for this sample phaseIncSqu = frequency/sampleRate; phaseIncSin = phaseIncSqu * ratio; // calculate the waveforms sinSamp = sineTab[(long)(phaseSin * tableSize)]; squSamp = squareTab[(long)(phaseSqu * tableSize)]; // mix the output from ring, sine and square *(output+sample) = sinSamp * squSamp * ringGain + squSamp * squareGain + sinSamp * sineGain; // increment the phases phaseSin = phaseSin + phaseIncSin; if(phaseSin >= 1.0) phaseSin = phaseSin - 1.0; phaseSqu = phaseSqu + phaseIncSqu; if(phaseSqu >= 1.0) phaseSqu = phaseSqu - 1.0; } }
As stated earlier, a simple harmonic oscillator can be made by using a single sinewave oscillator, AM and feedback. Feedback needs to be kept under 1.0 or the algorithm will become unstable. As the feedback gain increases, more and more harmonics will be added. A small offset needs to be added to the feedback to give the sound an initial gain. Not a very rich technique, but worth knowing….
// the samplerate in Hz float sampleRate = 44100; // initial phase float phase = 0.0; float fbSamp; long tableSize = 8192; long tableMask = 8191; float sineTab[8192]; void InitTables(void) { // create sineTab from sin() for(i = 0; i < tableSize; i++) { sineTab[i] = sin((6.283185307179586 * i)/tableSize); } } void RingFBOsc(float *input, float *output, float frequency, float feedback, long samples) { long sample; float phaseInc; float sinSamp, squSamp; if(feedback > .95) feedback = .95; // calculate for each sample in a block for(sample = 0; sample < samples; sample++) { // get the phase increment for this sample phaseInc = frequency/sampleRate; // calculate the waveforms sinSamp = sineTab[(long)(phase * tableSize)]; // ring modulate with last output * feedback fbSamp = sinSamp * ((fbSamp * feedback) + 0.1) *(output+sample) = fbSamp; // increment the phase phase = phase + phaseInc; if(phase >= 1.0) phase = phase - 1.0; } }