//
// 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]