//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Programmer:    Seny Lee <senylee@ccrma.stanford.edu>
// Creation Date: Mon May 17 10:25:04 PDT 1999
// Last Modified: Tue May 25 20:46:16 PDT 1999
// Last Modified: Fri Jun 25 16:34:54 PDT 1999
// Filename:      .../improv/examples/synthImprov/seny/seny.cpp
// Syntax:        C++; batonSynthImprov 2.1
//  

#include "batonSynthImprov.h"      
#include <ctype.h>

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

#ifdef VISUAL
   double drand48(void) {
      return (double)rand()/RAND_MAX;
   }
#endif


#define BOUND1    50              /* boundary note of low section of keyboard */
#define BOUND2    79              /* boundary note of high section of keybd   */
#define VARIATION  3.0            /* std devs to traverse w/drum controls     */
#define MAXVOICES  12             /* maximum drum voices at one time          */
#define MINVEL     20             /* minimum attack velocity output           */
#define MAXVEL     90             /* maximum attack velocity output           */


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

// Global variables:
int activeQ = 1;                  // true if outputing notes false otherwise
int controlDisplayQ = 1;          // for displaying baton control variables

// variables for keeping track of performer's notes with which baton will
// use to generate notes on the piano in an algorithmic fashion.
int lastPerformerTime = 0;        // time of last performer note on/off
int performerNotes[127] = {0};    // all of the performer's notes being played
int performerPC[12] = {0};        // pitch class of the performer
int performerPCHistory[12] = {0}; // performer pitch class history
int keyDuration = 5000;           // how long to remember performers notes
CircularBuffer<char> keys;        // store keys for memory of previous notes
CircularBuffer<long> keytimes;    // note times for keys buffer

// the variables for stick 1 x-axis density control
double density = 0;               // 0=thick, 1=thin

// keep track of performer's notes for stick 1 y-axis register control
int performerRegion[3] = {0};     // location that the performer is playing
double pregister = 0;             // 0=low, 1=high

// the variables for stick 1 z-axis controlling consonance and dissonance
double consonance = 0.0;         // 0 = consonant, 1 = dissonant
// cintervals are a list of the intervals in an octave starting with
// the most consonant ones and then going to the most dissonant ones.
int    cintervals[12] = {0,7,5,4,9,8,3,10,2,11,1,6};



// loudness variables for baton stick 2 x-axis control of velocity volumes
double avgVol = 0;                // average volume
double avgVolRange = 0;           // standard deviation of the durations
CircularBuffer<double> volumes;   // duration of notes being held by performer
CircularBuffer<double> voltimes;  // duration of notes being held by performer
int volDuration = 5000;           // how long to keep track of volumes
double velocity = 0;              // 0=similar, 1=inverted

// duration variables for baton stick 2 y-axis control of articulations
double avgDur = 0;                // average duration
double avgDurRange = 0;           // standard deviation of the durations
CircularBuffer<long> durations;   // duration of notes being held by performer
CircularBuffer<long> durtimes;    // duration of notes being held by performer
int durDuration = 5000;          // how long to keep track of durations
double articulation = 0;          // 0=staccato, 1=tenuto, 0.5=normal

// the variables for stick 2 z-axis controlling rhythm 
double rhythmType = 0;            // 0=constant, 1=random wide variation
int    zmax = 120;                // for z-axis normalizing
int    zmin = 25;                 // for z-axis normalizing


// baton and buffer checking variables
SigTimer batonTimer;             // time to get new state of baton
SigTimer offTimer;               // time to check buffer for forgetting
SigTimer controlDisplayTimer;    // time to check buffer for forgetting


//////////////////////////////
//
// Radio Drum performance variables
//

Voice    computer;
MidiMessage computerMessage;
Voice    voice[MAXVOICES];
int      voiceState[MAXVOICES] = {0};
int      voiceOnTime[MAXVOICES] = {0};
int      voiceOffTime[MAXVOICES] = {0};
SigTimer voiceTimer;
int      voicePeriod = 100;          
int      displayOutput = 0;            // for displaying output notes of baton


