Digital Music Programming II: digitar~




This lab demonstrates how to build the first physical model intended for music.

To trigger a sound, whitenoise is typically inserted into the wavetable. For example, Here are 30 random starting samples which could initially fill a wavetable with 30 cells:

0.254541, -0.989285, 0.398697, -0.903335, -0.496827, 0.206703, -0.719541,
0.32735, 0.861006, -0.498739, 0.699209, 0.315953, -0.603312, 0.863098,
-0.276961, 0.104826, 0.423026, -0.730595, 0.762872, 0.260196, -0.169276,
0.0591645, -0.44005, -0.140423, 0.576183, 0.0484494, 0.161253, -0.237088,
0.0730098, 0.841746

After the wavetable is read through once, the following values with be present in the wavetable after passing through the averaging filter (perhaps "backwards" in this case, but you get the idea). The edges of the wavetable wrap around, generating a "circular" wavetable for the purposes of filtering:

-0.367372, -0.295294, -0.252319, -0.700081, -0.145062, -0.256419,
-0.196096, 0.594178, 0.181134, 0.100235, 0.507581, -0.143679, 0.129893,
0.293069, -0.0860673, 0.263926, -0.153784, 0.0161388, 0.511534, 0.0454603,
-0.0550557, -0.190443, -0.290237, 0.21788, 0.312316, 0.104851, -0.0379176,
-0.0820391, 0.457378, 0.548144

By the 100th iteration of the averaging filter, the signal has a low amplitude and consists mostly of lower frequencies:

0.0799873, 0.0733677, 0.0673252, 0.0621212, 0.0576636, 0.0535048,
0.048906, 0.0429801, 0.0349083, 0.0241922, 0.0108834, -0.00427827,
-0.0198742, -0.0339868, -0.0445269, -0.0496429, -0.048113, -0.0396228,
-0.0248576, -0.00538518, 0.0166376, 0.0388566, 0.0591143, 0.075759,
0.0878144, 0.0950002, 0.0976314, 0.0964479, 0.0924264, 0.0866124

Here is an animated gif which shows the first 100 frames of the 30 cell wavetable after adjacent cells have been averaged at each step:

Here is a plot of the waveform generate by concatenating all of the wavetable contents in succession (click for larger view):


Source Code Example Usage
digitar.c


#include "ext.h"
#include "z_dsp.h"

typedef struct {
   t_pxobject msp_data;
   float*     table;
   long       allocsize, size, tempsize, read;
   float      save;
} MyObject;

void* object_data;

void   main            (void);
void*  create_object   (long tableAllocation);
void   destroy_object  (MyObject* mo);
void   InputBang       (MyObject* mo);
void   InputSize       (MyObject* mo, long value);
void   MessageDSP      (MyObject* mo, t_signal** signal, short* count);
void   MessageClear    (MyObject* mo);
t_int* Perform         (t_int* parameters);
float  randsignal      (void);

void main(void) {
   setup((t_messlist**)&object_data, (method)create_object,
         (method)destroy_object, (short)sizeof(MyObject), NULL,
         A_LONG, A_NOTHING);
   addbang((method)InputBang);
   addint((method)InputSize);
   addmess((method)MessageDSP,   "dsp",   A_CANT, A_NOTHING);
   addmess((method)MessageClear, "clear", A_NOTHING);
   dsp_initclass();
}

void* create_object(long tableAllocation) {
   MyObject *mo = (MyObject*)newobject(object_data);
   dsp_setup((t_pxobject*)mo, 0);
   outlet_new((t_pxobject*)mo, "signal");
   if (tableAllocation < 1) {
      post("Warning: setting table memory to 10,000 samples.");
      tableAllocation = 10000;
   }
   mo->table     = (float*)malloc(tableAllocation * sizeof(float));
   mo->allocsize = mo->tempsize = mo->size = tableAllocation;
   mo->read      = 0;
   MessageClear(mo);
   return mo;
}

