CISC181 lab04b, Fall 2005
(section 010-012, Instructor: P. Conrad)

Introduction

This lab is an introduction to project 1b, the WavMaker project.

The main purpose of this lab is to get you ready to do project 1b. In fact, when you finish this lab, you'll be already part of the way done with project 1b.

You should choose which version of project1 you want to do before you start this lab!!!

There are two alternatives for project 1:

proj1a.html drawingMaker simple line drawings for the web from C++ code
proj1b.html wavMaker make sound files from C++ code

Before starting this lab, you should look over the descriptions of both projects and decide which one you want to complete.

Completing one or the other does not "lock you in"; you are permitted to change your mind, and you are permitted to "mix and match".

But if you change your mind, or "mix and match", you'll end up doing more work, and not getting any extra credit. Once you've completed either lab4a or lab4b, you are half-way done with the corresponding project. Switching horses in mid-stream will cause you to have to start over from square one, and not earn you any extra credit. So, choose wisely.

Learning objectives common to both projects:

By the time you finish either version (lab04a + proj1a, or lab04b + proj1b), you'll have demonstrated your ability to:

  1. Write data to the screen using cout, and to external files using ofstream objects.
  2. Modify existing function and create new functions that use parameters of type int and double (passed by value), and call these functions where appropriate.
  3. Modify and create functions that pass parameters of type ostream (by reference).
  4. Use for loops, nested for loops and if/else.
  5. Use Makefiles and separate compilation to manage a project that is moderately complex in size.
  6. Make files available on the web.

Learning objectives present only on lab04a/proj1a (drawingMaker)

If you choose lab04a + proj1a, the drawingMaker project, you'll also acquire the following skills.

  1. Using gnuplot to create graphics.
  2. Controlling the creation of gnuplot files and data file for gnuplot from a C++ program.
  3. Basic principles of working with 2D graphics (e.g. using sine and cosine to determine x, y coordinates, and scaling and translating figures in a cartesian plane.)

Folks with some background (or interest in) drawing and visual art may find this project particularly interesting, but it is not necessary to have that background to be successful.

Learning objectives present only on lab04a/proj1a (wavMaker)

If you choose lab04a + proj1a, the wavMaker project, you'll also acquire the following skills:

  1. The difference between big-endian and little-endian byte orderings
  2. Working with sample size and bit rate in digital audio
  3. Generating simple sounds by the use of sine waves
  4. Concepts of frequency and amplitude, and their relation to musical concepts of pitch and volume.
  5. Use of the memcpy function with (void *) pointers.

Folks with a background (or interest in) music may find this project particularly interesting, but it is not necessary to have that background to be successful.

Step-by-Step Instructions, lab04b
(Nothing to turn in for this step: 0 pts of total, 0 pts complete when done)

Step 1: Introduction to WAV files

What is a WAV file? (required reading)

A WAV file is a way of representing sound as a sequence of bits. WAV files can be placed on web sites, hard drives, etc. and then played with software that understands how to turn the bits back into a signal that can be played through a sound card. A partial list of programs that can play WAV files appears in the table below:

Example software that can play WAV files
Operating system
Software Package
Windows
Windows Media Player
Windows, Mac OS
Apple QuickTime
Linux
mplayer

In the folder lab04b, there is a file called 4secondA440.wav. Try clicking on it. If your web browser is configured properly, you should hear a tone for five seconds. That tone is the musical note "A", played as a pure sine wave that cycles 440 times per second.

It is not necessary to understand music notation to do this lab and project, but in case you do know music notation, here is the representation of this note:

Picture Of Whole Note A440 In Music Notation

There are two other files: 4secondA220.wav, and 4secondA110.wav. These files are also the musical note A, but each at 1/2 the frequency of the preceding one, and so the notes sound "an octave lower". The musical note that corresponds to 110Hz is the A string on a guitar, which is two octaves lower in pitch than the A string on a violin. Listen to each of these, so you know what "lower and higher" sound like:

