Synthesis Theory I: Programming Lab 2
Command-Line Options




This lab demonstrates how to process command line arguments in C++, so that your programs can be more useful for different situations.

The program for generating noise used in lab 1 will be used as a starting point for this lab.

You may have noticed that in noise1.cpp, the output filename was always test.wav. If you wanted to run the program again, you had to rename the file or delete it, because the program would not overwrite the file by itself. But why run the computer a second time, anyway? It always outputs exactly one second of noise, so the file test.wav will always has the same contents.

To make noise1.cpp a more useful program, we will add command-line options to the program. This will allow the program to produce a wider range of output. First, lets plan on what would be useful parameters to be able to control after the program has been compiled into a command. I would suggest the following:

  1. be able to specify the name of the output soundfile.
  2. be able to control the duration of the output sound.
  3. be able to control the amplitude of the output sound.

For example, I would like to type the command:

   noise -a 0.1 -d 5  noise.wav
To create a soundfile called noise.wav containing 5 seconds of whitenoise at an amplitude of 0.1.

The following program demonstrates a C++ object called Options which can be used to manage the command-line arguments to a program. If you want to copy and paste the program to a file, here is the source code: noise2.cpp. The primarily new section of the code is highlighted in red in the following figure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "soundfile.h"

int main(int argc, char** argv) {
   Options options;
   options.define("a|amplitude=d:0.1",    "amplitude of output sound");
   options.define("d|duration=d:1.0",     "duration of output sound (seconds)");
   options.define("r|sample-rate=i:44100","set the sample rate of the output");
   options.process(argc, argv);
   if (options.getArgCount() != 1) {
      cout << "Usage: " << options.getCommand() 
           << " [-a amp][-d dur][-r srate] file.wav"
           << endl;
      exit(1);
   }

   int sampleCount  = (int)(44100 * options.getDouble("duration") + 0.5);
   int channels     = 1;
   int srate        = options.getInteger("sample-rate");
   double amplitude = options.getDouble("amplitude");
   const char* filename = options.getArg(1);

   SoundHeader header;
   header.setChannels(channels);
   header.setSrate(srate);
   SoundFileWrite soundfile(filename, header);

   int i;
   double sample = 0.0;
   for (i=0; i<sampleCount * channels; i++) {
      sample = drand48() * 2 - 1;
      soundfile.writeSampleDouble(sample);
   }

   return 0;
}

The option variable reads the command line options in three stages:

  1. Define the commands you want it to understand by using the define() function.
  2. Read the parameters argc and argv coming into the function main using the process() function.
  3. Extract parameter values from the option variable by using one of the four functions: getInteger(), getDouble(), getString(), or getBoolean().

To define a command, look at line 5 as an example:

   options.define("a|amplitude=d:0.1",    "amplitude of output sound");
This line defines a command line option called amplitude, or a as an alias name for the amplitude option. There are two string parameters given to the define function. The first string:
   a|amplitude=d:0.1
is the actual definition of the command-line option. This parameter is composed of three main parts:
  1. the option name(s) which is a list of acceptable command line names which can be used on the command line to mean the same thing. This is a list of names separated by a pipe character (|). No spaces or non-alphanumeric characters are allowed in the option names.
  2. next follows an equal sign (=) and then the intended data type for the option, which can be one of the following letters:
    1. i an integer is expected for the option.
    2. d a floating-point number (a double) is expected for the option.
    3. s a character string is expected for the option.
    4. b no data is expected for the option, but if the option is used, then the program will know that a flag option was set.
  3. after the data type character comes an optional colon (:) followed by a default value for the option. For the amplitude option, the default value is set to 0.1. This means that if the amplitude option is not specified on the command line, then the program will use an amplitude of 0.1 as a default.
The second string in the define() function:
   amplitude of output sound
is an optional parameter which is useful for remembering what the option actually means and does not do anything important in the program except help you remember what is going on when you revise the program the following day.

Example access to the command-line options is given on line 19 of the noise2.cpp program:

   double amplitude = options.getDouble("amplitude");  
On this line of the program the variable amplitude is being declared as a double (which is a floating-point number). It is being defined as the value options.getDouble("amplitude"). In this case, the function getDouble will return the value of the command-line option "amplitude" as it is specified on the command-line or the default value of 0.1 given in the definition of the amplitude option.

The output filename is extracted from the options with the getArg() function as shown on line 20 of the example program above. getArg(1) means return (as a string) the first command line argument (other than the command name).


Try compiling the program noise2.cpp on mambo, in the mambo terminal, you can copy the program by copying and pasting this command:

   wget http://peabody.sapp.org/class/350.867/lab/prog2/noise2.cpp

To compile the program, refer to the procedure in lab1. You will compile the program by typing the command:

   mkprog noise2

After the program is compiled, you can try out the new options added to the noise program. Here are a few program calls to try:

noise2 test1.wav
This should create a WAVE file called test1.wav which contains one second of noise at an amplitude of 0.1
noise2
This should display the usage statement given in the program on lines 9-14.
noise2 -a 1 test2.wav
This should raise the amplitude of test2.wav to the maximum possible without clipping the sound.
noise2 --amplitude 1 test2.wav
The --amplitude option is an alias for the -a option. Notice that two dashes are used for multi-character options according to the POSIX standards.
noise2 -a1 test2.wav
This should also work like the previous example.
noise2 -d 15 test3.wav
This should create a soundfile with 5 seconds of noise.
noise2 --options
This will display a list of all of the options defined in the program. The hidden option is useful for finding out what the lazy programmer forgot to document.
noise2 test4.wav -d 5 -a 0.4
This should create a file called test4.wav which is 5 seconds long and has an amplitude of 0.4. Notice that the soundfile name is first. The order and placement of the optional arguments which start with a dash is not important.

Exercises

  1. create an option to control the number of channels (to switch between mono and stereo output soundfiles).
  2. create a program which can do stereo noise where each channel contains independent noise.
  3. create a program which can do stereo noise where each channel contains the exact noise data (it should sound like a mono file, but when using peak, the sound will come from both speakers).