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 )
{
StkFloat value1 = data->message.floatValues[0];
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;
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.
RtAudio::StreamParameters parameters;
parameters.deviceId = dac.getDefaultOutputDevice();
parameters.nChannels = 1;
RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
unsigned int bufferFrames = RT_BUFFER_SIZE;
if ( dac.openStream( &parameters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ) ) {
std::cout << dac.getErrorText() << std::endl;
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;
if ( dac.startStream() ) {
std::cout << dac.getErrorText() << std::endl;
goto cleanup;
}
// Block waiting until callback signals done.
while ( !data.done )
Stk::sleep( 100 );
// Shut down the callback and output stream.
dac.closeStream();
cleanup:
for ( i=0; i<3; i++ ) delete instrument[i];
return 0;
}
stk::BeeThree
STK Hammond-oid organ FM synthesis instrument.
Definition BeeThree.h:43
stk::Instrmnt
STK instrument abstract base class.
Definition Instrmnt.h:20
stk::Messager
STK input control message parser.
Definition Messager.h:56
stk::StkError
STK error handling class.
Definition Stk.h:87
stk::Stk::setSampleRate
static void setSampleRate(StkFloat rate)
Static method that sets the STK sample rate.
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:148
stk::Stk::sleep
static void sleep(unsigned long milliseconds)
Static cross-platform method to sleep for a number of milliseconds.
stk::Voicer
STK voice manager class.
Definition Voicer.h:35
stk
The STK namespace.
Definition ADSR.h:6
stk::Skini::Message
A message structure to store and pass parsed SKINI messages.
Definition Skini.h:43

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]

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