Each of these musical notes was generated from a C++ program that generated cycles of a sine wave and wrote those bits to a file. In lab4b and project1b, you are going to learn how those C++ programs work, and write your own C++ programs to do each of the following:

"Basic principles of digital audio" or "Our friend, the sine wave" (required reading)

In order to work with WAV files, we'll first need to review some basic principles of digital audio, and what a sine wave is.

WAV is one example of how to encode audio in digital format. Other examples of digital audio formats include MP3 and Ogg Vorbis. While these formats differ, there are a few basic principles that are common to all audio formats. Those are covered in this section.

First, there is the idea of representing sound as a signal. For our purposes, a signal is a function f(t) that varies with time (you may learn a more general definition in an engineering course; this definition is a simplification that will "work for us".) For example, the signal that you hear when you listen to the 4secondA440.wav file is a sine wave that goes through its entire "cycle" 440 times every second.

One cycle of a sine wave is illustrated in the graph below. As the picture shows, in a single cycle of the sine wave, the value of sin(x)

plot of sin(x) from 0 through 2π
This graph was produced by the code in sineWaveOneCycle.gnuplot

 

What the difference between y = sin(x) and f(t) = sin(t) ?

For our purposes, not much. Please note that when we graph a sine wave, we typically use sin(x); since that corresponds to our notion of an x-axis and a y-axis, so that is what all the gnuplot pictures here show. However, to emphasize that the sine wave is a signal that varies with time, we typically use the variable t instead of x and write f(t)=sin(t). It's just a change of variable name—nothing more. We'll use the f(t) notation for the most part, and use the y = sin(x) notation only when we are showing graphs.

A more convenient sine wave: f(t) = sin(2πt)

The sine wave above goes through one cycle between 0 and 2π. However, it is more convenient if the sine wave goes through one cycle between 0 and 1. The formula f(t) = sin(2πt) does exactly that, as this graph shows:

plot of f(x) = sin(2π * x)
This graph was produced by the code in sineWaveOneCycleZeroToOne.gnuplot

 

What about the idea of frequency?

Let's go back to our 4secondA440.wav WAV file. Suppose that t represents the time on a stopwatch that starts counting up from zero at the moment the file starts playing. By the time the stop watch reaches 1 second, the value f(t) has gone from zero, up to 1, then down to -1, then back up to zero again—that is, one cycle of the sine wave—four hundred and forty times. We call this the frequency of the sine wave, and we measure it a unit called Hertz, which is the same thing as "cycles per second".

440 cycles is too many to show on a graph, so we can't show a graph of A440. However, we can show a graph of "one second's worth" of 4secondA110.wav:

 

plot of sin(110 * 2 π * x )
This graph was produced by the code in sineWave110Hz.gnuplot

How do we turn this into C++ code?

So, to generate the file A440.wav, we need to generate "samples" on this curve between 0 and cycles through the shape of a sine wave (from 0 to 2π) four hundred and forty times per second, and write hose samples to the WAV file. There are two other issues though. First, we need to consider "how many samples per second"?

CD quality audio uses 44100 samples per second; that is, when you play a CD, every 1/44100 of a second, a sixteen bit sample for each channel is converted into sound.

(44100 may seem like a very strange number, but there is some logic to it. I used to know the exact reason, but I've forgotten it, and I wasn't able to find the answer quickly from a web search. All I remember is that there is "a reason". A free candy bar to the first CISC181 student who finds the answer and emails it to me.)

We are going to use a number which is one-fourth of that, or 11025 samples per second. So, we need to increment "time" (or the variable t) in units of 1/11025 of a second, and at each increment, take a "sample" of the sine wave, and write it to our wave file as a 16-bit number.

The following pseudocode illustrates the idea:

our first pseudocode for generating 4secondA440.wav
open the file "4secondA440.wav" for output;

let numSamples be the number of samples that are needed
  for five seconds of audio;
  
double t = 0;
double sample;
for (int i=0; i<numSamples; i++)
{
   t += 1.0/11025.0;
   sample = sin(440.0 * 2 * M_PI * t);
   write sample to the file 4secondA440.wav
}

