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.
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;
}
unsigned int RtAudioStreamStatus
RtAudio stream status (over- or underflow) flags.
Definition RtAudio.h:178
unsigned long RtAudioFormat
RtAudio data format type.
Definition RtAudio.h:105
Realtime audio i/o C++ classes.
Definition RtAudio.h:268
unsigned int getDefaultOutputDevice(void)
A function that returns the ID of the default output device.
Definition RtAudio.h:915
const std::string getErrorText(void)
Retrieve the error message corresponding to the last error or warning condition.
Definition RtAudio.h:920
void closeStream(void)
A function that closes a stream and frees any associated stream memory.
Definition RtAudio.h:916
RtAudioErrorType openStream(RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData=NULL, RtAudio::StreamOptions *options=NULL)
A public function for opening a stream with the specified parameters.
RtAudioErrorType startStream(void)
A function that starts a stream.
Definition RtAudio.h:917
STK Hammond-oid organ FM synthesis instrument.
Definition BeeThree.h:43
STK instrument abstract base class.
Definition Instrmnt.h:20
STK input control message parser.
Definition Messager.h:56
STK error handling class.
Definition Stk.h:87
STK voice manager class.
Definition Voicer.h:35
The STK namespace.
Definition ADSR.h:6
The structure for specifying input or output stream parameters.
Definition RtAudio.h:302
unsigned int nChannels
Definition RtAudio.h:305
unsigned int deviceId
Definition RtAudio.h:304
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.