// function declarations:
void checkBuffers(void);
void checkOffNotes(void);
int  countActiveVoices(void);
void displayVariables(void);
void processBaton(void);
void processKeyboard(void);
void updateDuration(void);
void updatePerformRegions(void);
void updateVolume(void);
int  midiLimit(int aNumber);


//////////////////////////////
//
// Radio Drum performance variables
//

void generateVoices(void);


/*--------------------- 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 << 
   "Seny 1 -- project for \n"
   " Keyboard commands:  \n"
   "    n = display piano's history input pitch set\n"
   "    m = display piano's currently on pitch set\n"
   "    d = display baton's output notes\n"
   "    c = display baton's control variables\n"
   "    r = display piano's register tracking variables\n"
   "  ' ' = activate/deactivate program\n"
   "    v = display active voice count of baton's notes\n"
   "    z = forget the piano's current key history\n"
   "    [ = shorten note memory by 1 second\n"
   "    ] = length note memory by 1 second\n"
   << endl;
} 



//////////////////////////////
//
// 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) { 
   batonTimer.setPeriod(50);           // time to get new state of baton
   offTimer.setPeriod(200);            // time to check buffer for forgetting
   controlDisplayTimer.setPeriod(200); // time to check buffer for forgetting

   // set the voice channels all to be 0 for the disklavier and so
   // the channels do not have to be specified when playing the note.
   for (int i=0; i<MAXVOICES; i++) {
      voice[i].setChannel(0);
   }
   computer.setChannel(0);
   computerMessage.time = t_time;
   computerMessage.p0() = 0x90;
   computerMessage.p1() = 0;
   computerMessage.p2() = 0;

   keys.setSize(1000);        // store keys for memory of previous notes
   keytimes.setSize(1000);    // note times for keys buffer
   volumes.setSize(1000);     // duration of notes being held by performer
   voltimes.setSize(1000);    // duration of notes being held by performer
   durations.setSize(1000);   // duration of notes being held by performer
   durtimes.setSize(1000);    // duration of notes being held by performer
}


//////////////////////////////
//
// 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) { 
   if (synth.getNoteCount() > 0) {
      processKeyboard();
   }

   if (offTimer.expired()) {
      checkBuffers();
      checkOffNotes();
      offTimer.reset();
   }

   if (batonTimer.expired()) {
      processBaton();
      batonTimer.reset();
   }

   voicePeriod = (int)(avgDur - avgDurRange);
   if (voicePeriod <= 50) {
      voicePeriod = 50;
   }

   voiceTimer.setPeriod(voicePeriod);
   if (voiceTimer.expired()) {
      generateVoices();
      voiceTimer.reset();
   }

   if (controlDisplayQ && controlDisplayTimer.expired()) {
      displayVariables();
      controlDisplayTimer.reset();
   }

}
      

/*-------------------- 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) { 
   if (isdigit(key)) {
      int mkey = 5 * 12 + key - '0';
      int mvel = (int)(drand48() * 40 + 40);
       
      //turn off old note from computer's point of view
      computerMessage.time = t_time;
      computerMessage.p2() = 0;
      synth.insert(computerMessage);

      cout << "played note: " << mkey << " with velocity: " << mvel << endl;
      computer.play(mkey, mvel);

      computerMessage.time = t_time;
      computerMessage.p0() = 0x90;           // midi note-on command, channel 1
      computerMessage.p1() = mkey;
      computerMessage.p2() = mvel;
      synth.insert(computerMessage);
      cout << "played note: " << mkey << " with velocity: " << mvel << endl;

      return;
   }

   switch (key) {
      case ' ':           // turn output notes on/off
         activeQ = !activeQ;
         if (activeQ) {
            cout << "Program Activated" << endl;
         } else {
            cout << "Program Deactivated" << endl;
         }
         break;

      case 'c':           // toggle baton control display
         controlDisplayQ = !controlDisplayQ;
         if (controlDisplayQ == 0) {
            cout << endl;
         }
         break;
         
      case 'd':           // display baton's output notes
         displayOutput = !displayOutput;
         if (displayOutput) {
            cout << "Baton output notes display turned ON" << endl;
         } else {
            cout << "Baton output notes display turned OFF" << endl;
         }
         break;

      case 'n':           // display performerPCHistory pitches
         {
         cout << "Piano notes: ";
         for (int i=0; i<11; i++) {
            cout << performerPCHistory[i] << ", ";
         }
         cout << performerPCHistory[11] << endl;
         }
         break;

      case 'm':           // display performerPC pitches
         {
         cout << "Notes on:    ";
         for (int i=0; i<11; i++) {
            cout << performerPC[i] << ", ";
         }
         cout << performerPC[11] << endl;
         }
         break;

      case 'r':           // display the register information
         {
         cout << "Register: "
              << performerRegion[0]
              << performerRegion[1]
              << performerRegion[2]
              << endl;
         }
         break;

      case 'v':           // display the active voice count for baton
         {
         cout << endl << "Active voices = " << countActiveVoices();
         cout << ": ";
         for (int z=0; z<MAXVOICES; z++) {
            if (voiceState[z]) {
               cout << " " << (int)voice[z].getKey();
            } else {
               cout << "  0";
            }
         }
         cout << endl;
         }
         break;

      case 'z':           // forget the current piano's key history
         {
         for (int z=0; z<12; z++) { 
            performerPCHistory[z] = 0;
         }
         keys.reset();
         keytimes.reset();
         }
         break;

      case '[':           // decrease the note memory time
         keyDuration -= 1000;
         if (keyDuration < 1000) {
            keyDuration = 1000;
         }
         cout << "Note memory time set to : " << keyDuration/1000 
              << " seconds" << endl;
         break;

      case ']':           // increase the note memory time
         keyDuration += 1000;
         if (keyDuration > 60000) {
            keyDuration = 60000;
         }
         cout << "Note memory time set to : " << keyDuration/1000 
              << " seconds" << endl;
         break;

      case '-':           // turn off computer keyboard note
         // turn off old note from computer's point of view
         computerMessage.time = t_time;
         computerMessage.p2() = 0;
         synth.insert(computerMessage);

         // turn off old note from synthesizer's point of view
         computer.off();
         break;
   }

}


void stick1trig(void) {
   generateVoices();
}

void stick2trig(void) { 
   generateVoices();
}

void b14plustrig(void) { }
void b15plustrig(void) { }
void b14minusuptrig(void) { }
void b14minusdowntrig(void) { }
void b15minusuptrig(void) { }
void b15minusdowntrig(void) { }



/*------------------ begining of assistant functions --------------------*/

