Music 3SI

Week 3 Online Tutorial

Introduction to Audio/Multimedia Application Programming


Announcements  |  Course Info  |  Weekly Schedule  |  Tutorials  |  Links

Week 3 Online Tutorial: Stk Programming (2)

1. Callback (Continued)

We begin with the code from last week.
ex7.cpp [download]
// Plays a sine wave (440[Hz]) realtime, stops when "Enter" is pressed.
#include <iostream>

#include "sineWave.h"
#include "RtAudio.h"

// III': Callback function
// Continues to run until step V
int tick(char *buffer, int bufferSize, void *dataPointer) {
  // dataPointer: pointer handed over from the end of step II
  // Casted to a corresponding type
  SineWave *sine = (SineWave *) dataPointer;

  // buffer: pointer to the audio data buffer
  StkFloat *samples = (StkFloat *) buffer;

  // Every time this callback function gets called,
  // it handles just as many samples as "bufferSize"
  for ( int i=0; i<bufferSize; i++ )
    *samples++ = sine->tick();

  return 0;
}

int main (int argc, char* const argv[]) {
  Stk::setSampleRate( 44100.0 );

  // I: Initialize input (SineWave)
  SineWave* sine;             // SineWave (note: pointer)
  sine = new SineWave();      // "new": allocate a "pointee"
  sine->setFrequency(440.0);  // "->": method for a pointer

  // II: Initialize output (RtAudio)
  RtAudio* dac;    // RtAudio: realtime audio I/O
  // RtAudioFormat: 64-bit FP (or 32-bit FP). Check RtAudio.h
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  // RT_BUFFER_SIZE: defined in Stk.h
  int bufferSize = RT_BUFFER_SIZE;
  // Allocate a pointee and opens a stream
  dac = new RtAudio(0, 1, 0, 0, format, (int)Stk::sampleRate(), &bufferSize, 0);
  // tick: name of callback function
  // sine: pointer handed over to callback
  dac->setStreamCallback(&tick, (void *)sine);

  // III: Start stream to output
  dac->startStream();

  // IV: Waiting for [ENTER], while III' is running
  char keyhit;
  std::cout << "\nPlaying ... press [enter] to quit.\n";
  std::cin.get(keyhit);

  // V: Stop stream: in two steps!
  dac->cancelStreamCallback();
  dac->closeStream();

  // VI: Clean up used memory space
  delete dac;
  delete sine;

  // VII: The End!
  return 0;
}

Duplex mode

Setting "non-zero" values for both input and output channel of RtAudio makes it possible to have realtime input and output at the same time.
ex8.cpp [download]
// Duplex mode: bypasses realtime input through realtime output
// stops when "Enter" is pressed.

#include <iostream>

#include "SineWave.h"
#include "RtAudio.h"

// Callback function
int tick(char *buffer, int bufferSize, void *) {
  // buffer: pointer to the audio data buffer
  StkFloat *samples = (StkFloat *) buffer;

  // Bypass: do NOTHING (just move over to the next address)
  for ( int i=0; i<bufferSize; i++ )
    *samples++;

  return 0;
}

int main (int argc, char* const argv[]) {
  Stk::setSampleRate( 44100.0 );

  RtAudio* dac;    
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  int bufferSize = RT_BUFFER_SIZE;
  // Both input and output have one channel each
  dac = new RtAudio(0, 1, 0, 1, format, (int)Stk::sampleRate(), &bufferSize, 0);
  // NULL: no pointer to hand over to callback
  dac->setStreamCallback(&tick, (void *)NULL);

  dac->startStream();

  char keyhit;
  std::cout << "\nPlaying ... press [enter] to quit.\n";
  std::cin.get(keyhit);

  dac->cancelStreamCallback();
  dac->closeStream();

  delete dac;

  return 0;
}

2. Effects

Now we add some effects to realtime input.
ex9.cpp [download]
// Applies chorus effect to realtime input and plays through realtime output
// stops when "Enter" is pressed.

#include <iostream>

#include "Chorus.h"
#include "RtAudio.h"

// Callback function
int tick(char *buffer, int bufferSize, void *dataPointer) {
  // Pointer to "Chorus" from main
  Chorus *chorus = (Chorus *) dataPointer;
  StkFloat *samples = (StkFloat *) buffer;

  // Passes RT input through "chorus" to RT output
  for ( int i=0; i<bufferSize; i++ )
    *samples++ = chorus->tick(*samples);

  return 0;
}

