#include "RtAudio.h" #include #include #include #include using namespace std; #define SAMPLE double #define MY_PI 3.14159265358979 #define MY_SRATE 44100.0 //Global variables ----------------------- //Logical control variable bool g_input_mod = false; // --------------------------------------- //function which outputs correct argument input formatting void usage() { cerr << "SigGen [type] [frequency] [width] [input]" << endl; cerr << " [type]: --sine | --saw | --pulse | --noise | --impulse" << endl; cerr << " [frequency]: a number between 0 and 22050 (only applicable to some types)" << endl; cerr << " [width]: pulse width between 0 and 1.0 (some types only)" << endl; cerr << " [input]: --input , specifies to modulate generated wave by input signal" << endl; exit( 1 ); } //Signal generator classes --------------- // Abstract class for signal unit generators class UGen { public: UGen() { freq = 0.0; pulse_width = 0.0; t = 0.0; }; virtual SAMPLE generateSample() = 0; virtual double incrementTime() = 0; virtual void processCmdLineArgs(int argc, char** argv) = 0; protected: double freq; double pulse_width; double t; }; //Actual generator classes //Sine class SineGen: public UGen { public: SAMPLE generateSample() { return sin( 2 * MY_PI * t * freq / MY_SRATE ); } double incrementTime() { t += 1.0; } void processCmdLineArgs(int argc, char** argv) { if (argc < 3) { cerr << "Sine wave generation requires a specified frequency argument." << endl; exit( 1 ); } else if (argc == 4 && (strcmp( argv[3], "--input" ))) // if more than type and freq are supplied and the last argument isn't "--input" { cerr << "Width is not applicable to sine wave generation. No width argument should be supplied. Only --input may be supplied as a third argument." << endl; exit( 1 ); } else if (argc > 4) { cerr << "Only [type], [frequency], and [input] may be specified for sine wave generation. Too many arguments were supplied." << endl; exit( 1 ); } //Force Frequency to be numeric if (strlen(argv[2]) != strspn(argv[2],"0123456789.") ) { cerr << "Frequency value must be numeric." << endl; exit( 1 ); } freq = atof(argv[2]); // Check actual argument value if ( freq <= 0.0 || freq >= MY_SRATE / 2.0 ) { cerr << "[frequency] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( argc > 3 && !strcmp( argv[3], "--input" ) ) g_input_mod = true; else if ( argc > 3 ) { cerr << "Invalid third argument supplied for --sine. Only \"--input\" is allowed." << endl; exit( 1 ); } } }; //Saw class SawGen: public UGen { public: SAMPLE generateSample() { if ( t < pulse_width * (MY_SRATE / freq) ) { return ( -1.0 + 2.0 * t * (freq / MY_SRATE) / pulse_width ); } else { return ( 1.0 - 2.0 * (t - pulse_width * (MY_SRATE / freq)) / ( (1 - pulse_width) * (MY_SRATE / freq) ) ); } } double incrementTime() { t += 1.0; // Modulo time by the waveform period if ( t >= MY_SRATE / freq ) t -= MY_SRATE / freq; } void processCmdLineArgs(int argc, char** argv) { if ( argc < 4 ) { cerr << "Saw wave generation requires both frequency and width arguments." << endl; exit( 1 ); } //Force Frequency and Width to be numeric if (strlen(argv[2]) != strspn(argv[2],"0123456789.") ) { cerr << "Frequency value (second argument) must be numeric." << endl; exit( 1 ); } if (strlen(argv[3]) != strspn(argv[3],"0123456789.") ) { cerr << "Width value (third argument) must be numeric." << endl; exit( 1 ); } freq = atof(argv[2]); pulse_width = atof(argv[3]); // Check actual argument values if ( pulse_width < 0.0 || pulse_width > 1.0 ) { cerr << "[width] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( freq <= 0.0 || freq >= MY_SRATE / 2.0 ) { cerr << "[frequency] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( argc > 4 && !strcmp( argv[4], "--input" ) ) // if fourth argument is supplied and it is "--input" g_input_mod = true; else if ( argc > 4 ) { cerr << "Invalid fourth argument supplied for --saw. Only \"--input\" is allowed." << endl; exit( 1 ); } } }; //Pulse class PulseGen: public UGen { public: SAMPLE generateSample() { if ( t < pulse_width * (MY_SRATE / freq) ) { return 1.0; } else { return -1.0; } } double incrementTime() { t += 1.0; // Modulo time by the waveform period if ( t >= MY_SRATE / freq ) t -= MY_SRATE / freq; } void processCmdLineArgs(int argc, char** argv) { if ( argc < 4 ) { cerr << "Pulse wave generation requires both frequency and width arguments." << endl; exit( 1 ); } //Force Frequency and Width to be numeric if (strlen(argv[2]) != strspn(argv[2],"0123456789.") ) { cerr << "Frequency value (second argument) must be numeric." << endl; exit( 1 ); } if (strlen(argv[3]) != strspn(argv[3],"0123456789.") ) { cerr << "Width value (third argument) must be numeric." << endl; exit( 1 ); } freq = atof(argv[2]); pulse_width = atof(argv[3]); // Check actual argument values if ( pulse_width < 0.0 || pulse_width > 1.0 ) { cerr << "[width] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( freq <= 0.0 || freq >= MY_SRATE / 2.0 ) { cerr << "[frequency] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( pulse_width == 0.0 || pulse_width == 1.0 ) { cerr << "Pulse width cannot be 0.0 or 1.0 for pulse waves (results in DC signal)" << endl; exit( 1 ); } if ( argc > 4 && !strcmp( argv[4], "--input" ) ) g_input_mod = true; else if ( argc > 4 ) { cerr << "Invalid fourth argument supplied for --pulse. Only \"--input\" is allowed." << endl; exit( 1 ); } } }; //Noise class NoiseGen: public UGen { public: SAMPLE generateSample() { return 2 * ((rand() / double(RAND_MAX)) - 0.5); } double incrementTime() { //Don't use time in generation of noise. Do nothing here. return 0.0; } void processCmdLineArgs(int argc, char** argv) { if ( argc > 3 ) { cerr << "Only [type] and [input] may be specified for noise generation. Too many arguments were supplied." << endl; exit( 1 ); } else if ( argc > 2 && !strcmp( argv[2], "--input" ) ) g_input_mod = true; else if ( argc > 2 ) { cerr << "Invalid second argument supplied for --noise. Only \"--input\" is allowed." << endl; exit( 1 ); } } }; //Impulse class ImpulseGen: public UGen { public: SAMPLE generateSample() { if ( t >= MY_SRATE / freq ) { t -= MY_SRATE / freq; return 1.0; } else { return 0.0; } } double incrementTime() { t += 1.0; } void processCmdLineArgs(int argc, char** argv) { if (argc < 3) { cerr << "Impulse train generation requires a specified frequency argument." << endl; exit( 1 ); } else if (argc == 4 && (strcmp( argv[3], "--input" ))) // if more than type and freq are supplied and the last argument isn't "--input" { cerr << "Width is not applicable to impulse train generation. No width argument should be supplied. Only --input may be supplied as a third argument." << endl; exit( 1 ); } else if (argc > 4) { cerr << "Only [type], [frequency], and [input] may be specified for impulse train generation. Too many arguments were supplied." << endl; exit( 1 ); } //Force Frequency to be numeric if (strlen(argv[2]) != strspn(argv[2],"0123456789.") ) { cerr << "Frequency value (second argument) must be numeric." << endl; exit( 1 ); } freq = atof(argv[2]); // Check actual argument value if ( freq <= 0.0 || freq >= MY_SRATE / 2.0 ) { cerr << "[frequency] value outside proper range. Correct usage is as follows:" << endl; usage(); } if ( argc > 3 && !strcmp( argv[3], "--input" ) ) g_input_mod = true; else if ( argc > 3 ) { cerr << "Invalid third argument supplied for --impulse. Only \"--input\" is allowed." << endl; exit( 1 ); } } }; // --------------------------------------- // Callback function int tick( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *sigGenPointer ) { //output warning if stream status is not good if ( status ) cerr << "Stream over/underflow detected." << endl; //recast output and input buffer pointers register SAMPLE *oSamples = (SAMPLE *) outputBuffer; register SAMPLE *iSamples = (SAMPLE *) inputBuffer; //recast sigGen pointer UGen* sigGen = (UGen*) sigGenPointer; //Set values in output buffer ------------- // If modulating by input if ( g_input_mod ) { for (unsigned int i = 0; i < nBufferFrames; i++) { *oSamples++ = sigGen->generateSample() * (*iSamples++); sigGen->incrementTime(); } } // else, we are not modulating by the input else { for (unsigned int i = 0; i < nBufferFrames; i++) { *oSamples++ = sigGen->generateSample(); sigGen->incrementTime(); } } return 0; } int main(int argc, char *argv[]) { // Define RtAudio Parameters unsigned int inChannels = 0, outChannels = 1, fs = MY_SRATE, bufferFrames = 256, iDevice = 0, oDevice = 0, iOffset = 0, oOffset = 0; // Input argument checking ---------- // Basic check on number of arguments if (argc < 2) { cerr << "Expecting at least 1 input argument. Correct usage is as follows:" << endl; usage(); } else if (argc > 5) { cerr << "Expecting at most 4 input arguments. Correct usage is as follows:" << endl; usage(); } // Determine generator type and create generator UGen* sigGen; if ( strcmp( argv[1], "--sine" ) == 0) { sigGen = new SineGen; } else if ( strcmp( argv[1], "--saw" ) == 0) { sigGen = new SawGen; } else if ( strcmp( argv[1], "--pulse" ) == 0) { sigGen = new PulseGen; } else if ( strcmp( argv[1], "--noise" ) == 0) { sigGen = new NoiseGen; } else if ( strcmp( argv[1], "--impulse" ) == 0) { sigGen = new ImpulseGen; } else { cerr << "Improper [type] specification. Correct usage is as follows: " << endl; usage(); } // Process command line arguments, checking with regard to that generator type sigGen->processCmdLineArgs(argc, argv); //If modulating by the input we need to open an input channel if ( g_input_mod ) inChannels = 1; RtAudio adac; if ( adac.getDeviceCount() < 1 ) { std::cout << "\nNo audio devices found!\n"; exit( 1 ); } // Let RtAudio print messages to stderr. adac.showWarnings( true ); // Set the number of channels for input and output. RtAudio::StreamParameters iParams, oParams; // Input if ( iDevice == 0 ) iParams.deviceId = adac.getDefaultInputDevice(); else iParams.deviceId = iDevice - 1; iParams.nChannels = inChannels; iParams.firstChannel = iOffset; // Output if ( oDevice == 0 ) oParams.deviceId = adac.getDefaultOutputDevice(); else oParams.deviceId = oDevice - 1; oParams.nChannels = outChannels; oParams.firstChannel = oOffset; //Create options struct in case we want to set some of these RtAudio::StreamOptions options; try { if ( inChannels ) adac.openStream( &oParams, &iParams, RTAUDIO_FLOAT64, fs, &bufferFrames, &tick, (void*) sigGen, &options ); else adac.openStream( &oParams, NULL, RTAUDIO_FLOAT64, fs, &bufferFrames, &tick, (void*) sigGen, &options ); } catch ( RtError& err ) { err.printMessage(); exit( 1 ); } try { adac.startStream(); } catch ( RtError& err ) { err.printMessage(); goto cleanup; } while (true) { usleep( 10000 ); } // If we get here, stop the stream. try { adac.stopStream(); } catch( RtError& err ) { err.printMessage(); } cleanup: if ( adac.isStreamOpen() ) adac.closeStream(); return 0; }