1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
// Eguitar.cpp
//
// This is a program to create a simple electric guitar model using
// the STK Guitar class. The is model is derived in part from an
// implementation made by Nicholas Donaldson at McGill University in
// 2009. The distortion model is poor, using a simple soft-clipping
// expression provided by Charles R. Sullivan in "Extending the
// Karplus-String Algorithm to Synthesize Electric Guitar Timbres with
// Distortion and Feedback," Computer Music Journal, Vol.14 No.3, Fall
// 1990. Other distortion models would be better, such as that found
// in Pakarinen and Yeh's "A Review of Digital Techniques for Modeling
// Vacuum-Tube Guitar Amplifiers," Computer Music Journal, Vol 33
// No. 2, Summer 2009.
//
// This program performs simple voice management if all noteOn and
// noteOff events are on channel 0. Otherwise, channel values > 0 are
// mapped to specific string numbers. By default, the program creates
// a 6-string guitar. If the normalized noteOn() velocity is < 0.2, a
// string is undamped but not plucked (this is implemented in the
// stk::Guitar class). Thus, you can lightly depress a key on a MIDI
// keyboard and then experiment with string coupling.
//
// The Tcl/Tk GUI allows you to experiment with various parameter
// settings and that can be used in conjunction with a MIDI keyboard
// as: wish < tcl/EGuitar.tcl | ./eguitar -or -ip -im 1
//
// For the moment, this program does not support pitch bends.
//
// Gary P. Scavone, McGill University 2012.
#include "Guitar.h"
#include "SKINImsg.h"
#include "WvOut.h"
#include "JCRev.h"
#include "Skini.h"
#include "RtAudio.h"
#include "Delay.h"
#include "Cubic.h"
// Miscellaneous command-line parsing and instrument allocation
// functions are defined in utilites.cpp ... specific to this program.
#include "utilities.h"
#include <signal.h>
#include <iostream>
#include <algorithm>
#include <cmath>
using std::min;
bool done;
static void finish(int ignore){ done = true; }
using namespace stk;
const unsigned int nStrings = 6;
// Data structure for string information.
struct StringInfo{
bool inUse; // is this string being used?
unsigned int iNote; // note number associated with this string
StringInfo() : inUse(false), iNote(0) {};
};
// The TickData structure holds all the class instances and data that
// are shared by the various processing functions.
struct TickData {
WvOut **wvout;
Guitar *guitar;
StringInfo voices[nStrings];
JCRev reverb;
Messager messager;
Skini::Message message;
StkFloat volume;
StkFloat t60;
unsigned int nWvOuts;
int channels;
int counter;
bool realtime;
bool settling;
bool haveMessage;
int keysDown;
StkFloat feedbackGain;
StkFloat oldFeedbackGain;
StkFloat distortionGain;
StkFloat distortionMix;
Delay feedbackDelay;
Cubic distortion;
StkFloat feedbackSample;
// Default constructor.
TickData()
: wvout(0), volume(1.0), t60(0.75),
nWvOuts(0), channels(2), counter(0),
realtime( false ), settling( false ), haveMessage( false ),
keysDown(0), feedbackSample( 0.0 ) {}
};
#define DELTA_CONTROL_TICKS 30 // 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];
unsigned int channel = (unsigned int) data->message.channel;
switch( data->message.type ) {
case __SK_Exit_:
if ( data->settling == false ) goto settle;
done = true;
return;
case __SK_NoteOn_:
if ( value2 > 0.0 ) { // velocity > 0
unsigned int iNote = data->message.intValues[0];
if ( channel == 0 ) { // do basic voice management
unsigned int s;
if ( data->keysDown >= (int) nStrings ) break; // ignore extra note on's
// Find first unused string
for ( s=0; s<nStrings; s++ )
if ( !data->voices[s].inUse ) break;
if ( s == nStrings ) break;
data->voices[s].inUse = true;
data->voices[s].iNote = iNote;
data->guitar->noteOn( Midi2Pitch[iNote], value2 * ONE_OVER_128, s );
data->keysDown++;
// If first key down, turn on feedback gain
if ( data->keysDown == 1 )
data->feedbackGain = data->oldFeedbackGain;
}
else if ( channel <= nStrings )
data->guitar->noteOn( Midi2Pitch[iNote], value2 * ONE_OVER_128, channel-1 );
break;
}
// else a note off, so continue to next case
case __SK_NoteOff_:
if ( channel == 0 ) { // do basic voice management
if ( !data->keysDown ) break;
// Search for the released note
unsigned int s, iNote;
iNote = data->message.intValues[0];
for ( s=0; s<nStrings; s++ )
if ( data->voices[s].inUse && iNote == data->voices[s].iNote )
break;
if ( s == nStrings ) break;
data->voices[s].inUse = false;
data->guitar->noteOff( value2 * ONE_OVER_128, s );
data->keysDown--;
if ( data->keysDown == 0 ) { // turn off feedback gain and clear delay
data->feedbackDelay.clear();
data->feedbackGain = 0.0;
}
}
else if ( channel <= nStrings )
data->guitar->noteOff( value2 * ONE_OVER_128, channel-1 );
break;
case __SK_ControlChange_:
if ( value1 == 44.0 )
data->reverb.setEffectMix( value2 * ONE_OVER_128 );
else if ( value1 == 7.0 )
data->volume = value2 * ONE_OVER_128;
else if ( value1 == 27 ) // feedback delay
data->feedbackDelay.setDelay( (value2 * Stk::sampleRate() / 127) + 1 );
else if ( value1 == 28 ) { // feedback gain
//data->oldFeedbackGain = value2 * 0.01 / 127.0;
data->oldFeedbackGain = value2 * 0.02 / 127.0;
data->feedbackGain = data->oldFeedbackGain;
}
else if ( value1 == 71 ) // pre-distortion gain
data->distortionGain = 2.0 * value2 * ONE_OVER_128;
else if ( value1 == 72 ) // distortion mix
data->distortionMix = value2 * ONE_OVER_128;
else
data->guitar->controlChange( (int) value1, value2 );
break;
case __SK_AfterTouch_:
data->guitar->controlChange( 128, value1 );
break;
case __SK_PitchBend_:
// Implement me!
break;
case __SK_Volume_:
data->volume = value1 * ONE_OVER_128;
break;
} // end of switch
data->haveMessage = false;
return;
settle:
// Exit and program change messages are preceeded with a short settling period.
for ( unsigned int s=0; s<nStrings; s++ )
if ( data->voices[s].inUse ) data->guitar->noteOff( 0.6, s );
data->counter = (int) (0.3 * data->t60 * Stk::sampleRate());
data->settling = true;
}
// The tick() function handles sample computation and scheduling of
// control updates. If doing realtime audio output, 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 temp, sample, *samples = (StkFloat *) outputBuffer;
int counter, nTicks = (int) nBufferFrames;
while ( nTicks > 0 && !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++ ) {
// Put the previous distorted sample thru feedback
sample = data->feedbackDelay.tick( data->feedbackSample * data->feedbackGain );
sample = data->guitar->tick( sample );
// Apply distortion (x - x^3/3) and mix
temp = data->distortionGain * sample;
if ( temp > 0.6666667 ) temp = 0.6666667;
else if ( temp < -0.6666667 ) temp = -0.6666667;
else temp = data->distortion.tick( temp );
sample = (data->distortionMix * temp) + ((1 - data->distortionMix) * sample );
data->feedbackSample = sample;
// Tick instrument and apply reverb
sample = data->volume * data->reverb.tick( sample );
for ( unsigned int j=0; j<data->nWvOuts; j++ ) data->wvout[j]->tick( sample );
if ( data->realtime )
for ( int k=0; k<data->channels; k++ ) *samples++ = sample;
nTicks--;
}
if ( nTicks == 0 ) break;
// Process control messages.
if ( data->haveMessage ) processMessage( data );
}
return 0;
}
int main( int argc, char *argv[] )
{
TickData data;
int i;
#if defined(__STK_REALTIME__)
RtAudio *dac = 0;
#endif
// If you want to change the default sample rate (set in Stk.h), do
// it before instantiating any objects! If the sample rate is
// specified in the command line, it will override this setting.
Stk::setSampleRate( 44100.0 );
// By default, warning messages are not printed. If we want to see
// them, we need to specify that here.
Stk::showWarnings( true );
// Check the command-line arguments for errors and to determine
// the number of WvOut objects to be instantiated (in utilities.cpp).
data.nWvOuts = checkArgs( argc, argv );
data.wvout = (WvOut **) calloc( data.nWvOuts, sizeof(WvOut *) );
// Parse the command-line flags, instantiate WvOut objects, and
// instantiate the input message controller (in utilities.cpp).
try {
data.realtime = parseArgs( argc, argv, data.wvout, data.messager );
}
catch (StkError &) {
goto cleanup;
}
// If realtime output, allocate the dac here.
#if defined(__STK_REALTIME__)
if ( data.realtime ) {
dac = (RtAudio *) new RtAudio( RtAudio::UNSPECIFIED );
RtAudioFormat format = ( sizeof(StkFloat) == 8 ) ? RTAUDIO_FLOAT64 : RTAUDIO_FLOAT32;
RtAudio::StreamParameters parameters;
parameters.deviceId = dac->getDefaultOutputDevice();
parameters.nChannels = data.channels;
unsigned int bufferFrames = RT_BUFFER_SIZE;
if ( dac->openStream( ¶meters, NULL, format, (unsigned int)Stk::sampleRate(), &bufferFrames, &tick, (void *)&data ) ) {
std::cout << dac->getErrorText() << std::endl;
goto cleanup;
}
}
#endif
// Set the reverb parameters.
data.reverb.setT60( data.t60 );
data.reverb.setEffectMix( 0.2 );
// Allocate guitar
data.guitar = new Guitar( nStrings );
// Configure distortion and feedback.
data.distortion.setThreshold( 2.0 / 3.0 );
data.distortion.setA1( 1.0 );
data.distortion.setA2( 0.0 );
data.distortion.setA3( -1.0 / 3.0 );
data.distortionMix = 0.9;
data.distortionGain = 1.0;
data.feedbackDelay.setMaximumDelay( (unsigned long int)( 1.1 * Stk::sampleRate() ) );
data.feedbackDelay.setDelay( 20000 );
data.feedbackGain = 0.001;
data.oldFeedbackGain = 0.001;
// Install an interrupt handler function.
(void) signal(SIGINT, finish);
// If realtime output, set our callback function and start the dac.
#if defined(__STK_REALTIME__)
if ( data.realtime ) {
if ( dac->startStream() ) {
std::cout << dac->getErrorText() << std::endl;
goto cleanup;
}
}
#endif
// Setup finished.
while ( !done ) {
#if defined(__STK_REALTIME__)
if ( data.realtime )
// Periodically check "done" status.
Stk::sleep( 200 );
else
#endif
// Call the "tick" function to process data.
tick( NULL, NULL, 256, 0, 0, (void *)&data );
}
// Shut down the output stream.
#if defined(__STK_REALTIME__)
if ( data.realtime )
dac->closeStream();
#endif
cleanup:
for ( i=0; i<(int)data.nWvOuts; i++ ) delete data.wvout[i];
free( data.wvout );
delete data.guitar;
delete dac;
std::cout << "\nStk eguitar finished ... goodbye.\n\n";
return 0;
}
|