Home   Information   Classes   Download   Usage   Mail List   Requirements   Links   FAQ   Tutorial


Voice Management

The previous tutorial chapters were concerned only with monophonic ToolKit instrument playback and control. At this point, it should be relatively clear that one can instantiate multiple instruments and perhaps sum together their outputs or even direct their outputs to separate channels. It is less clear how one might go about controlling a group of instruments. The stk::Voicer class is designed to serve just this purpose.

The stk::Voicer class is a relatively simple voice manager. The user can dynamically add and delete instruments to/from its "control", with the option of controlling specific instruments via unique note tags and/or grouping sets of instruments via a "group" number. All sounding instrument outputs are summed and returned via the tick() function. The stk::Voicer class responds to noteOn, noteOff, setFrequency, pitchBend, and controlChange messages, automatically assigning incoming messages to the voices in its control. When all voices are sounding and a new noteOn is encountered, the stk::Voicer interrupts the oldest sounding voice. The user is responsible for creating and deleting all instrument instances.

In the following example, we modify the controlbee.cpp program to make use of three stk::BeeThree instruments, all controlled using a stk::Voicer.

// threebees.cpp STK tutorial program
#include "BeeThree.h"
#include "RtAudio.h"
#include "Messager.h"
#include "Voicer.h"
#include "SKINImsg.h"
#include <algorithm>
using std::min;
using namespace stk;
// The TickData structure holds all the class instances and data that
// are shared by the various processing functions.
struct TickData {
Voicer voicer;
Messager messager;
Skini::Message message;
int counter;
bool haveMessage;
bool done;
// Default constructor.
TickData()
: counter(0), haveMessage(false), done( false ) {}
};
#define DELTA_CONTROL_TICKS 64 // default sample frames between control input checks
// The processMessage() function encapsulates the handling of control
// messages. It can be easily relocated within a program structure
// depending on the desired scheduling scheme.
void processMessage( TickData* data )
{
register StkFloat value1 = data->message.floatValues[0];
register StkFloat value2 = data->message.floatValues[1];
switch( data->message.type ) {
case __SK_Exit_:
data->done = true;
return;
case __SK_NoteOn_:
if ( value2 == 0.0 ) // velocity is zero ... really a NoteOff
data->voicer.noteOff( value1, 64.0 );
else { // a NoteOn
data->voicer.noteOn( value1, value2 );
}
break;
case __SK_NoteOff_:
data->voicer.noteOff( value1, value2 );
break;
case __SK_ControlChange_:
data->voicer.controlChange( (int) value1, value2 );
break;
case __SK_AfterTouch_:
data->voicer.controlChange( 128, value1 );
case __SK_PitchChange_:
data->voicer.setFrequency( value1 );
break;
case __SK_PitchBend_:
data->voicer.pitchBend( value1 );
} // end of switch
data->haveMessage = false;
return;
}
// This tick() function handles sample computation and scheduling of
// control updates. It will be called automatically when the system
// needs a new buffer of audio samples.
int tick( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
double streamTime, RtAudioStreamStatus status, void *dataPointer )
{
TickData *data = (TickData *) dataPointer;
register StkFloat *samples = (StkFloat *) outputBuffer;
int counter, nTicks = (int) nBufferFrames;
while ( nTicks > 0 && !data->done ) {
if ( !data->haveMessage ) {
data->messager.popMessage( data->message );
if ( data->message.type > 0 ) {
data->counter = (long) (data->message.time * Stk::sampleRate());
data->haveMessage = true;
}
else
data->counter = DELTA_CONTROL_TICKS;
}
counter = min( nTicks, data->counter );
data->counter -= counter;
for ( int i=0; i<counter; i++ ) {
*samples++ = data->voicer.tick();
nTicks--;
}
if ( nTicks == 0 ) break;
// Process control messages.
if ( data->haveMessage ) processMessage( data );
}
return 0;
}
int main()
{
// Set the global sample rate and rawwave path before creating class instances.
Stk::setSampleRate( 44100.0 );
Stk::setRawwavePath( "../../rawwaves/" );
int i;
TickData data;
RtAudio dac;
Instrmnt *instrument[3];
for ( i=0; i<3; i++ ) instrument[i] = 0;
// Figure out how many bytes in an StkFloat and setup the RtAudio stream.
parameters.deviceId = dac.getDefaultOutputDevice();
parameters.nChannels = 1;
RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
unsigned int bufferFrames = RT_BUFFER_SIZE;
try {
dac.openStream( &parameters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data );
}
catch ( RtAudioError &error ) {
error.printMessage();
goto cleanup;
}
try {
// Define and load the BeeThree instruments
for ( i=0; i<3; i++ )
instrument[i] = new BeeThree();
}
catch ( StkError & ) {
goto cleanup;
}
// "Add" the instruments to the voicer.
for ( i=0; i<3; i++ )
data.voicer.addInstrument( instrument[i] );
if ( data.messager.startStdInput() == false )
goto cleanup;
try {
dac.startStream();
}
catch ( RtAudioError &error ) {
error.printMessage();
goto cleanup;
}
// Block waiting until callback signals done.
while ( !data.done )
Stk::sleep( 100 );
// Shut down the callback and output stream.
try {
dac.closeStream();
}
catch ( RtAudioError &error ) {
error.printMessage();
}
cleanup:
for ( i=0; i<3; i++ ) delete instrument[i];
return 0;
}

