// // Programmer: Craig Stuart Sapp // Creation Date: Tue Jun 13 10:14:57 PDT 2000 // Last Modified: Tue Jul 4 21:37:29 PDT 2000 // Filename: ...sig/doc/examples/all/batontap/batontap.cpp // Syntax: C++; batonImprov 2.0 // // Description: Two general methods of tempo control for the radio // batons. // #include "batonImprov.h" #define MAXBEATPERIOD 2500 /* maximum time between beats, ignore if longer */ #define MAXLAGTIME 70 /* maximum time for same instant in time */ #define BASENOTE 60 /* MIDI base note for performance */ #define BATONSRATE 20.0 /* Radio Baton position reporting samping rate */ /*----------------- beginning of improvization algorithms ---------------*/ CircularBuffer beattrigtime(1000); // times of previous beats int avglen = 1; // beats to average for tempo double idletime = 0.0; // time to sleep between iterations SigTimer beatFraction; // for timing performance locations double beatLocation = 0.0; // for timing performance double lastBeatLocation = 0.0; // for timing performance int divisions = 4; // performance divisions per beat int newdivisions = 4; // when changing the beat divisions int divisionChange = 0; // for changing the beat divisions int currentsub = -1; // currently playing subdivision int numbermeaning = 'd'; // meaning of digits on keyboard int runningQ = 0; // boolean for playing notes int printsubQ = 1; // boolean for printing subbeats int printbeatQ = 0; // boolean for printing beat info int printpredictionQ = 0; // boolean for printing beat pred. info Voice voice; // for performance of subbeats int predictionQ = 0; // boolean for beat prediction int beatprediction = 0; // prediction location of next beat int newprediction = 0; // newest predition of beat location int correction = 0; // correction between old and new pred. int triggerplane = 110; // trigger plane location // function declarations: double averagePeriod(CircularBuffer& buffer, int avglen); void beattrig(int trigtime); double periodToTempo(int mstime); double periodToTempo(double mstime); void playnotes(double beatLocation, double lastBeatLocation, int& currentsub); void performnote(int subdivision, int totaldivision); void adjustTempoWithPrediction(void); void stick1position(void); double tempoToPeriod(double tempo); /*--------------------- 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 << "BATONTAP by Craig Stuart Sapp, June 2000\n" "Commands:\n" " [, ] = lower/raise the number of periods to average for tempo\n" " d# = set the divisions per beat\n" " p = toggle beat prediction\n" " u = toggle display of subbeats on screen\n" " o = toggle display of beat statistics\n" " i = toggle display of beat prediction info\n" << endl; } ////////////////////////////// // // initialization -- this function is called by the improv // interface once at the start of the program. Put items // here which need to be initialized at the beginning of // the program. // void initialization(void) { baton.stick1position = stick1position; beattrigtime.reset(); beattrigtime[0] = 0; beattrigtime[1] = 0; beattrigtime[2] = 0; beattrigtime[3] = 0; eventIdler.setPeriod(idletime); runningQ = 0; voice.setPort(synth.getPort()); beatFraction.setTempo(80); } ////////////////////////////// // // finishup -- this function is called by the improv interface // whenever the program 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 global 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) { lastBeatLocation = beatLocation; beatLocation = beatFraction.getPeriodCount(); if (beattrigtime[1] != 0 && beattrigtime[0] - beattrigtime[1] < MAXBEATPERIOD) { if (runningQ == 0) { lastBeatLocation = 0.0; } runningQ = 1; } else { runningQ = 0; } if (runningQ && beatLocation < 1.0) { playnotes(beatLocation, lastBeatLocation, currentsub); } } ////////////////////////////// // // prepareNewPrediction -- make a new prediction when // the next beat will occur. // int prepareNewPrediction(void) { // get the instantaneous (average) velocity double velocity1 = baton.z1pb[0] - baton.z1pb[1]; double velocity2 = baton.z1pb[1] - baton.z1pb[2]; double velocity = (velocity1 + velocity2) / 2.0; // do stuff here with accelerations, etc., to improve prediction. // now know use trajectory velocity to figure // out when we should arrive at the trigger plane // if heading in the wrong direction, then don't make // a prediction: if (velocity < -2) { return -1; } else if (velocity < 2) { return 0; } double tickcount = triggerplane / velocity; return t_time + (int)(BATONSRATE * tickcount); } ////////////////////////////// // // playnotes -- // void playnotes(double beatLocation, double lastBeatLocation, int& currentsub) { int desiredsub = (int)(beatLocation * divisions); // if there is only one division per beat, then need // a special case: if (beatLocation < lastBeatLocation && divisions == 1) { performnote(0, 1); currentsub = 0; return; } int amount, j; if (desiredsub == currentsub) { return; } else if (desiredsub < currentsub) { // first play all notes upto the previous beat location: amount = divisions - 1 - currentsub; for (j=0; j 1000) { avglen = 1000; } cout << "Beat period averaging is: " << avglen << endl; break; case '-': // lower idle period idletime -= 1.0; if (idletime < 0.0) { idletime = 0.0; } cout << "Idle time = " << idletime << endl; eventIdler.setPeriod(idletime); break; case '=': // raise idle period idletime += 1.0; cout << "Idle time = " << idletime << endl; eventIdler.setPeriod(idletime); break; case 'd': // set the meaning of the numbers to divisions numbermeaning = 'd'; cout << "Digits mean divisions per beat" << endl; break; } if (isdigit(key)) { switch (numbermeaning) { case 'd': if (key == '0') { break; } if (t_time - beattrigtime[0] < MAXLAGTIME) { divisions = key - '0'; // number of notes per beat cout << "Beat division: " << divisions << endl; divisionChange = 0; } else { newdivisions = key - '0'; // number of notes per beat cout << "Next beat divisions: " << divisions << endl; divisionChange = 1; } break; } } } /*------------------ end improvization algorithms -----------------------*/ /////////////////////////////// // // beattrig -- trigger a new beat. // void beattrig(int trigtime) { beattrigtime.insert(trigtime); double avgperiod = averagePeriod(beattrigtime, avglen); int diff = beattrigtime[0] - beattrigtime[1]; if (printbeatQ && beattrigtime[1] != 0 && diff < MAXBEATPERIOD) { cout << "T:" << trigtime << "\tbeat: " << diff; if (avglen > 1) { cout << "\tAvg: " << avgperiod; } cout << "\tTempo: " << periodToTempo(diff); if (avglen > 1) { cout << "\tAvg: " << periodToTempo(avgperiod); } cout << endl; } if (avgperiod > 0) { beatFraction.setPeriod(avgperiod); beatFraction.reset(); // make a prediction for when the next beat will occur beatprediction = t_time + (int)avgperiod; } } /////////////////////////////// // // beattrig -- trigger a new beat. // double averagePeriod(CircularBuffer& buffer, int avglen) { double sum = 0.0; int i = 0; int diff; for (i=1; i<=avglen; i++) { diff = buffer[i-1] - buffer[i]; if (buffer[i] == 0) { break; } else if (diff > MAXBEATPERIOD) { break; } else { sum += diff; } } i--; if (i == 0) { return 0.0; } else { return sum/i; } } /////////////////////////////// // // periodToTempo -- converts a time in milliseconds into a tempo // in beats per minute. // double periodToTempo(int mstime) { return (60000.0 / mstime); } double periodToTempo(double mstime) { return (60000.0 / mstime); } double tempoToPeriod(double tempo) { return (60000.0 / tempo); } ////////////////////////////// // // stick1position -- called whenever a stick 1 position report comes in. // void stick1position(void) { if (predictionQ && beatLocation > 0.5) { adjustTempoWithPrediction(); } } ////////////////////////////// // // adjustTempoWithPrediction -- // void adjustTempoWithPrediction(void) { newprediction = prepareNewPrediction(); // don't know what is happening (stationary stick) if (newprediction == 0) { return; } // going in the wrong direction if (newprediction < 0) { newprediction = beatprediction + 100; return; } correction = beatprediction - newprediction; if (abs(correction) > beatFraction.getPeriod() * 0.7) { // error is too large, so ignore correction return; } // now have a half-way decent prediction, so adjust // tempo so that beatFraction timer will hit 1.0 at // newly predicted beat location. double oldTempo = beatFraction.getTempo(); double newPeriod = beatFraction.getPeriod() + correction; double newTempo = periodToTempo(newPeriod); if (printpredictionQ) { cout << "Time: " << t_time << "\tError: " << correction << "\tOld Tempo = " << oldTempo << "\tNew Tempo = " << newTempo << endl; } beatFraction.setTempo(newTempo); beatprediction = newprediction; } // md5sum: 48f142d9e661d25f4c7746cf7651f787 batontap.cpp [20050403]