//////////////////////////////
//
// checkBuffers -- look at the history buffers and determine if any
//     of the buffers needs to be emptied of any contents.
//

void checkBuffers(void) {

   // look at the volume buffers for old volumes:
   while ( (voltimes.getCount() > 0) && (voltimes[voltimes.getCount()-1] <
         t_time - volDuration) ) {
      volumes.extract();
      voltimes.extract();
   }

   // look at the duration buffers for old durations:
   while ( (durtimes.getCount() > 0) && (durtimes[durtimes.getCount()-1] <
         t_time - durDuration) ) {
      durations.extract();
      durtimes.extract();
   }

   // look at the key buffers for old keys:
   int oldkey;
   while ( (keytimes.getCount() > 0) && (keytimes[keytimes.getCount()-1] <
         t_time - keyDuration) ) {
      oldkey = keys.extract();
      keytimes.extract();
  
      performerPCHistory[oldkey%12]--;
      if (performerPCHistory[oldkey%12] < 0) {
         performerPCHistory[oldkey%12] = 0;
      }

   }

}



//////////////////////////////
//
// checkOffNotes -- check the voices array to see if any of the notes
//    need to be turned off.
//

void checkOffNotes(void) {
   for (int i=0; i<MAXVOICES; i++) {
      if (voiceState[i] && voiceOffTime[i] < t_time) {
         voice[i].off();
         voiceState[i] = 0;
      }
   }
}