close the file

Seems simple enough right? Well, there are a few details we have to take care of.

A few sticky details: file headers, quantization, binary output, byte ordering

First, a WAV file has a particular format. In addition to the bits that represent the samples of the sine wave, we also have to write a few extra bytes at the start of the file to let the software that reads the file know things like:

A note about that last bullet: we can use Hz to stand both for the sample rate, and for the frequency of the sine wave. Due to something called the Nyquist theorem, the sampling rate must be at least double the highest frequency present in the signal in the file. So, since our sampling rate is 11025Hz, we can't hope to represent any "note" higher than 5012.5Hz. (We'll stay at frequencies well below that.)

We'll cover how to write the WAV headers later in this lab.

Second, instead of a number between -1 and +1, which is how a mathematical sine wave is represented, we are going to be representing the sine wave as a "signed 16 bit integer", i.e. a number between -32768 and 32767. We are going to cheat a bit, and use only -32767 through + 32767; we'll simply multiply the value produced by the sine wave by 32767.0, use the floor() function to throw away any value after the decimal point, and assign the result to a "signed short"; that's a special kind of integer variable that only has 16 bits.

Finally, we need to take care of byte ordering. WAV files use something called little-endian byte ordering. The strauss.udel.edu system uses big-endian byte ordering. That means we have to swap the bytes of our 16-bit values. We'll cover byte-swapping in Step 2 of this lab.

Format of a basic WAV file (required reading)

There are many different formats that can appear in a WAV file, and if you writing software that can read WAV files, you have to take many of those into account. We are writing software to generate (i.e., write) WAV files, so we can focus on one very simple instance of the WAV file format. We'll call this a "basic WAV file".

Our "basic WAV file" will be mono (not stereo, i.e. 1 channel only), will use 16 bits per sample, and will be encoded at 11025Hz (i.e. 11025 samples per second.) It starts with a twelve-byte "Resource Interchange File Format (RIFF)" header, which is followed by a 24 byte "format chunk", and a variable length "data chunk".

The table on the right below shows the format. It may all sound and look very complicated. In fact, it is quite simple. We will always write a WAV file with a simple variable on the pseudocode shown on the left below. The only thing that will ever really change is the code that generates the actual sound samples.

our final pseudocode for generating 4secondA440.wav
open the file "4secondA440.wav" for output;

const int samplesPerSecond = 11025;
double durationInSeconds = 4.0;
numSamples = durationInSeconds * samplesPerSecond;

// Write the header to the file

   Write 4 ASCII bytes: "RIFF";
   Write a 32 bit value: (numSamples * 2) + 36;
   Write 32 bytes (always the same for 11025,16-bit,mono);
   Write a 32 bit value: (numSamples * 2);
      
// Write the samples to the file
  
double t = 0;
double sample;
for (int i=0; i<numSamples; i++)
{
   t += 1.0/11025.0;
   sample = sin(440.0 * 2 * M_PI * t);
   convert sample to 16 bit integer;
   write sample; 
}

close the file;
format of a WAV file
The first 12 bytes
(RIFF header)
 
4 ASCII bytes: "RIFF"
32-bit integer, little endian format
length of remainder of file
(excluding the first 8 bytes)
4 ASCII bytes
"WAVE"
The next 24 bytes
(format chunk)
4 ASCII bytes
"fmt "

32-bit integer,
little endian

16
(size of remainder of chunk)
two 16-bit integers,
little endian
1, 1
(1 channel, no compression)
32-bit integer, little endian
11025
(samples per second)
32-bit integer, little endian
22050
(samples per second
times bytes per sample)
two 16-bit integers,
little endian
2, 16
(2 bytes per sample,
16 bits per sample)
The remaining bytes
(data chunk)
4 ASCII bytes
"data"
32-bit integer, little endian format
length of the data portion
(duration in seconds * 22050)
sequence of 16-bit integers,
little endian format
actual sound samples

 

 

 

