Previous: Object-Oriented Programming and Timbre Definition Up: Kiwi : A Parallel System for Software Sound Synthesis Next: Score File Format

Building an Orchestra

The tree of Sound objects is known as an instance tree. There is one such tree for each note in the piece. The composer writes C++ functions called ``timbre builders.'' These functions take a line from the score file as a parameter and return a pointer to the root of an instance tree. Objects are created in C++ using the new operator. C++ allows for initialization of dynamically created objects through the use of ``constructor functions.'' These constructor functions initialize the variables of the object being constructed. Kiwi uses constructor functions to specify information pertinent to the sound being generated. For example, the Lookup object is a lookup table oscillator. The output of Lookup depends on what frequency is being played, and what table to take the samples from. The Lookup constructor function passes in that information (see figure 5.1).

Sound *temp;
temp = new Lookup(myTable,440.0); /* an oscillator at 440.0 hertz */

Figure 5.1. Constructor function

Thus there are two major tasks in defining Kiwi timbres -- specifying the shape of the instance tree, and filling in the constructors of the objects. The latter is also referred to as ``parameter mapping,'' since the constructors are usually filled in with parameters taken from the score file. For example, a simple lookup oscillator instrument would take its frequency from the appropriate line in the score file, and each instance tree would operate at a different frequency.

The easiest way to extract the parameters from the score file is to use C's sscanf facility. sscanf extracts various kinds of data from text strings.

Sound *pluck(char *line)
{
  char temp[8];
  float a, b, fre;

  /* extract the start time, duration, and frequency from the score */
  if (sscanf(line,"%8s%f%f%f",temp,&a,&b,&fre) != 4)
    report("pluck scanf problem");

  return new Karplus(fre); /* A Karplus-Strong sound */
}

Figure 5.2

Figure 5.2 is an actual timbre builder. It uses sscanf to extract the frequency from the score file line into the variable fre, and then sends that information to the constructor function for Karplus. This timbre builder is very simple because there is only one object created. Thus, the instance ``tree'' has only one node. Figure 5.3 is a more complicated example.

Sound *boringFM(char *line)
{
  char temp[8];
  float a, b, fre;

  /* extract the start time, duration, and frequency from the score */
  if (sscanf(line,"%8s%f%f%f",temp,&a,&b,&fre) != 4)
    report("FM scanf problem");
  if (sintab == NULL) sintab = new SineTable(40000);

  /* an envelope generator modifys the output of an oscillator */
  ExpGen *ampi = new ExpGen(new Lookup(sintab,2.01 * fre));
  ampi->addbreak(0.0,1.0);
  ampi->addbreak(0.9*b,0.4);
  ampi->addbreak(2.0*b,0.01);
  /* a second envelope generator controls the output of a FM sound */
  ExpGen *amp2 = new ExpGen(new FreqMod(sintab,ampi,fre));
  amp2->addbreak(0.0,1.0);
  amp2->addbreak(0.9*b,0.7);
  amp2->addbreak(2.0*b,0.01);
  return amp2;
}

Figure 5.3. A fancy timbre builder

This timbre builder constructs a tree consisting of two exponential envelope generators, a lookup table oscillator, and a frequency modulator. The addbreak function allows the composer to specify breakpoints in the envelope. Notice that the actual breakpoints in this example depend on the variable b, which is the duration of the note taken from the score file. Also notice how some Sound objects are assigned to named variables (i. e., ampi), while other objects are created anonymously.

There is a special object called the InstrTable that holds a table of timbre builders and timbre names. It needs an initialization function for it, as in Figure 5.4.

InstrTable::InstrTable()
{
  ibuilders[0] = pluck; names[0] = "pluck";
  ibuilders[1] = Imp; names[1] = "imp";
  ibuilders[2] = Drum; names[2] = "drum";
  ibuilders[3] = boringFM; names[3] = "fm";
  num_entries = 4;
}

Figure 5.4. InstrTable construction

ibuilders is an array of pointers to functions, and names is an array of strings. The two arrays are initialized as shown, and the num_entries variable tells how many different timbres there are.


[Bill's Home Page] Comments to walker@shout.net