int main (int argc, char* const argv[]) {
  // Input arguments as chorus parameters
  float effectMix = atof( argv[1] );       // Wet/dry ratio (0.0~1.0)
  float modDepth = atof( argv[2] );        // Modulation depth (0.0~1.0)
  float modFrequency = atof( argv[3] );    // Modulation frequency [Hz]

  Stk::setSampleRate( 44100.0 );

  Chorus* chorus = new Chorus();           // Chorus
  chorus->setEffectMix(effectMix);         // Wet/dry ratio
  chorus->setModDepth(modDepth);           // Mod. depth
  chorus->setModFrequency(modFrequency);   // Mod. freq.

  RtAudio* dac;    
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  int bufferSize = RT_BUFFER_SIZE;
  dac = new RtAudio(0, 1, 0, 1, format, (int)Stk::sampleRate(), &bufferSize, 0);
  dac->setStreamCallback(&tick, (void *)chorus);

  dac->startStream();

  char keyhit;
  std::cout << "\nPlaying ... press [enter] to quit.\n";
  std::cin.get(keyhit);

  dac->cancelStreamCallback();
  dac->closeStream();

  delete dac;
  delete chorus;

  return 0;
}
ex10 is an example with pitch shifter.
ex10.cpp [download]

3. Instruments

Instead of realtime input from ADC, we use one of the Stk instruments classes. ex11 is an example of BeeThree (B3), with text-based user interface to change its pitch.
ex11.cpp [download]
// Pitch-controllable "BeeThree" instrument. Stops when pitch is set "zero"
#include <iostream>

#include "BeeThree.h"
#include "RtAudio.h"

// Callback function
int tick(char *buffer, int bufferSize, void *dataPointer) {
  // Pointer to "BeeThree" from main
  BeeThree *beethree = (BeeThree *) dataPointer;
  StkFloat *samples = (StkFloat *) buffer;

  // Filling buffer with "tick"s of beethree
  for ( int i=0; i<bufferSize; i++ )
    *samples++ = beethree->tick();

  return 0;
}

int main (int argc, char* const argv[]) {

  Stk::setSampleRate( 44100.0 );

  float frequency = 440.0;                // Initial frequency value

  BeeThree* beethree = new BeeThree();    // BeeThree
  beethree->noteOn( frequency, 0.7 );     // "noteOn"

  RtAudio* dac;  
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  int bufferSize = RT_BUFFER_SIZE;
  dac = new RtAudio(0, 1, 0, 0, format, (int)Stk::sampleRate(), &bufferSize, 0);
  dac->setStreamCallback(&tick, (void *)beethree);

  dac->startStream();

  // Loop to take frequency input. Enter "0" to exit.
  while ( frequency != 0 ) {
    std::cout << "\nEnter new frequency (0 to quit): ";
    std::cin >> frequency;
    beethree->setFrequency( frequency );
  }

  dac->cancelStreamCallback();
  dac->closeStream();

  delete dac;
  delete beethree;

  return 0;
}

4. Error (or Exception) Handling

Exceptions provide a way to react to exceptional circumstances (like runtime errors) in our program by transferring control to special functions called handlers.
ex12 does exactly the same work as ex11, but with some exception handlings.
ex12.cpp [download]
// Pitch-controllable "BeeThree" instrument. Stops when pitch is set "zero"
// Now with error handling.
#include <iostream>

#include "BeeThree.h"
#include "RtAudio.h"

int tick(char *buffer, int bufferSize, void *dataPointer) {
  BeeThree *beethree = (BeeThree *) dataPointer;
  StkFloat *samples = (StkFloat *) buffer;

  for ( int i=0; i<bufferSize; i++ )
    *samples++ = beethree->tick();

  return 0;
}

int main (int argc, char* const argv[]) {

  Stk::setSampleRate( 44100.0 );

  float frequency = 440.0;

  BeeThree* beethree = new BeeThree();
  beethree->noteOn( frequency, 0.7 );

  RtAudio* dac;
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  int bufferSize = RT_BUFFER_SIZE;

  try {
    dac = new RtAudio(0, 1, 0, 0, format, (int)Stk::sampleRate(), &bufferSize, 0);
  }
  catch (RtError& error) {
    error.printMessage();
    exit(0);
  }

  try {
    dac->setStreamCallback(&tick, (void *)beethree);
    dac->startStream();
  }
  catch (RtError& error) {
    error.printMessage();
    goto cleanup;
  }

  while ( frequency != 0 ) {
    std::cout << "\nEnter new frequency (0 to quit): ";
    std::cin >> frequency;
    beethree->setFrequency( frequency );
  }

  try {
    dac->cancelStreamCallback();
    dac->closeStream();
  }
  catch (RtError& error) {
    error.printMessage();
  }

  cleanup:
    delete dac;
    delete beethree;

  return 0;
}