Why are we using WAV and not MP3, or some other audio format? (optional reading)

There are many other audio formats, including MP3, and .au files. Most media players (e.g. Windows Media Player, Apple QuickTime Media Player) can support all three of those, along with many others. So why do WAV?

MP3 files are more commonly used than WAV files these days. However, the file format is quite a bit more complex, and requires understanding of principles such as time vs. frequency domain, fourier transforms, lossy and lossless compression, and other advanced signal processing and information theory concepts. If you want to learn about MP3 files, you can take CISC451 taught by Prof. Amer. From CISC181, the path to CISC451 takes you through CISC220 (data structures), and three math courses: MATH210 (discrete math), MATH241 (calc), MATH205 (stats).

The .au file format is a step in the other direction: it is actually even simpler than WAV files. It is a format that originated from Sun Microsystems, the makers of copland and strauss. I chose WAV files instead of .au because I thought that WAV files would be more familiar to CISC181 students.

A web link to learn more about WAV files (optional reading)

I turned to a web link for information about WAV files when I prepared this assignment. Although there is probably lots of source code available on the Internet for working with WAV files, I didn't use any of that; I started with this description of the format, and wrote the code from scratch. For this assignment, you should too.

If you are interested in learning more about digital audio, the following tutorial on digital audio may be of interest.

 

Step 2: Learning about Big-endian and Little-endian
(
30 pts of total, 30 pts complete when done)

Background about big-vs.-little endian

When generating WAV files from a C++ program, it is important to understand the concept of big-endian vs. little-endian.

Big-endian vs. little-endian refers to the way in which computer store the bits and bytes of integers. Some computers store the most significant byte first, and the least significant byte last. This is called "big endian". Others do it the opposite way, and this is called "little-endian".

Historical Notes (optional reading)

A historical note: the terms "big-endian" and "little-endian" come from a satirical novel by the British author Jonathan Swift called Gulliver's Travels. In this novel, the inhabitants of a certain place fight a war over whether to eat eggs starting at the big end, or the little end.

Here are some links with more about big vs. little endian, and Gulliver's Travels in case you want to know more (reading these is not required for the assignment; these links are just here for your edification and amusement.)

Encyclopedia type article on big vs. little endian http://www.webopedia.com/TERM/b/big_endian.html
Discussion of Gulliver's travels http://www.jaffebros.com/lee/gulliver/
An article drawing the links between the two http://www.maxmon.com/1726ad.htm

Here's what you do need to do (required reading)

Copy the program whichEndian.cc into your account and run it. Depending on where you run the program, you'll get different output. The left column below shows the output from strauss.udel.edu. The right column shows what happens when I run the same program on my Windows PC, equipped with cygwin (a Unix emulator for Windows, available for free from www.cygwin.com):

 

strauss.udel.edu my own PC (using cygwin)
> ls
whichEndian.cc
> make whichEndian
CC    -o whichEndian whichEndian.cc 
> ./whichEndian 
The value of x is   :65
The address of x is :ffbffad0
The value of c[0] is   :
The address of c[0] is :ffbffac8
The value of c[1] is   :
The address of c[1] is :ffbffac9
The value of c[2] is   :
The address of c[2] is :ffbffaca
The value of c[3] is   :A
The address of c[3] is :ffbffacb
Your system is big-endian.
> 
$ ls
whichEndian.cc
$ make whichEndian
g++     whichEndian.cc   -o whichEndian
$ ./whichEndian.exe 
The value of x is   :65
The address of x is :0x22efc4
The value of c[0] is   :A
The address of c[0] is :0x22efbc
The value of c[1] is   :
The address of c[1] is :0x22efbd
The value of c[2] is   :
The address of c[2] is :0x22efbe
The value of c[3] is   :
The address of c[3] is :0x22efbf
Your system is little-endian.
$ 

