// // Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu> // Creation Date: Mon Oct 26 17:00:30 PST 1998 // Last Modified: Wed Oct 28, 1998 // Last Modified: Fri Jun 25 16:38:47 PDT 1999 // Filename: ...sig/doc/examples/improv/synthImprov/position2/position2.cpp // Syntax: C++; synthImprov 2.0 // // Description: plays notes according to input data coming from NIDAQ card. // Sensor used to design this program was an ultrasonic sensor // part 134105 from Senix Corporation, 52 Maple St. Bristol. VT 05443. // Bought at Jameco in Redwood City CA, Fall 1998. // The sonar sensor output 0 volts when an object is 6 inches or less // positioned infront of the ultrasonic speaker. The output voltage // rises linearly to 10 volts around the position 5 feet from the // sensor. Beyond the far range of the sensor, the voltage stays // at its maximum (9.9975 or so). This program should work with // other types of sensors, but specific musical interface may be less // practical. Also works well on Force Sensitive Resistors. // #include "synthImprov.h" /* basic MIDI I/O interface with MIDI synthesizer */ #include "NidaqSensor.h" /* interface with NIDAQ Card(Windows 95 only) */ /*----------------- beginning of improvization algorithms ---------------*/ // global variables: double min = 2.4; // miniumum value from NIDAQ, adapts double max = 2.5; // maximum value from NIDAQ, adapts int keyno; // MIDI keynumber to play int velocity; // MIDI attack velocity int instrument = GM_VIBRAPHONE; // instrument on MIDI synth int sustain = 0; // sustain pedal (is initially off) int volume = 0; // value for volume control int oldVolume = 0; // previous value for volume control int display = 0; // boolean for displaying data on screen int method = 0; // which note generation method to use int methodCount = 4; // number of generation methods int minimumDuration = 10; // 10 millisec is fastest tempo for neighb. int lastNoteTime = 0;// time of last note for method 2 int volControl = 0; // boolean for volume control mode int interval = 1; // for certain note controller methods int channel = 0; // NIDAQ channel to use Voice voice; // for MIDI output (auto handles note off messages) Nidaq sensor; // NIDAQ card interface for sensor SigTimer rhythmTick; // for timing rhythm of voice SigTimer displayTimer; // for displaying positions on the screen int lastVolTime =0; // for volume continuous controller int difference; // static temp value int currentDuration; // static temp value int direction = 0; // boolean for pitch range direction #define STATE_COUNT 8 /* number of slots to save performance states */ #define STATE_VARS 9 /* number of variables to store for state */ int state[STATE_COUNT][STATE_VARS] = {0}; // perf. state variables int stateQ = 0; // true if save state is activated int stateA = 0; // true if restore state is activated // rhythmic ostinato variables #define PATTERN_COUNT 3 // number of rhythmic patterns int patternSize[PATTERN_COUNT] = {1, 6, 6}; // array sizes of patterns int patternIndex[PATTERN_COUNT] = {0,0,0}; // current index of patterns int *pattern[PATTERN_COUNT]; // the array of patterns int pattern0[1] = {1}; // constant 16th note pattern int pattern1[6] = {3, 1, 2, 2, 1, 3}; // 3=dotted eighth, 2=eighth int pattern2[6] = {2, -2, 2, -2, -2, 2}; // rests are negative int curpat = 0; // pattern to currently play // non-interface function declarations: void determineMidiNotes(void); void newNoteMethod1(void); void newNoteMethod2(void); void volumeMethod(void); void saveState(int aState); void restoreState(int aState); void printState(int aState); /*--------------------- maintenance algorithms --------------------------*/ ////////////////////////////// // // description -- this function is called by the improv interface // whenever a capital "D" is pressed on the computer keyboard. // Put a description of the program and how to use it here. // void description(void) { cout << '\n'; printboxtop(); pcl("POSITION2: Craig Stuart Sapp <craig@ccrma.stanford.edu> 26 Oct 1998"); printintermediateline(); psl(" NIDAQ device is hardwired to device #1. Initial data range is"); psl(" 2.4 to 2.5, but range adapts. So go through whole range of sensor"); psl(" to set the software range."); psl(" Commands:"); psl(" \"0\"-\"9\" = select NIDAQ analog input channel"); psl(" \"d\" = toggle sensor display. \"s\" = toggle sustain pedal"); psl(" \"-\" = decrement instrument. \"=\" = increment instrument number"); psl(" \"m\" = control method select. \"v\" = volume control toggle"); psl(" \"r\" = select new rhythm. \"x\" = reset the sensor range"); psl(" \",\" = decrease interval. \".\" = increase interval"); psl(" \"t\" = slow tempo. \"g\" = increase tempo"); psl(" \"u\" = display current state. \"z\" = reverse pitch range"); psl(" \"q\" = save performance state. \"]\" = restore preformance state"); psl("Performance state storage keys:"); pcl("\"i\" \"o\" \"p\" \"[\""); pcl(" \"k\" \"l\" \";\" \"\'\""); printboxbottom(); } ////////////////////////////// // // initialization -- this function is called by the improv // intervace once at the start of the program. Put items // here which need to be initialized at the beginning of // the program. // void initialization(void) { sensor.initialize(gargv); // start CVIRTE stuff for NIDAQ Card sensor.setPollPeriod(1); // check for new data every 1 millisecond sensor.setBufferSize(8); // buffer size of transfer frame sensor.setSrate(500); // set NIDAQ sampling rate to X Hz cout << "starting data aquisition ... " << flush; sensor.start(); // start aquiring data from NIDAQ Card cout << "ready." << endl; voice.setPort(synth.getOutputPort()); // specify output port of voice channel = 0; // specify output channel of voice voice.pc(instrument); // set the MIDI synthesizer tambre rhythmTick.setPeriod(100); // minimum note duration every X milliseconds displayTimer.setPeriod(200); // display position every X milliseconds cout << "Using instrument number " << instrument << " (vibraphone)" << endl; // set the contents of the pattern array pattern[0] = pattern0; pattern[1] = pattern1; pattern[2] = pattern2; } ////////////////////////////// // // finishup -- this function is called by the improv interface // whenever the function is exited. Put items here which // need to be taken care of when the program is finished. // void finishup(void) { } /*-------------------- main loop algorithms -----------------------------*/ ////////////////////////////// // // mainloopalgorithms -- this function is called by the improv interface // continuously while the program is running. The variable t_time // which stores the current time is set just before this function is // called and remains constant while in this functions. // void mainloopalgorithms(void) { sensor.checkPoll(); // see if it is time to check for new data frame determineMidiNotes(); // check if it is time to play a MIDI note if (display && displayTimer.expired()) { displayTimer.reset(); cout << "\rmin = " << min << "\tmax = " << max << "\tcurrent = " << sensor[channel][channel][0] << "\t\t" << flush; } } ////////////////////////////// // // determineMidiNotes -- a user-specific function which is not part // of the improv interface. This function determines the method // of note generation/control from incoming position data from // the sensor. // void determineMidiNotes(void) { switch (method) { case 0: newNoteMethod1(); break; case 1: newNoteMethod2(); break; case 3: volumeMethod(); break; } if (volControl && method != 3) { volumeMethod(); } } ////////////////////////////// // // newNoteMethod2 -- plays a new note whenever sensor detects // a new note. The global variable "interval" controls // the determination of a new note. interval = 1 means that // the new note can be any intervale larger than a unison. // interval = 7 would mean that a new note would be triggered // whenever a interval size of a tritone (interval 6) is // traversed. // void newNoteMethod2(void) { // if the duration of the previous note is long enough and // the sensor is not at its min or max value, then play a note // if the interval distance has been traversed since the last // note which was played. if (t_time - voice.getOnTime() > minimumDuration && (sensor[channel][0] != min || sensor[channel][0] != max)) { // adjust the range of the sensor values if necessary: if (sensor[channel][0] > max) max = sensor[channel][0]; if (sensor[channel][0] < min) min = sensor[channel][0]; // keyno is the note to be played. It is in the range from 0 to 127 keyno = 127 - (int)((sensor[channel][0] - min) / (max - min) * 127); if (direction) { keyno = 127 - keyno; } // limit the range of keyno to valid range, in case max/min not correct if (keyno < 0) keyno = 0; if (keyno > 127) keyno = 127; // if the interval between the last note and the current position's // note is large enough, then play the new note. difference = voice.getKey() - keyno; if (difference < 0) { difference = -difference; } if (difference >= interval) { if (keyno < 2 || keyno > 125) { // turn off notes out of sensor range voice.off(); } else { velocity = 120; voice.play(keyno, velocity); } } } } ////////////////////////////// // // volumeMethod -- interprets the sensor data as a volume control // which in MIDI standard is continuous controller #7 (0 offset) // which can take a value from 0 (no sound) to 127 (max sound). // Max sound is near the sonar detector, min sound is away from the // sonar detector. If the sensor data suddenly jumps to maximum // voltage, then the loudness control is disabled to keep the // current loudness level in effect. // void volumeMethod(void) { if (t_time - lastVolTime > 20) { // if statement prevents MIDI saturation lastVolTime = t_time; // generate a volume level in the range from 0 to 127. volume = 127 - (int)((sensor[channel][0] - min) / (max - min) * 127); // disactivate volume control at extremes of range if (keyno <= 6) return; if (keyno >= 126) return; // this if statement isn't necessary but prevents sending out // duplicate volume messages which might tie up the MIDI cable. if (volume != oldVolume) { oldVolume = volume; voice.cont(7, volume); } } } ////////////////////////////// // // newNoteMethod1 -- plays notes in a rhythmic ostinato // void newNoteMethod1(void) { currentDuration = pattern[curpat][patternIndex[curpat]]; if (rhythmTick.expired() >= abs(currentDuration)) { // time to play a new MIDI note rhythmTick.update(abs(currentDuration)); // set the index of the next note duration patternIndex[curpat]++; patternIndex[curpat] %= patternSize[curpat]; if (rhythmTick.expired() > 10) { // if this happens, then the timer is WAY behind, // so just reset it. rhythmTick.reset(); } // check for rests if (currentDuration < 0) { return; } // adjust the range of the sensor values if necessary: if (sensor[channel][0] > max) max = sensor[channel][0]; if (sensor[channel][0] < min) min = sensor[channel][0]; // create a MIDI key number in the range from 127 (near) to 0 (far) keyno = 127 - (int)((sensor[channel][0] - min) / (max - min) * 127); if (direction) { keyno = 127 - keyno; } // make sure there wasn't an invalid min or max value if (keyno < 0) keyno = 0; if (keyno > 127) keyno = 127; if (keyno < 2 || keyno > 125) { voice.off(); } else { velocity = 120; voice.play(keyno, velocity); } } } /*-------------------- triggered algorithms -----------------------------*/ /////////////////////////////// // // keyboardchar -- this function is called by the improv interface // whenever a key is pressed on the computer keyboard. // Put commands here which will be executed when a key is // pressed on the computer keyboard. // void keyboardchar(int key) { // select a NIDAQ channel if the key is a number from 0 to 9: if (isdigit(key)) { cout << "\nresetting data aquisition ... " << flush; channel = key - '0'; cout << "done." << endl; cout << "Channel " << channel << " selected" << endl; return; } switch (key) { case '[': // performance state 4 if (stateA) { printState(4); } else if (stateQ) { saveState(4); } else { restoreState(4); } stateA = stateQ = 0; break; case ';': // performance state 7 if (stateA) { printState(7); } else if (stateQ) { saveState(7); } else { restoreState(7); } stateA = stateQ = 0; break; case '\'': // performance state 8 if (stateA) { printState(8); } else if (stateQ) { saveState(8); } else { restoreState(8); } stateA = stateQ = 0; break; case '-': // decrement the MIDI instrument instrument--; if (instrument < 0) { instrument = 0; } cout << "\n Instrument: " << instrument << endl; voice.pc(instrument); break; case '=': // increment the MIDI instrument instrument++; if (instrument > 127) { instrument = 127; } cout << "\nInstrument: " << instrument << endl; voice.pc(instrument); break; case ',': // decrement the method2 interval interval--; if (interval <= 0) { interval = 1; } cout << "\ninterval: " << interval << endl; break; case '.': // increment the method2 interval interval++; if (interval > 24) { interval = 24; } cout << "\ninterval: " << interval << endl; break; case ']': // set restore state flag if (stateQ != 0) { stateQ = 0; } stateA = 1; break; case 'd': // toggle display of data on screen display = !display; break; case 'g': // decrease sixteenth note duration by 10 ms if (rhythmTick.getPeriod() >= 20.0) { rhythmTick.setPeriod(rhythmTick.getPeriod() - 3.0); } cout << "\nsixteenthnote duration set to " << rhythmTick.getPeriod() << " milliseconds" << endl; break; case 'i': // performance state 1 if (stateA) { printState(1); } else if (stateQ) { saveState(1); } else { restoreState(1); } stateA = stateQ = 0; break; case 'k': // performance state 5 if (stateA) { printState(5); } else if (stateQ) { saveState(5); } else { restoreState(5); } stateA = stateQ = 0; break; case 'l': // performance state 6 if (stateA) { printState(6); } else if (stateQ) { saveState(6); } else { restoreState(6); } stateA = stateQ = 0; break; case 'm': // switch to a new note generation method method = (method + 1) % methodCount; cout << "\nUsing method " << method + 1; switch (method) { case 0: cout << " (rhymic pattern) "; break; case 1: cout << " (neighbor) "; break; case 2: cout << " (volume only) "; break; case 3: cout << " (silence) "; break; } cout << endl; break; case 'o': // performance state 2 if (stateA) { printState(2); } else if (stateQ) { saveState(2); } else { restoreState(2); } stateA = stateQ = 0; break; case 'p': // performance state 3 if (stateA) { printState(3); } else if (stateQ) { saveState(3); } else { restoreState(3); } stateA = stateQ = 0; break; case 'q': // set save state flag if (stateA != 0) { stateA = 0; } stateQ = 1; break; case 'r': // select a new rhythmic pattern curpat = (curpat + 1) % PATTERN_COUNT; cout << "\nrhythmic pattern = " << curpat << endl; break; case 's': // toggle the sustain pedal sustain = !sustain; voice.sustain(sustain); // sustain function takes a boolean if (sustain) { cout << "\nsustain pedal is ON" << endl; } else { cout << "\nsustain pedal is OFF" << endl; } break; case 't': // increase sixteenth note duation by 10 ms. rhythmTick.setPeriod(rhythmTick.getPeriod() + 3.0); cout << "\nsixteenth note duration = " << rhythmTick.getPeriod() << " milliseconds" << endl; break; case 'u': // display performance variables cout << "\nsus = " << sustain << "\tint = " << interval << "\tmet = " << method << "\trhy = " << curpat << "\tvol = " << volControl << "\tins = " << instrument << "\tdir = " << direction << "\ttem = " << (int)rhythmTick.getPeriod() << endl; break; case 'v': // toggle volume control volControl = !volControl; if (volControl) { cout << "\nVolume control is ON" << endl; } else { cout << "\nVolume control is OFF" << endl; } break; case 'x': // reset the min and max range min = 2.4; max = 2.5; break; case 'z': direction = !direction; break; } } /////////////////////////// // // saveState -- records performance state variables which can be restored // with the restoreState function to return to a particular // performance setup. Format of state array is: // index 0 = validity of state (0 = bad, 1 = good) // index 1 = [sustain] // index 2 = [interval] // index 3 = [method] // index 4 = [rhythm] // index 5 = [volControl] // index 6 = [instrument] // index 7 = [direction] // index 8 = [rhythmTick.getPeriod()] // void saveState(int aState) { if (aState < 0 || aState >= STATE_COUNT) { return; // invalid state } state[aState][0] = 1; // state is a valid state which can be restored state[aState][1] = sustain; state[aState][2] = interval; state[aState][3] = method; state[aState][4] = curpat; state[aState][5] = volControl; state[aState][6] = instrument; state[aState][7] = direction; state[aState][8] = (int)rhythmTick.getPeriod(); } ////////////////////////////// // // restoreState -- restores a performance state that was previously stored // with the saveState function. // void restoreState(int aState) { if (aState < 0 || aState >= STATE_COUNT) { return; // invalid state } if (state[aState][0] == 0) { return; // also an invalid state } sustain = state[aState][1]; voice.sustain(sustain); interval = state[aState][2]; method = state[aState][3]; curpat = state[aState][4]; volControl = state[aState][5]; instrument = state[aState][6]; voice.pc(instrument); direction = state[aState][7]; rhythmTick.setPeriod(state[aState][8]); } ////////////////////////////// // // printState -- prints the values for a particular stored state // void printState(int aState) { if (aState < 0 || aState >= STATE_COUNT) { cout << "invalid state: " << aState << endl; return; } if (state[aState][0] == 0) { cout << "Empty state" << endl; return; } cout << "\nsus = " << state[aState][1] << "\tint = " << state[aState][2] << "\tmet = " << state[aState][3] << "\trhy = " << state[aState][4] << "\tvol = " << state[aState][5] << "\tins = " << state[aState][6] << "\tdir = " << state[aState][7] << "\ttem = " << state[aState][8] << endl; } /*------------------ end improvization algorithms -----------------------*/ // md5sum: 8a1f8d223af3dd82b107fd0969fd5b0a position2.cpp [20050403]