5. Tips For Assignment 1

Two channels: stereo

For stereo input/output, set the number of channels of RtAudio as 2.
    dac = new RtAudio(0, 2, 0, 0, format, (int)Stk::sampleRate(), &bufferSize, 0);
And process one more sample in the callback loop. Here, sound level from the right channel is lower than the left channel by 20 [dB].
int tick(char *buffer, int bufferSize, void *dataPointer) {
  BeeThree *beethree = (BeeThree *) dataPointer;
  StkFloat *samples = (StkFloat *) buffer;

  for ( int i=0; i<bufferSize; i++ )
    StkFloat bee3 = beethree->tick();    // tick result of "beethree"
    *samples++ = bee3;            // left
    *samples++ = bee3 * 0.1;      // right: multiplied by 0.1 -> 20[dB] lower

  return 0;
}
ex13 is this "stereo" version of ex12.
ex13.cpp [download]

Multiple effects

To have more than one effects, pointers of those effect instances need to be sent over to the callback. This can be done by using a structure which, in fact, can contain not only the pointers to effects but also other control parameters. Compare ex14 with ex9 and ex10.
ex14.cpp [download]
// Multi effect example: RT Input --> Chorus --> PitShift --> Pan --> RT Output
// Panning value (0.0 ~ 1.0) can be adjusted during playback.
// stops when entered pan value goes out of range.
#include <iostream>

#include "Chorus.h"
#include "PitShift.h"
#include "RtAudio.h"

// TickData: structure to contain pointers to chorus, pitshift, and pan
struct TickData {
  Chorus *chorus;
  PitShift *pitshift;
  StkFloat pan;
};

int tick(char *buffer, int bufferSize, void *dataPointer) {
  TickData *data = (TickData *) dataPointer;
  StkFloat *samples = (StkFloat *) buffer;

  for ( int i=0; i<bufferSize; i++ ) {
    *samples++ =
      (1.0 - data->pan) * data->pitshift->tick( data->chorus->tick( *samples ) );
    *samples++ =
      data->pan * data->pitshift->tick( data->chorus->tick( *samples ) );
  }
  return 0;
}

int main (int argc, char* const argv[]) {

  float chorusMix = atof( argv[1] );       // Chorus wet/dry ratio (0.0~1.0)
  float modDepth = atof( argv[2] );        // Modulation depth (0.0~1.0)
  float modFrequency = atof( argv[3] );    // Modulation frequency [Hz]

  float pitshiftMix = atof( argv[4] );     // PitShift wet/dry ratio (0.0~1.0)
  float shift = atof( argv[5] );           // Amount of pitch shift

  float pan = 0.5;

  Stk::setSampleRate( 44100.0 );

  TickData data;

  data.chorus = new Chorus();        // Chorus
  data.chorus->setEffectMix(effectMix);
  data.chorus->setModDepth(modDepth);
  data.chorus->setModFrequency(modFrequency);

  data.pitshift = new PitShift();           // PitShift
  data.pitshift->setEffectMix( pitshiftMix );
  data.pitshift->setShift( shift );

  RtAudio* dac;    
  RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64:RTAUDIO_FLOAT32;
  int bufferSize = RT_BUFFER_SIZE;
  dac = new RtAudio(0, 2, 0, 2, format, (int)Stk::sampleRate(), &bufferSize, 0);
  dac->setStreamCallback(&tick, (void *)&data);

  dac->startStream();

  while ( frequency != 0 ) {
    std::cout << "Enter new panning value (0.0 <= pan <= 1.0): ";
    std::cin >> pan;
    data.pan = pan;
  }

  dac->cancelStreamCallback();
  dac->closeStream();

  delete dac;
  delete data.chorus;
  delete data.pitshift;

  return 0;
}
Announcements  |  Course Info  |  Weekly Schedule  |  Tutorials  |  Links

Music 3SI / Spring 2006 / CCRMA, Stanford University
Woon Seung Yeo
Last updated: Wed, 26 May 2006 16:00:25 -0700