A few things to notice about this output:

  1. The value of x is the same in both cases.
  2. The address of x (printed in hexadecimal) changes; different systems will locate the same variable in different places in memory.
  3. On both systems, the value of x (65), when converted to character, ends up being the capital letter 'A'. The rest of the bits are all zero, resulting in the character '\0'. When you print '\0' as a character, you get no output at all. However, notice that on strauss, the big-endian system, the capital letter A ends up in the last byte of the array (c[3]), while on the little endian system, the capital letter A ends up in the first byte (c[0]).
  4. At the end of the program, the code prints out whether the system is big-endian or little endian.

Now look at the source code, and notice these things:

  1. The & operator is used to take the address of the variable x.
  2. The type of addressOfX is not int. It is a different type, called (int *) (pronounced: "int star").
  3. The name of the array c can be treated in three different ways, as can the variable addressOfX. See the comments in the source code for details. This is a very important point for being a successful C or C++ programmer, and is a central theme of the course.
  4. We use the memcpy() function to copy four bytes of data from the integer variable x into the character array c. We use type-casting to convert the (int *) and (char *) variables into (void *) variables. memcpy requires that its first two parameters be (void *) pointers, because we want to be able to copy any kind of data using memcpy, regardless of the type of the data.
  5. The first parameter of memcpy() is the destination of the copy, and the second parameter is the source. This may seem backwards, until you realize that it mirrors the order of an assignment statement:

    There is a subtle difference between a=b; and memcpy(a,b,1); but we'll cover that another day; the point I want here is that the direction of data copying is the same: right to left.
  6. When printing the address of a character (e.g. a char * value), you need to cast that value to a (void *). Otherwise, C++ sees a char * value, and treats the operation as "printing a string". In this case, you will not see the address of the character. Instead, C++ prints out all characters from that address until hitting a '\0' (the "null terminator" or "stop sign" of C-style strings.
  1. On both systems, the value of x (65), when converted to character, ends up being the capital letter 'A'. The rest of the bits are all zero, resulting in the character '\0'. When you print '\0' as a character, you get no output at all. However, notice that on strauss, the big-endian system, the capital letter A ends up in the last byte of the array (c[3]), while on the little endian system, the capital letter A ends up in the first byte (c[0]).
  2. At the end of the program, the code prints out whether the system is big-endian or little endian.

One more thing to check out (required reading)

Note the code in the final if/else if/else test. There are three possibilities for this test; the third one prints a message indicating that there is something wrong with the program:

if ( c[0] == '\0' &&  c[1] == '\0' &&  c[2] == '\0' &&  c[3] == 'A')
  {
    cout << "Your system is big-endian." << endl;
  }
else if ( c[0] == 'A' &&  c[1] == '\0' &&  c[2] == '\0' &&  c[3] == '\0')
  {
    cout << "Your system is little-endian." << endl;
  }
else
  {
    cout << "I can't tell is your system is big or little endian" << endl;
    cout << "Something is probably wrong with the program." << endl;
  }

Since we might assume that systems are only big-endian or little-endian, you might think that we could just write the code this way:

if ( c[0] == '\0' &&  c[1] == '\0' &&  c[2] == '\0' &&  c[3] == 'A')
  {
    cout << "Your system is big-endian." << endl;
  }
else 
  {
    cout << "Your system is little-endian." << endl;
  }

So why do it the first way? The reason is that we want to practice what is known as "defensive programming". Just like "defensive driving", we anticipate things that could go wrong, and protect ourselves against those. For example, in the second version of the code above, if I were to change the initial value of x=65; to some other number, but forget to change the condition in my if test, my program might report "little-endian" when the system was in fact "big-endian". The first version is more "robust" to such problems, meaning it doesn't "break as easily".

Your job: write the lab04bs2.cc program

The lab04bs2.cc program shows that you understand bigEndian and littleEndian byte-order.

This program will contain five functions:

function prototype purpose what you have to do
bool isLittleEndian(); return true if system is littleEndian, otherwise false nothing; the function is provided for you
(copy it from endian.cc)
bool isBigEndian(); return true if system is bigEndian, otherwise false write this function (use isLittleEndian()
from endian.cc as a model)
int toLittleEndian(int x); convert int to littleEndian format to system's default format (if system is already littleEndian, returns the value of x unchanged, otherwise, moves around the bytes). nothing: function is already provided for you (copy it from endian.cc)
int toBigEndian(int x); convert int from system's default format to bigEndian (if system is already bigEndian, returns the value of x unchanged, otherwise, moves around the bytes) write this function (use toLittleEndian()
from endian.cc as a model.)
int main(void); a driver program that tests the four functions above. a driver program that tests the four functions above. Use whichEndian.cc as a model.