//////////////////////////////
//
// countActiveVoices -- return the number of baton voices that
//   are currently on.
//

int countActiveVoices(void) {
   int output = 0;
   for (int i=0; i<MAXVOICES; i++) {
      if (voiceState[i]) {
         output++;
      }
   }
   
   return output;
}



//////////////////////////////
//
// displayVariables -- display the baton's control variables in
//    real-time on the computer monitor.
//

void displayVariables(void) {
   // move cursor to the beginning of the line
   cout << "\r";
   // erase previous line
   cout << 
"                                                                          ";
   cout << "\r";

   // print out each axis variable:
   cout << "den:";
   cout.width(6);
   cout << (int)(MAXVOICES*density);
   cout << "   reg:";
   cout.width(6);
   cout << (int)(3*pregister);
   cout << "   con:";
   cout.width(6);
   cout << cintervals[(int)(consonance*12)];
   cout << "   vel:";
   cout.width(6);
   cout << ((int)(velocity*100))/100.0;
   cout << "   art:";
   cout.width(6);
   cout << ((int)(articulation*100))/100.0;
   cout << "   rhy:";
   cout.width(6);
   cout << ((int)(rhythmType*100))/100.0;
   cout << flush;
}




//////////////////////////////
//
// generateVoices -- 
//

void generateVoices(void) {
   int voiceson = 0;
   int i;

   // count how many voices are on
   for (i=0; i<MAXVOICES; i++) {
      if (voiceState[i]) {
         voiceson++;
      }
   }

   // determine how many voices should be on.
   int voicesnow = (int)(MAXVOICES * density);

   if (voicesnow == voiceson) {
      return;
   }

   if (voicesnow < voiceson) {
      // need to erase voices
 
      int eraseindex; 
      for (i=0; i<voiceson-voicesnow; i++) {
         eraseindex = rand()%MAXVOICES;
         if (voiceState[eraseindex] == 1) {
            voiceState[eraseindex] = 0;
            voice[eraseindex].off();
         } else {
            int errorprotect = 0;
            while (voiceState[eraseindex] == 0 && errorprotect < MAXVOICES*2) {
               errorprotect++;
               eraseindex = (eraseindex + 1) % MAXVOICES;
               if (voiceState[eraseindex] == 1) {
                  voiceState[eraseindex] = 0;
                  voice[eraseindex].off();
               } 
            }
         }
      }

   } else {
      // need to add voices

      int totalnotes = 0;
      for (i=0; i<12; i++) {
         totalnotes += performerPCHistory[i];
      }
      if (totalnotes == 0) {
         return;
      }
         
      double noteDist[12];
      for (i=0; i<12; i++) {
         noteDist[i] = performerPCHistory[i]/(double)totalnotes;
      }

      double randomval;
      int j;
      for (i=0; i< voicesnow-voiceson; i++) {
         randomval = drand48();          // random number between 0.0 and 1.0
         double sum = 0.0;

         // choose a pitch class from the piano performer
         // you are more likely to choose a pitch which the pianist
         // has play alot.
         for (j=0; j<12; j++) {
            sum += noteDist[j]; 
            if (sum > randomval) {
               break;
            }
         }
         int choosenpc = j;

         // now that a pitch class from the pianist has been choosen
         // choose the output pitch class from the radio drum's 
         // consonance dissonance control
         int outputpc = (choosenpc + cintervals[(int)(consonance*12)]) % 12;

         // now that we have the output pitch class, assign a register 
         // to it.

         // choose a register, then look to find if there is a free register
         // associated with the chosen register
         int outputregister = (int)(pregister * 3);
         if (performerRegion[outputregister] != 0) {
            if (pregister < 0.5) {
               outputregister = (outputregister+1) % 3;
            } else {
               outputregister = (outputregister-1+30) % 3;
            }
         } 

         if (performerRegion[outputregister] != 0) {
            if (pregister < 0.5) {
               outputregister = (outputregister+1) % 3;
            } else {
               outputregister = (outputregister-1+30) % 3;
            }
         }

         if (performerRegion[outputregister] != 0) {
            if (pregister < 0.5) {
               outputregister = (outputregister+1) % 3;
            } else {
               outputregister = (outputregister-1+30) % 3;
            }
         }

         if (performerRegion[outputregister] != 0) {
            continue;
         }
            
         int octave = rand()%2 + outputregister * 2 + 3;

         int outputpitch = outputpc + octave * 12;
         int initialvolume = (int)(avgVol - VARIATION * avgVolRange + 
               VARIATION * 2 * avgVolRange * velocity);
         if (initialvolume < MINVEL) {
            initialvolume = MINVEL;
         } else if (initialvolume > MAXVEL) {
            initialvolume = MAXVEL;
         }
//         int oppositevol = 127 - initialvolume;
//         int outputvolume = (int)((oppositevol - initialvolume) * velocity + 
//               initialvolume);
int outputvolume = initialvolume;
         if (outputvolume < MINVEL) {
            outputvolume = MINVEL;
         } else if (outputvolume > MAXVEL) {
            outputvolume = MAXVEL;
         }
         
         // we now know the velocity and pitch of the output note.
         // now we need to calculate the duration and the voice slot
         // to place the note in.

         double outduration = avgDur - VARIATION * avgDurRange + 
               VARIATION * 2 * drand48() * avgDurRange;
       
         // adjust duration of notes according to baton control
         outduration += VARIATION * (avgDurRange * (articulation * 
            2 - 1));
         outduration *=  3.0 * rhythmType;
 
         // prevent very short notes, nothing less than 50 ms in duration
         if (outduration < 50) {
            outduration = 50.0;
         }
         if (avgDur < 50) {
            outduration = 150.0;
         }

         int outputduration = (int)outduration;

         // choose a voice slot to store the note.
         int assignment = (int)(drand48() * MAXVOICES);
         int errorprotect = 0;
         while (voiceState[assignment] != 0 && errorprotect < MAXVOICES*2) {
            errorprotect++;
            assignment = (assignment + 1) % MAXVOICES;
         }
         if (voiceState[assignment] == 0 && activeQ) {
            voiceState[assignment] = 1;
            voiceOnTime[assignment] = t_time;
            voiceOffTime[assignment] = t_time + outputduration;
            voice[assignment].play(outputpitch, outputvolume);
            if (displayOutput) {
               cout << "Note: " <<  outputpitch << "\tvolume = " 
                    << outputvolume << "\tduration = " << outputduration 
                    << endl;
            }
         }
      }
   }
}



