Lab 2 - Wavetable Generators, AM/FM Modulation
Lab 2 - Wavetable Generators, AM/FM Modulation
fmin =
fs
D
D=
fs
fmin
For example, if fs = 8 kHz and the smallest desired frequency is fmin = 10 Hz, then one must choose
D = 8000/10 = 800. The D-dimensional buffer holds one period at the frequency fmin of the desired
waveform to be generated. The shape of the stored waveform is arbitrary, and can be a sinusoid, a square
wave, sawtooth, etc. For example, if it is sinusoidal, then the buffer contents will be:
w[n]= sin
2fmin
fs
n
= sin
2n
n = 0, 1, . . . , D 1
Similarly, a square wave whose first half is +1 and its second half, 1, will be defined as:
w[n]=
+1 ,
if
0 n < D/2
1 ,
if
D/2 n < D
To generate higher frequencies (with the Nyquist frequency fs /2 being the highest), the wavetable is
cycled in steps of c samples, where c is related to the desired frequency by:
f = c fmin = c
fs
D
c=D
f
DF,
fs
F=
f
fs
where F = f /fs is the frequency in units of [cycles/sample]. The generated signal of frequency f and
amplitude A is obtained by the loop:
repeat forever:
y = A w[q]
q = (q + c)mod(D)
(2.1)
The shift c need not be an integer. In such case, the quantity q + c must be truncated to the integer
just below it. The text [1] discusses alternative methods, for example, rounding to the nearest integer,
or, linearly interpolating. For the purposes of this lab, the truncation method will suffice.
The following function, wavgen(), based on Ref. [1], implements this algorithm. The mod-operation
is carried out with the help of the function qwrap():
//
//
//
//
21
y = A * w[*q];
*q = qwrap(D-1, (int) (*q+c));
return y;
}
// -------------------------------------------------
We note that the circular index q is declared as a pointer to int, and therefore, must be passed by
address in the calling program. Before using the function, the buffer w must be loaded with one period
of length D of the desired waveform. This function differs from the one in Ref. [1] in that it loads the
buffer in forward order and cycles the index q forward.
#define D 4000
#define N 128
short fs=8;
float c, A=5000, f=1;
float w[D];
float buffer[N];
int q=0, k=0;
//
//
//
//
fs = 8 kHz
f = 1 kHz
wavetable buffer
buffer for plotting with CCS
// ---------------------------------------------------------------------------------void main()
{
int n;
float PI = 4*atan(1);
initialize();
sampling_rate(fs);
// audio_source(LINE);
while(1);
}
22
write_outputs(yL,yL);
// audio output
}
// ----------------------------------------------------------------------------------
The wavetable is loaded with the sinusoid in main(). At each sampling instant, the program does
nothing with the codec inputs, rather, it generates a sample of the sinusoid and sends it to the codec,
and saves the sample into a buffer (only the last N generated samples will be in present in that buffer).
Lab Procedure
a. Create a project for this program and run it. The amplitude was chosen to be A = 5000 in order to
make the wavetable output audible. Hold the processor after a couple of seconds (SHIFT-F5).
b. Using the keyboard shortcut, ALT-V RT, or the menu commands View -> Graph -> Time/Frequency,
open a graph-properties window as that shown below:
Select the starting address to be, buffer, set the sampling rate to 8 and look at the time waveform.
Count the number of cycles displayed. Can you predict that number from the fact that N samples
are contained in that buffer? Next right-click on the graph and select Properties, and choose FFT
Magnitude as the plot-type. Verify that the peak is at f = 1 kHz.
c. Reset the frequency to 500 Hz. Repeat parts (a,b).
d. Create a GEL file with a slider for the value of the frequency over the interval 0 f 1 kHz in steps
of 100 Hz. Open the slider and run the program while changing the frequency with the slider.
e. Set the frequency to 30 Hz and run the program. Keep decreasing the frequency by 5 Hz at a time and
determine the lowest frequency that you can hear (but, to be fair dont increase the speaker volume;
that would compensate the attenuation introduced by your ears.)
f. Replace the following two lines in the isr() function:
yL = (short) (A * w[q]);
q = (int) (q+c); if (q >= D) q = 0;
23
2.4. AM Modulation
Here, we use two wavetables to illustrate AM modulation. The picture below shows how one wavetable
is used to generate a modulating amplitude signal, which is fed into the amplitude input of a second
wavetable.
Aenv
A(n)
wenv
Fenv
y(n)
F
The AM-modulated signal is of the form:
where
The following program, amex.c, shows how to implement this with the function wavgen(). The
envelope frequency is chosen to be 2 Hz and the signal frequency 200 Hz. A common sinusoidal wavetable
sinusoidal buffer is used to generate both the signal and its sinusoidal envelope.
// amex.c - AM example
// -----------------------------------------------------------------------------------#include "dsplab.h"
// DSK initialization declarations and function prototypes
#include <math.h>
//#define PI 3.141592653589793
short xL, xR, yL, yR;
#define D 8000
float w[D];
short fs=8;
float A, f=0.2;
float Ae=10000, fe=0.002;
int q, qe;
float wavgen(int, float *, float, float, int *);
// -----------------------------------------------------------------------------------void main()
{
int i;
float PI = 4*atan(1);
q=qe=0;
for (i=0; i<D; i++) w[i] = sin(2*PI*i/D);
initialize();
sampling_rate(fs);
audio_source(LINE);
while(1);
24
}
// ------------------------------------------------------------------------------------interrupt void isr()
{
float y;
// read_inputs(&xL, &xR);
Although the buffer is the same for the two wavetables, two different circular indices, q, qe are used
for the generation of the envelope amplitude signal and the carrier signal.
Lab Procedure
a. Run and listen to this program with the initial signal frequency of f = 200 Hz and envelope frequency
of fenv = 2 Hz. Repeat for f = 2000 Hz. Repeat the previous two cases with fenv = 20 Hz.
b. Repeat and explain what you hear for the cases:
f = 200 Hz,
f = 200 Hz,
f = 200 Hz,
fenv = 100 Hz
fenv = 190 Hz
fenv = 200 Hz
2.5. FM Modulation
The third program, fmex.c, illustrates FM modulation in which the frequency of a sinusoid is timevarying. The generated signal is of the form:
x(t)= sin 2f (t)t
The frequency f (t) is itself varying sinusoidally with frequency fm :
f (t)= f0 + Am sin(2fm t)
Its variation is over the interval f0 Am f (t) f0 +Am . In this experiment, we choose the modulation
depth Am = 0.3f0 , so that 0.7f0 f (t) 1.3f0 . The center frequency is chosen as f0 = 500 Hz and the
modulation frequency as fm = 1 Hz. Again two wavetables are used as shown below, with the first one
generating f (t), which then drives the frequency input of the second generator.
25
// fmex.c - FM example
// -----------------------------------------------------------------------------------#include "dsplab.h"
// DSK initialization declarations and function prototypes
#include <math.h>
//#define PI 3.141592653589793
short xL, xR, yL, yR;
#define D 8000
float w[D];
short fs=8;
float A=5000, f=0.5;
float Am=0.3, fm=0.001;
int q, qm;
float wavgen(int, float *, float, float, int *);
// -----------------------------------------------------------------------------------void main()
{
int i;
float PI = 4*atan(1);
q = qm = 0;
for (i=0; i<D; i++) w[i] = sin(2*PI*i/D);
//for (i=0; i<D; i++) w[i] = (i<D/2)? 1 : -1;
initialize();
sampling_rate(fs);
audio_source(LINE);
while(1);
}
// ------------------------------------------------------------------------------------interrupt void isr()
{
float y, F;
// read_inputs(&xL, &xR);
// modulated frequency
y = wavgen(D, w, A, F, &q);
// FM signal
yL = yR = (short) y;
write_outputs(yL,yR);
return;
}
// ------------------------------------------------------------------------------------
Lab Procedure
a. Compile, run, and hear the program with the following three choices of the modulation depth: Am =
0.3f0 , Am = 0.8f0 , Am = f0 , Am = 0.1f0 . Repeat these cases when the center frequency is changed to
f0 = 1000 Hz.
b. Replace the sinusoidal wavetable with a square one and repeat the case f0 = 500 Hz, Am = 0.3f0 . You
26
will hear a square wave whose frequency switches between a high and a low value in each second.
c. Keep the square wavetable that generates the alternating frequency, but generate the signal by a sinusoidal wavetable. To do this, generate a second sinusoidal wavetable and define a circular buffer for
it in main(). Then generate your FM-modulated sinusoid using this table. The generated signal will
be of the form:
x(t)= sin 2f (t)t ,
f (t)= 1 Hz square wave
y(n)= x(n)+x(n)cos(2F0 n)= x(n) + cos(2F0 n)
(2.2)
y(n)= x(n)cos(2F0 n)
(2.3)
y(n)= x(n)+x(n)cos(2F0 n)= x(n) 1 + cos(2F0 n)
(2.4)
Lab Procedure
a. Modify the amex.c project to implement the ring modulator/tremolo effect. Set the carrier frequency
to f0 = 400 Hz and = = 1. Compile, run, and play a wavefile with voice in it (e.g., dsummer.)
b. Experiment with higher and lower values of f0 .
c. Repeat part (a) when = 0 and = 1 to hear the ring-modulator effect.
27
Y(f )=
1
X(f f0 )+X(f + f0 )
2
(2.5)
As f0 is chosen closer and closer to the Nyquist frequency fs /2, the shifted replicas begin to resemble
the inverted spectrum of X(f ). In particular, if f0 = fs /2, then,
Y(f )=
1
X(f fs /2)+X(f + fs /2)
2
Using the periodicity property X(f fs )= X(f ), we then obtain the equivalent expressions:
Y(f ) =
0f
1
X(f fs /2 + fs )+X(f + fs /2) = X(f + fs /2) ,
2
Y(f ) =
1
X(f fs /2)+X(f + fs /2 fs ) = X(f fs /2) ,
2
fs
2
fs
2
f 0
which imply that the positive (negative) frequency part of Y(f ) is equal to the negative (positive) frequency part of X(f ), in other words, Y(f ) is the inverted version of X(f ). This is depicted below.
Because in this case F0 = f0 /fs = (fs /2)/fs = 1/2, the carrier waveform is simply the alternating
sequence of 1:
cos(2F0 n)= cos(n)= (1)n
and the modulator output becomes
(2.6)
Lab Procedure
Modify the template.c program to implement the frequency-inversion or scrambling operation of
Eq. (2.6). This can be done easily by introducing a global index:
int q = 1;
and keep changing its sign at each interrupt call, i.e., after reading the left/right codec inputs, define the
corresponding codec outputs by:
yL = q * xL;
yR = q * xR;
q = -q;
28
Compile and run this program. Send the wave file JB.wav into it. First comment out the line q = -q,
and hear the file as pass through. Then, enable the line, recompile, and hear the scrambled version of
the file.
The scrambled version was recorded with MATLAB and saved into another wave file, JBm.wav. If you
play that through the scrambler program, it will get unscrambled. In Labs 3 & 4, we will implement the
frequency inversion in alternative ways.
2.8. References
[1] S. J. Orfanidis, Introduction to Signal Processing, online book, 2010, available from:
http://www.ece.rutgers.edu/~orfanidi/intro2sp/
[2] R. Chassaing and D. Reay, Digital Signal Processing and Applications with the TMS320C6713 and
TMS320C6416 DSK, 2nd ed., Wiley, Hoboken, NJ, 2008.
[3] F. R. Moore, Elements of Computer Music, Prentice Hall, Englewood Cliffs, NJ, 1990.
[4] C. Dodge and T. A. Jerse, Computer Music, Schirmer/Macmillan, New York, 1985.
[5] J. M. Chowning, The Synthesis of Complex Audio Spectra by Means of Frequency Modulation, J.
Audio Eng. Soc., 21, 526 (1973).
[6] M. Kahrs and K. Brandenburg, eds., Applications of Digital Signal Processing to Audio and Acoustics,
Kluwer, Boston, 1998.
[7] Udo Z
olzer, ed., DAFX Digital Audio Effects, Wiley, Chichester, England, 2003. See also the DAFX
Conference web page: http://www.dafx.de/.