Scripting this program for submission

Generate a script called lab04bs2.txt in which you cat, compile, and run your lab04bs2.cc program. You'll upload and print these as part of your final submission step.

Step 3: Generating a simple WAV file: 5 seconds of A-440.
(Nothing to turn in for this step: 0 pts of total, 30 pts complete when done)

 

This part of the lab does not involve anything that you have to turn in for credit. So, you could skip it, and neither your instructor nor your TA would have any way to know. Well, that's not entirely true.. we'd figure out it out pretty quickly if you were sort of clueless about all the things you could learn by actually doing it. So, I'd encourage you to go through all these steps anyway, even though there is nothing here that is going to be graded. :-)

In this step, your job is to experiment with the makeWav4secA440.cc and makeWav.cc programs from the lab04b directory. You need to try to understand the code in these files as best you can before moving on to creating your own code to generate WAV files.

Look first at the makeWav4secA440.cc file. Start with the int main(void) function, and work through the file from there. You'll notice that the main is very short. It opens the file 4secondA440.wav for output with the code:

int fd = open("4secondA440.wav", O_RDWR | O_CREAT );

This code is different from what we typically do to write an output file in C++ (namely, using an ofstream object). In this case, we are using the lowest level routine possible to open the file; we are directly calling a Unix system call. This is because, when writing binary files, we want to literally write out certain binary values. The ofstream object is intended for writing text files, not binary files, and it will do special things with certain ASCII values (such as the newline characters.) Since we don't want that, we use these low level routines instead.

In a similar fashion, to write to the file, we use:

write(fd,&sixteenBitSample,2);

This code writes 2 bytes (from a variable of type signed short) which represent a 16-bit sample (in little endian format.) The write() system call is the lowest level routine available in Unix to write files.