void destroy_object(MyObject *mo) {
   dsp_free(&(mo->msp_data));
   if (mo->table != NULL) {
      free(mo->table);
      mo->table = NULL;
      mo->allocsize = mo->size = mo->tempsize = 0;
   }
}

void InputBang(MyObject *mo) {
   long i;
   mo->size = mo->tempsize;
   for (i=0; i<mo->size; i++)  mo->table[i] = randsignal();
   mo->read = 0;  mo->save = mo->table[mo->read];
}

void MessageClear(MyObject *mo) {
   long i;
   for (i=0; i<mo->allocsize; i++)  mo->table[i] = 0.0;
   mo->save = 0.0;  mo->read = 0;
}

void InputSize(MyObject *mo, long value) {
   if (value > mo->allocsize || value <= 0) 
      post("Error: Maximum table size allowed is %ld.", mo->allocsize);
   else mo->tempsize = value;
}

void MessageDSP(MyObject* mo, t_signal** signal, short* count) {
   #pragma unused(count)
   dsp_add(Perform, 4, 4, mo, signal[0]->s_vec, signal[0]->s_n);
}

t_int* Perform(t_int *parameters) {
   long      pcount = (long)     (parameters[1]);
   MyObject *mo     = (MyObject*)(parameters[2]);
   t_float  *output = (t_float*) (parameters[3]);
   long      count  = (long)     (parameters[4]);
   long      i;

   for (i=0; i<count; i++) {
      output[i] = mo->table[mo->read];
      mo->table[mo->read] = (mo->table[mo->read] + mo->save)/2.0;
      mo->save  = output[i];
      mo->read++;
      if (mo->read >= mo->size)  mo->read = 0;
   }

   return parameters+pcount+1;
}

float randsignal(void) {
   return (float)rand()/RAND_MAX * 2.0 - 1.0;
}


Exercises

  1. Compile and test the digitar~ object.

  2. What is the pitch of the output string sound (in Hertz) when the input size is set to 50:

    What is the pitch of the string in Hertz, when the input size is set to 51? Listen to the pitch of the object when the table size is 50. How does it sound compared to a sinewave set to the frequency you calculated for the pitch when the table is set to 50 samples?

  3. Try the following patch to generate sound with the digitar~ object:



  4. Write a patch which converts the MIDI note numbers output from the henon object into table sizes for the digitar~ object which approximates the correct pitch for the given MIDI note number.

  5. Modify the digitar~ object to insert other signals besides noise into the wavetable when a bang is received on the input. For example, try inserting a triangular shape, or insert a segment of a soundfile.

  6. Use another filter other than the averaging filter in the digitar~ object. Try a biquad, allpass or the difference filter.



  7. Extra Credit: How might the pitch of the digitar~ object be precisely tuned? In other words, how could you get the pitch which is equivalent to a table size of 50.5 (a pitch at 873 Hz) or 50.75 or 50.1 samples. If you come up with an idea about how to tune the digitar~, implement it.

  8. Extra Credit: Have someone listen to the output sound, but use two different whitenoise patterns. Can they tell the difference between the two starting points of the wavetable? At what point can someone distinguish different starting contents for the wavetable?


References

  • Karplus-Strong Algorithm in Elementary Digital Waveguide Models for Vibrating Strings by Julius Smith.
  • Picking for digital string, by Craig Sapp.
  • The digitar algorithm: short history and links by Kevin Karplus.
  • U.S. Patent 4,649,783: Wavetable-modification instrument and method for generating musical sound by Alexander Strong and Kevin Karplus.
  • Kevin Karplus and Alex Strong. "Digital Synthesis of Plucked-String and Drum Timbres", Computer Music Journal 7(2) (Summer 1983), pp. 43-55.
  • David Jaffe and Julius Smith, "Extensions of the Karplus-Strong Plucked-String Algorithm", Computer Music Journal 7(2) (Summer 1983). reprinted in The Music Machine.