We have written this program to accept control messages from STDIN. Assuming the program is compiled as threebees, the three-voice SKINI scorefile bachfugue.ski (located in the scores directory with the examples) can be redirected to the program as:

threebees < scores/bachfugue.ski

For more fun, surf to Kern Scores for a huge assortment of other scorefiles that can be downloaded in the SKINI format.

Another easy extension would be to add the stk::Messager::startMidiInput() function to the program and then play the instruments via a MIDI keyboard.

[Main tutorial page]

RtAudioFormat
unsigned long RtAudioFormat
RtAudio data format type.
Definition: RtAudio.h:86
RtAudioError
Exception handling class for RtAudio.
Definition: RtAudio.h:220
stk::Skini::Message
A message structure to store and pass parsed SKINI messages.
Definition: Skini.h:43
stk::Messager
STK input control message parser.
Definition: Messager.h:56
RtAudio::openStream
void openStream(RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData=NULL, RtAudio::StreamOptions *options=NULL, RtAudioErrorCallback errorCallback=NULL)
A public function for opening a stream with the specified parameters.
RtAudioError::printMessage
virtual void printMessage(void) const
Prints thrown error message to stderr.
Definition: RtAudio.h:243
RtAudio::getDefaultOutputDevice
unsigned int getDefaultOutputDevice(void)
A function that returns the index of the default output device.
Definition: RtAudio.h:873
stk::Stk::sleep
static void sleep(unsigned long milliseconds)
Static cross-platform method to sleep for a number of milliseconds.
stk::Stk::setRawwavePath
static void setRawwavePath(std::string path)
Static method that sets the STK rawwave path.
stk::Stk::sampleRate
static StkFloat sampleRate(void)
Static method that returns the current STK sample rate.
Definition: Stk.h:145
stk::Stk::setSampleRate
static void setSampleRate(StkFloat rate)
Static method that sets the STK sample rate.
stk::Voicer
STK voice manager class.
Definition: Voicer.h:35
RtAudio::closeStream
void closeStream(void)
A function that closes a stream and frees any associated stream memory.
Definition: RtAudio.h:874
RtAudio::StreamParameters::nChannels
unsigned int nChannels
Definition: RtAudio.h:320
RtAudio::startStream
void startStream(void)
A function that starts a stream.
Definition: RtAudio.h:875
stk::BeeThree
STK Hammond-oid organ FM synthesis instrument.
Definition: BeeThree.h:43
stk
The STK namespace.
Definition: ADSR.h:6
stk::Instrmnt
STK instrument abstract base class.
Definition: Instrmnt.h:20
RtAudioStreamStatus
unsigned int RtAudioStreamStatus
RtAudio stream status (over- or underflow) flags.
Definition: RtAudio.h:159
RtAudio
Realtime audio i/o C++ classes.
Definition: RtAudio.h:280
RtAudio::StreamParameters
The structure for specifying input or ouput stream parameters.
Definition: RtAudio.h:318
RtAudio::StreamParameters::deviceId
unsigned int deviceId
Definition: RtAudio.h:319
RtAudio.h
stk::StkError
STK error handling class.
Definition: Stk.h:86

The Synthesis ToolKit in C++ (STK)
©1995--2019 Perry R. Cook and Gary P. Scavone. All Rights Reserved.