//////////////////////////////
//
// midiLimit -- guarentee that the given number is in the range
//     from 0 to 127.
//

int midiLimit(int aNumber) {
   if (aNumber < 0) {
      return 0;
   } else if (aNumber > 127) {
      return 127;
   } else {
      return aNumber;
   }
}



//////////////////////////////
//
// processBaton -- update the program variables related to the radio
//      batons.
//

void processBaton(void) {
   // update the variable for keeping track of texture density:
   // density 0 = thick, density 1 = thin
   density = baton.x1p/127.0;             // baton.x1p == xt1

   // update the variable for keeping track of articulation
   // pregister 0 = low, pregister 1 = high
   pregister = baton.y1p/127.0;            // baton.y1p == yt1

   // update the variable for keeping track of the consonance:
   // consonance 0 = consonant, consonance 1 = dissonant
   // zt1 = baton.z1p
   if (zmax - zmin == 0) {
      consonance = 0.5;
   } else {
      consonance = 1.0 - (baton.z1p - zmin) * 1.0 / (zmax - zmin);
      if (consonance < 0.0) {
         consonance = 0.0;
      }
   }


   // update the variable for keeping track of velocity
   // velocity 0 = similar, velocity 1 = inverted
   velocity = baton.x2p/127.0;        // baton.x2p == xt2

   // update the variable for keeping track of articulation
   // articulation 0 = staccato, articulation 1 = tenuto, 0.5 = normal
   articulation = baton.y2p/127.0;        // baton.y2p == yt2

   // update the variable for keeping track of rhythm type:
   // rhythmType 0 = even, rhythmType 1 = random
   // zt2 = baton.z2p
   if (zmax - zmin == 0) {
      rhythmType = 0.5;
   } else {
      rhythmType = 1.0 - (baton.z2p - zmin) * 1.0 / (zmax - zmin);
   }
   if (rhythmType < 0.0) {
      rhythmType = 0.0;
   }
}



