//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Last Modified: Fri Mar 31 13:47:54 PST 2000
// Last Modified: Sat Apr  1 11:54:55 PST 2000
// Filename:      ...improv/doc/examples/batonImprov/beatvel/beatvel.cpp
// Syntax:        C++; batonImprov
//  
// Description:   Determines musical beats according to the velocity of the
//                z-axis.  The space bar will record the data used to 
//                determine the beats and the beat states:
//                  beat state 0  = no beat is expected
//                  beat state 1  = beat threshold is exceeded, beat coming soon
//                  beat state 10 = a beat has occurred
//                  beat state 2  = a beat has just occurred, do not change 
//                                  state until threshold timeout occurs.
// 
//                The output pitched played when a beat is detected is related 
//                to the location on the z-axis at the time of the beat.
//

#include "batonImprov.h" 

#ifndef OLDCPP
   #include <fstream>
   using namespace std;
#else
   #include <fstream.h>
#endif


/*----------------- beginning of improvization algorithms ---------------*/

CircularBuffer<short> zposition(32); // a history of the past positions
CircularBuffer<short> zvelocity(32); // a history of the past velocities
double  threshold        = 4.0;      // Minimum activation velocity
double  thresholdTime    = 0;        // Time which threshold was reached
int     thresholdQ       = 0;        // Boolean for threshold region
double  thresholdTimeout = 75;       // ms timeout for threshold
int     beatnum          = 1;        // beat number

fstream datafile;                    // for recording beats
int     datawriteQ       = 0;        // boolean for writing datafile
int     datasession      = 0;        // for multiple recordings in one file
int     datamarker       = 1;        // for placing marker in recorded data
Voice   voice;                       // for playing a note on the beat

/*----------------- FUNCTIONS -------------------------------------------*/

//////////////////////////////
//
// writedata -- write z velocity data to a test file.
//

void writedata(int velocity, int state) {
   if (datawriteQ) {
      datafile << velocity << '\t' << state << '\n';
   }
}
   

//////////////////////////////
//
// writecomment -- write comment to the data file if recording.
//

#define writecomment(x) if (datawriteQ) { datafile << "# " << x; } 



//////////////////////////////
//
// stick1position -- callback function for stick1 position reporting.
//

void stick1position(void) {
   int beatQ = 0;
   zposition.insert(baton.z1p);
   if (zposition.getCount() <= 1) {
      return;
   } 
 
//   cout << baton.t1pb[0] - baton.t1pb[1] << endl;

   zvelocity.insert(zposition[0] - zposition[1]);
   if (zvelocity.getCount() <= 1) {
      return;
   } 

   if (thresholdQ == 2) {
      if (t_time > thresholdTime + thresholdTimeout) {
         thresholdQ = 0;
         writedata(zvelocity[0], 0);
         goto nextline;
      } else {
         writedata(zvelocity[0], 2);
         goto nextline;
      }
   }
   if (thresholdQ == 0) {
      if (zvelocity[0] > threshold) {
         thresholdQ = 1;
         thresholdTime = t_time;
         writedata(zvelocity[0], 1);
         goto nextline;
      } else {
         writedata(zvelocity[0], 0);
         goto nextline;
      }
   }
   if (thresholdQ == 1) {
      if (zvelocity[0] < zvelocity[1]) {
         writedata(zvelocity[0], 10);
         cout << "Beat: " << beatnum++ << endl;
         voice.play(baton.z1p - 30, 64);
         if (beatnum % 4 == 0) {
            cout << "\n";
         }
         thresholdQ = 2;
         goto nextline;
      } else {
         writedata(zvelocity[0], 1);
         goto nextline;
      }
   }

   writedata(zvelocity[0], beatQ);

nextline:
   ;
}


/*--------------------- maintenance algorithms --------------------------*/

void description(void) {
   cout 
   << "\nThis program detects musical beats according to the velocity of\n"
   << "the z-axis positions. \n"
   << "  Keyboard commands:\n"
   << "  -/=   = lower/raise the velocity trigger threshold\n"
   << "  [/]   = lower/raise the threshold blackout time\n"
   << "  space = start/stop data recording\n"
   << "  s     = display the status of data recording\n"
   << "  m     = put a marker in the data recording\n"
   << "the z-axis positions\n"
   << endl;
}


void initialization(void) { 
   description();
   baton.stick1position = stick1position;
   zposition.reset();
   zvelocity.reset();
   datafile.open("datafile", ios::out);
   if (!datafile.is_open()) {
      cerr << "Error: cannot open output file: datafile" << endl;
      exit(1);
   }
   voice.setChannel(0);        // default MIDI channel for beat performance
}

void finishup(void) { }


/*-------------------- main loop algorithms -----------------------------*/

void mainloopalgorithms(void) { }


/*-------------------- triggered algorithms -----------------------------*/

void stick1trig(void) { }

void stick2trig(void) { }

void b14plustrig(void) { }

void b15plustrig(void) { }

void b14minusuptrig(void) { }

void b14minusdowntrig(void) { }

void b15minusuptrig(void) { }

void b15minusdowntrig(void) { }

void keyboardchar(int key) { 
   switch (key) {
      case '-':                 // lower threshold by one
      case '_':                 // lower threshold by one
         threshold = threshold - 1;
         if (threshold < 1) {
            threshold = 1;
         }
         cout << "Velocity threshold is: " << threshold << endl;
         writecomment("Threshold: " << threshold << "\n");
         break;
      case '=':                 // raise threshold by one
      case '+':                 // raise threshold by one
         threshold = threshold + 1;
         if (threshold > 50) {
            threshold = 50;
         }
         cout << "Velocity threshold is: " << threshold << endl;
         writecomment("Threshold: " << threshold << "\n");
         break;

      case '[':                 // lower timeout threshold by 5
         thresholdTimeout = thresholdTimeout - 5;
         if (thresholdTimeout < 20) {
            thresholdTimeout = 20;
         }
         cout << "threshold timeout is: " << thresholdTimeout << endl;
         writecomment("Timeout: " << thresholdTimeout << "\n");
         break;
      case ']':                 // raise timeout threshold by 5
         thresholdTimeout = thresholdTimeout + 5;
         if (thresholdTimeout > 1000) {
            thresholdTimeout = 1000;
         }
         cout << "threshold timeout is: " << thresholdTimeout << endl;
         writecomment("Timeout: " << thresholdTimeout << "\n");
         break;

      case 's':                 // display recording status
         if (datawriteQ) {
            cout << "\nRecording data" << endl;
         } else {
            cout << "\nNOT Recording data" << endl;
         }
         break;
         
      case 'm':                 // place a data marker in the recording
         if (datawriteQ) {
            cout << "\nData Marker: " << datamarker << endl;
            writecomment("Marker: " << datamarker++ << "\n");
         }
         break;

      case ' ':                 // toggle writing of datafile
         datawriteQ = !datawriteQ;
         if (!datafile.is_open()) {
            datawriteQ = 0;
         }
         if (datawriteQ) {
            datasession++;
         }
         if (datawriteQ) {
            cout << "Starting to write data: session " << datasession << endl;
            writecomment("Session: " << datasession << "\n");
         } else {
            cout << "Stopping data session " << datasession << " recording" 
                 << endl;
         }
         break;
   }
}


/*------------------ end improvization algorithms -----------------------*/



// md5sum: 779aab60dbfae8df50fb56aee9cf7534 beatvel.cpp [20050403]