Home Information Classes Download Usage Mail List Requirements Links FAQ Tutorial
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 "channel" number. All sounding instrument outputs are summed and returned via the tick()
function. The 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 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 BeeThree instruments, all controlled using a Voicer.
// threebees.cpp STK tutorial program #include "BeeThree.h" #include "RtAudio.h" #include "Messager.h" #include "Voicer.h" #include "SKINI.msg" #include <algorithm> using std::min; // 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. 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; try { dac.openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ); } catch ( RtError &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 ( RtError &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 ( RtError &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 Messager::startMidiInput()
function to the program and then play the instruments via a MIDI keyboard.
The Synthesis ToolKit in C++ (STK) |
©1995-2007 Perry R. Cook and Gary P. Scavone. All Rights Reserved. |