//////////////////////////////
//
// processKeyboard -- get notes from the piano and store them as needed
//    the the radio baton monitoring variables.
//

void processKeyboard(void) {
   MidiMessage message;
   int key;
   int vel;
   int command;
   
   while (synth.getNoteCount() > 0) {
      message = synth.extractNote();
      command = message.p0();
      key = message.p1();
      vel = message.p2();
 
      if (vel == 0 || (command & 0xf0 == 0x80)) {
         // note-off command section
         long duration = t_time - performerNotes[key];
         durations.insert(duration);
         durtimes.insert(t_time);

         performerNotes[key] = 0;
         performerPC[key%12]--;
         if (performerPC[key%12] < 0) {
            performerPC[key%12] = 0;
         }
      
      } else {   // note-on command
         performerNotes[key] = t_time;
         performerPC[key%12]++;
         performerPCHistory[key%12]++;
         keys.insert(key);
         keytimes.insert(t_time);
         volumes.insert(vel);
         voltimes.insert(t_time);

      } // end of the note-on command section

   }  // end of the while loop for processing notes from the performer

   // update the time that the performer last play a note on/off:
   lastPerformerTime = t_time;

   updatePerformRegions();
   updateDuration();
   updateVolume();
}



//////////////////////////////
//
// updateDuration -- recalculate the average duration and its possible
//       range.  Calculate average duration and the standard deviation 
//       of durations.
//

void updatePerformRegions(void) {
   // determine which region of the piano the performer is
   // playing on:
   performerRegion[0] = 0;
   performerRegion[1] = 0;
   performerRegion[2] = 0;
   for (int i=0; i<127; i++) {
      if (performerNotes[i] > 0) {
         if (i < BOUND1) {
            performerRegion[0] = 1;
         } else if (i < BOUND2) {
            performerRegion[1] = 1;
         } else {
            performerRegion[2] = 1;
         }
      }
   }

}



//////////////////////////////
//
// updateDuration -- recalculate the average duration and its possible
//       range.  Calculate average duration and the standard deviation 
//       of durations.
//

void updateDuration(void) {
   int i;
   double dursum = 0.0;
   for (i=0; i<durations.getCount(); i++) {
      dursum += durations[i];
   }
   if (durations.getCount() != 0) {
      avgDur = dursum / durations.getCount();
   } else {
      avgDur = 0.0;
   }
   // get the variance
   dursum = 0.0;
   for (i=0; i<durations.getCount(); i++) {
      dursum += (avgDur - durations[i]) * (avgDur - durations[i]);
   }
   avgDurRange = sqrt(dursum);
}



//////////////////////////////
//
// updateVolume -- recalculate the average volume and it possible
//       range.  Calculate average volume and the standard deviation 
//       of volumes
//
   
void updateVolume(void) {
   int i;
   double volsum = 0.0;
   for (i=0; i<volumes.getCount(); i++) {
      volsum += volumes[i];
   }
   if (volumes.getCount() != 0) {
      avgVol = volsum / volumes.getCount();
   } else {
      avgVol = 0.0;
   }
   // get the variance
   volsum = 0.0;
   for (i=0; i<volumes.getCount(); i++) {
      volsum += (avgVol - volumes[i]) * (avgVol - volumes[i]);
   }
   avgVolRange = sqrt(volsum);
}
        


            
   

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




// md5sum: c8bbb162e65d0138d1619ab7891fc58a seny.cpp [20050403]