(As an aside, the library of routines we access through #include <iostream>, at its root, is just a layer of abstraction built on top of system calls such as open() and write(). )

The file makeWav4secA440.cc has some limitations. It has hard-coded values for the frequency, duration, and filename of the generated file. Look, by contrast, at makeWav.cc. This file uses command line arguments to allow the user to choose the frequency, duration, and filename. Look at the header for the main function:

code from makeWav.cc
int main(int argc, char *argv[])
{
...

Note that instead of int main(void) this main is declared with formal parameters argc and argv. The parameter argc is the "argument count". When you run a C or C++ program, the argument count is the number of things on the command line. So, if you run a program with:

./a.out

then the argument count is 1. Typically, a program designed to be run like that would start with int main(void). However, the makeWav.cc program is designed to be run with a command line such as:

./a.out 3 220 threeSecondA220.wav

That command line would have an argument count (argc) equal to 4. The various arguments are placed into four elements of the array argv, each of which is a character string, so argv[0] is "./a.out", argv[1] is "3", argv[2] is "220" and argv[3] is "threeSecondA220.wav".

The makeWav.cc program checks the command line arguments, and converts some of them from string values to numeric values by doing the following. Note the use of atof() to convert from string data (in ASCII) to floating-point representation (in this case a double), and the use of atoi() to convert from ASCII to integer.

makeWav.cc code for command line args
 if (argc!=4)
    {
      cerr << "Usage: " << argv[0] << " numSeconds frequency filename" << endl;
      exit(1);
    }

  double duration = atof(argv[1]);

  if (duration <= 0 || duration > 60)
    {
      cerr << "Error: numSeconds must be between 1 and 60" << endl;
      exit(2);
    }

  const int samplesPerSecond = 11025;

  int frequency = atoi(argv[2]);

  if (frequency < 20 || frequency > (samplesPerSecond / 2))
    {
      cerr <<"Error: frequency must be between 20 and 5012" << endl;
      exit(2);
    }

 

One style problem with this code is the embedding of the number 5012 in the error message. It would be better to calculate the value of the upper limit of the frequency once, and use that value in both the if test, and in the error message. The reason for the number 5012 is that it is one half the sampling rate; the Nyquist sampling theorem tells us that we cannot put any frequency higher than 1/2 the sampling rate into the output, or that frequency will get lost.

Experiment with running the makeWav binary several times, to generate WAV files of various frequencies and durations, and make sure you can hear the output.

Listening to WAV files on the Sun Rays, and on Windows

The following link explains how to listen to WAV files on the Sun Rays:

http://www.udel.edu/topics/os/unix/sunray/aboutAudio.html

On Windows, you should be able to open the files with Windows Media Player, or any one of several other Windows audio playback programs. You might be able to just open the SSH Secure File Transfer window, navigate to the directory where the WAV file is located, and double click on it to open it. If that doesn't work, you can download the file to your PC and then open it directly in Windows Media Player.

If at first when you try to listen to the files you get errors such as "permission denied" (on the Sun Rays) or "file transfer failed" (on Windows), it may be because you need to do a chmod command on the file to make it readable. The command you need is:

chmod 755 file.wav

In step 5 (the Morse code step), we introduce some C++ code that takes care of the chmod command right in the C++ program itself!

 

Step 4: Generating telephone effects (dialtone, DTMF, busy signal)
(30 pts of total, 60 pts complete when done, files: lab04bs4.cc, lab04bs4.txt, dialBusy.wav)

Telephone signals are typically made up of combinations of two sine waves. When you use a touch tone phone, the sounds you hear when you press each number on the keypad are called DTMF signals, for "dual tone, multiple frequency". To generate a DTMF tone, all you have to do is generate two sine waves on different frequencies, and then add them together.

The following web site contains a description of these tones:

http://www.nts-technologies.org/articles/elec/%28Telephony%29%20-%20DTMF%20Reference.txt

The program makeDTMF.cc illustrates the principle. However, it has a few style issues. It uses far too many "magic numbers"—numbers that appear "out of nowhere" such as the numbers 697, 1209, 1336 and 1477 in the following segment of code from the main() function:

code from makeDTMF.cc
  writeDTMFSamples(fd, toneDuration, 697, 1209);
  writeSilenceSamples(fd, toneDuration);

  writeDTMFSamples(fd, toneDuration, 697, 1336);
  writeSilenceSamples(fd, toneDuration);

  writeDTMFSamples(fd, toneDuration, 697, 1477);
  writeSilenceSamples(fd, toneDuration);
         

What would be better would be to use constants such as:

sample of how code might be re-written in better style
  
  // define frequencies for DTMF touch tone
  // telephone signals.  1 on keypad is combination
  // of sine wave on DTMF1a and DTMF1b, etc.
  
  const int DTMF1a = 697; const int DTMF1b = 1209;
  const int DTMF2a = 697; const int DTMF2b = 1336;
  const int DTMF3a = 697; const int DTMF3b = 1477;


  writeDTMFSamples(fd, toneDuration, DTMF1a,  DTMF1b);
  writeSilenceSamples(fd, toneDuration);

  writeDTMFSamples(fd, toneDuration, DTMF2a, DTMF2b);
  writeSilenceSamples(fd, toneDuration);
  // etc...
         

Then, perhaps one might write a function to generate the various tones, as in the following example. Note that because the int and char data types can be used somewhat interchangeably, one can pass '*' or '#' (note the single quotes, not double quotes) as an argument to the function, and include those values in the case labels of the switch statement.

sample of an even better style of code for DTMF generation
  
  void writeDTMFtouchtone(int fd, double toneDuration, int num)
  {
      // @@@ TODO: use a switch statement for all
      // the cases of 0-9.  Use '*' or '#' in quotes for
      // the star and pound keys; note that the ASCII values
      // don't overlap with the digits 0-9


  }  

  
  ...
  // touch tones for "12*" (press 1, then 2, then the star key)

  writeDTMFtouchtone(fd, toneDuration, 1);
  writeSilenceSamples(fd, toneDuration);

  writeDTMFSamples(fd, toneDuration, 2);
  writeSilenceSamples(fd, toneDuration);

  writeDTMFSamples(fd, toneDuration, '*');
  writeSilenceSamples(fd, toneDuration);

  // etc...
         

Your job is to rewrite makeDTMF.cc so that it uses these "style improvements":

Call your new file lab04bs4.cc. Change the main into one that simulates dialing an actual 10 digit phone number, then getting a busy signal (let the busy signal go on long enough that you can tell it was a busy signal.) Call your WAV file "dialBusy.wav". You also need a script file lab04bs4.txt.

Step 5: Generating some simple Morse Code: -.-. .. ... -.-. (CISC)
(30 pts of total, 90 pts complete when done, files: lab04bs5.cc, lab04bs5.txt, CISC181.wav)

Look at the file makeMorse.cc. This file generates the letters CISC in Morse code.

Your task: add the digits 181 in Morse Code. You can find a table of Morse Code values at:

http://www.babbage.demon.co.uk/morseabc.html

Call your program lab04bs5.cc. Generate a file CISC181.wav, and a script file lab04bs5.txt

Step 6: Creating a tar file of your finished product.
(10 pts of total, 100 pts complete when done, file: lab04b.tar)

Don't start this step until you have finished all your programming, and are ready to submit your work. However, don't wait until this last minute; you may need some time to figure this part out.

A "tar" file is a file that allows an entire collection of files to wrapped up into a single file.

You may be familiar with "zip" files, which are often used to distribute software packages on Windows PCs, or "sit" files which are used to distribute files on Macintosh computers. The files known as "tar" files are similar.

(A bit of history: "tar" originally stood for "tape archive". It's still the format sometimes used to store backup files on tape drives.)

To create a tar file from your lab04b files, follow these steps:

  1. Change into your ~/cisc181/lab04b directory and do a "make clean". This is very important! If you don't do this step first, you'll end up with a tar file that will be huge; this will annoy your TAs greatly and cause your final grade to be lowered.
  2. Change directory to one directory above your ~/cisc181/lab04b directory. You can you can use "cd .." to move up one level from your current directory, or you can use "cd ~/cisc181" to go there directly.
  3. Use the command:
      tar -cvf lab04b.tar lab04b
    This command says "create a new tar file named lab04b.tar"
    (that's the -cvf lab04b.tar part) and put all the files from the subdirectory lab04b into it.
  4. To test your file, create a new subdirectory called ~/cisc181/test, and cd into it.
    Copy your lab04b.tar file into that directory. Then use the command:
      tar -xvf lab04b.tar
    This command should expand the lab04b.tar file, and leave you with a new subdirectory ~/cisc181/test/lab04b that contains all of the files from your lab04b directory. Change your working directory into that directory and use the "ls" command to list your files. Then, try the "make clean", "make all" and "make install" commands and make sure that everything still works properly. If so, then you can now submit "lab04b.tar" as your submission to WebCT.

    Note: you have reached a milestone in your development as a Unix programmer: you have created your first "package". A package is a tar file that contains source code, and a Makefile. Sometimes these packages are called "tarballs", and they are how professional Unix programmers distribute their software.

Finishing up and Submitting

  1. Create a directory ~/public_html/cisc181/lab04b
  2. Copy all your .wav files into this directory (but NOT your C++ source code!)
  3. Create script files for the steps that need them: lab04s2.txt, lab04bs4.txt, lab04bs5.txt
  4. Submit the .cc and .txt files for those three steps to WebCT.
  5. Print your three script files, staple together and give to your TA.

Next Steps

You will find the description of Project 1 in the project directory of the course web site.