//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Oct 15 14:46:35 PDT 2001
// Last Modified: Wed Oct 17 20:30:37 PDT 2001
// Filename:      ...sig/doc/examples/improv/synthImprov/tumble/tumble.cpp
// Syntax:        C++; synthImprov 2.0
//  
// Description:   Melodic sequences with a uniform rhythm are played
//                to input a melodic pattern which is then repeated
//                until the algorith notes fall outside the range of
//                an 88-note keyboard.
//

#include "synthImprov.h"       /* include synthImprov environment */
#define  PARAMSIZE 256         /* number of max simultaneous algorithms */


// Tumble Parameters is a data structure for holding all of the data
// for a tumble algorithm.  It stores all of the necessary information
// for generating the tumble algorithm's notes.

class TumbleParameters {
   public:
      int max;                 // maximum number of notes in sequence
      int pos;                 // current position to play next note.
      int current;             // current note being played
      Array<char> n;           // note sequence; n0 = cycle delta
      Array<char> v;           // attack velocity of note n.
      Array<unsigned short> i; // duration between note attacks (ioi)
      Array<unsigned short> d; // duration of note n
      int active;              // 0=unused, 1=currently running algorithm
      int dir;                 // 1=normal, -1=inversion

      TumbleParameters(void) {
         clear();
      }

      TumbleParameters(TumbleParameters& b) {
         TumbleParameters& a = *this;
         a = b;      
      }

      void clear(void) {
         n.setSize(0);
         v.setSize(0);
         i.setSize(0);
         d.setSize(0);
         max = 0;
         pos = 0;
         active = 0;
         current = 0;
         dir = 1;
      }

      TumbleParameters& operator=(TumbleParameters& b) {
         TumbleParameters& a = *this;
         a.n       = b.n;
         a.v       = b.v;
         a.i       = b.i;
         a.d       = b.d;
         a.max     = b.max;
         a.pos     = b.pos;
         a.active  = b.active;
         a.current = b.current;
         a.dir     = b.dir;
         return a;
      }
         
};


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

EventBuffer eventBuffer;  // for future notes (2048 notes max)
int channel = 0;          // channel to play the notes on.
MidiMessage message;      // for reading keyno and velocity (and time)
int direction = 1;        // direction of algorithm generation notes
int length = 4;           // number of notes in a algorithm cycle
int anticipation = 125;   // anticipation of first note in tumble for
                          // use with the Yamaha Disklavier which has
                          // a slight delay in playing notes from the computer.
                          // Value is in milliseconds.
double tolerance = 0.90;  // allowable trigger rhythm tolerance. *// 0.90
SigCollection<TumbleParameters> tparam;  // data storage for tumble functions


// function declarations:
void    processNote(MidiMessage message, int seqLength, int direction);
int     startAlgorithm(TumbleParameters& p);
int     storeParameters(SigCollection<TumbleParameters>& params,
                             TumbleParameters& p);
void    randomizeDirections(SigCollection<TumbleParameters>& p);
void    reverseDirections(SigCollection<TumbleParameters>& p);
void    forwardDirections(SigCollection<TumbleParameters>& p);
void    invertDirections(SigCollection<TumbleParameters>& p);
void    sillyKeyboard(int key, int chan = 0);

template<class type>
type    limit(type value, type min, type max);

/*--------------------- Event Algorithms --------------------------------*/



//////////////////////////////
//
// TumbleNoteFunction -- creates the tumble note sequence.  To be
//     used with the FunctionEvent class; NoteEvents are generated
//     one at a time in the EventBuffer from a FunctionEvent.
//

static void TumbleNoteFunction(FunctionEvent& p, EventBuffer& midiOutput) {
   static NoteEvent note;           // temporary note for placing in buffer

   TumbleParameters& param = tparam[p.charValue(0)];
   int newnote = limit(param.current + param.dir * param.n[param.pos], 0, 127);

   // turn off algorithm if someone turned the algorithm off externally
   // or if the current note is too large or too small.
   if (param.active == 0 || newnote < A0 || newnote > C7) {
      param.active = 0;
      p.off(midiOutput);
      return;
   }

   // set the parameters of the output note:
   note.setOnDur(t_time, param.d[param.pos]);   // off time holds dur
   note.setVel(param.v[param.pos]);
   note.setChan(p.getChan());
   note.setKey(newnote);
   note.activate();
   note.action(midiOutput);     // start right now, avoiding any buffer delay
   midiOutput.insert(note);     // store the note for turning off later

   // update the parameters for the tumble algorithm
   p.setOnTime(p.getOnTime() + param.i[param.pos]);
   param.current = newnote;
   param.pos++;
   if (param.pos > param.n.getSize()) {
      param.pos = 0;
   }
}


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

void description(void) {
   printboxtop();
   psl(
   "   TUMBLE -- by Craig Stuart Sapp <craig@ccrma.stanford.edu> 16 Oct 2001");
   psl("");
   psl("  Description: continues a melodic sequence of a given length.");
   psl("    Computer keyboard keys are assigned random attack velocities.");
   printintermediateline();
   psl("  User commands:");
   psl(
   "     \"-\" = decrease seq.   \"=\" = increase seq.   \"\\\" = change "
                                                                  "direction");
   psl("      \"0\"-\"9\" = octave number of computer keyboard notes");
   psl("      Notes:           s   d      g    h   j   ");
   psl("                     z   x   c   v   b   n   m  ");
   printboxbottom();
} 


void initialization(void) { 
   eventBuffer.setPollPeriod(10);  // look in the algorithm buffer every 10 ms.
   tparam.setSize(PARAMSIZE);      // 256 simultaneous algorithms at once.
   tparam.allowGrowth(0);
}


void finishup(void) { 
   for (int i=0; i<tparam.getSize(); i++) {
      tparam[i].active = 0;
   }
}


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

void mainloopalgorithms(void) {
   eventBuffer.checkPoll();

   while (synth.getNoteCount() > 0) {
      message = synth.extractNote();
      if (message.is_note_on() && message.p1() == A0) {
         direction = -direction;
         cout << "Direction = " << direction << endl;
      } else if (message.is_note_on() && message.p1() == C7) {
         // add one to the length of the tumble sequence
         length = limit(length+1, 2, 200);
         cout << "Sequence length = " << length << endl;
      } else if (message.is_note_on() && message.p1() == B6) {
         // subtract one from the length of the tumble sequence
         length = limit(length-1, 2, 200);
         cout << "Sequence length = " << length << endl;
      } else {
         processNote(message, length, direction);
      }
   }
}



//////////////////////////////
//
// processNote -- processes notes for algorithm and starts
//     the algorithm if it is time to do so.
//

void processNote(MidiMessage message, int seqLength, int direction) {
   static Array<char>         notes;
   static Array<char>         velocities;
   static Array<int>          durations;
   static Array<int>          iois;
   static Array<int>          ontimes;
   static CircularBuffer<int> attacktimes;
   static int                 init = 0;
   static TumbleParameters    temparam;
   char vel;

   if (!init) {
      attacktimes.setSize(256);
      attacktimes.reset();
      notes.setSize(0);
      velocities.setSize(0);
      durations.setSize(0);
      iois.setSize(0);
      ontimes.setSize(128);
      ontimes.zero();
      init = 1;
   }

   char note;
   int deltatime;
   int ioi0;
   int ioix;
   if (message.is_note_on()) {
      attacktimes.insert(message.time);

      // check to see if the ioi is in the correct range
      if (notes.getSize() == 0) {
         // no notes yet, so don't know the first ioi
      } else {
         deltatime = attacktimes[0] - attacktimes[1];
         iois.append(deltatime);
      }
      if (iois.getSize() > 1) {
         ioi0 = iois[0];
         ioix = iois[iois.getSize()-1];
         if ((ioix < ioi0 * tolerance) || (ioix > ioi0 / tolerance)) {
            goto resettrigger;
         }
      }

      // at this point the note can be added to the sequence
      if (notes.getSize() + 1 >= seqLength) {
         // time to trigger an algorithm
         if (durations.getSize() < notes.getSize()) {
            // if the last note has not yet been turned off, approximate dur.
            deltatime = iois[iois.getSize()-1];
            durations.append(deltatime);
         }

         int i;
         for (i=0; i<seqLength; i++) {
            temparam.v[i] = velocities[i];
            temparam.i[i] = iois[i];
            temparam.d[i] = durations[i];
            temparam.n[i] = notes[i] - notes[0];
         }
         temparam.n[0]    = message.p1() - notes[0];
         temparam.current = message.p1();
         temparam.pos     = 1;
         temparam.max     = seqLength;
         temparam.active  = 1;
         
         startAlgorithm(temparam);
         goto resettrigger;
      } else {
         // add the note info to the algorithm pile
         note = message.p1();
         notes.append(note);
         vel = message.p2();
         velocities.append(vel);
         attacktimes[message.p1()] = message.time;
      }
   } else if (message.is_note_off()) {
      if (notes.getSize() > 0) {
         if (notes[notes.getSize()-1] == message.p1()) {
         deltatime = message.time - ontimes[message.p1()];
         durations.append(deltatime);
      } else {
         cout << "A funny error ocurred" << endl;
      }
   }

   return;

resettrigger:
   attacktimes.setSize(0);
   notes.setSize(0);
   velocities.setSize(0);
   durations.setSize(0);
   iois.setSize(0);

   if (message.is_note_on()) {
      note = message.p1();
      notes.append(note);
      ontimes[message.p1()] = message.time;
      vel = message.p2();
      velocities.append(vel);
   }
}



//////////////////////////////
//
// startAlgorithm -- start playing the tumble algorithm.  Inserts a
//     FunctionEvent into the eventBuffer which plays the tumble
//     algorithm sequence.  The algorithm will die after the notes
//     fall off of the 88-note keyboard.
//

}
int startAlgorithm(TumbleParameters& p) {
   static FunctionEvent tn;   // a Temporary Note for copying into eventBuffer

   int ploc = storeParameters(tparam, p);
   if (ploc < 0) {
      cout << "Warning: Parameter space is full.  Not adding new algorithm"
           << endl;
      return -1;
   }

   // setting the fields of the function note
   tn.setFunction(TumbleNoteFunction);
   tn.setChannel(channel);
   tn.setKeyno(0);
   tn.setVelocity(0);
   tn.charValue(0) = (char)ploc;         // store location of the parameters
   tn.setStatus(EVENT_STATUS_ACTIVE);
   tn.setOnTime(t_time + p.i[0] - anticipation);

   // display the basic algorithm info
   cout << "Tumble: Time: " << t_time << "\tStart = " << (int)p.current
        << "\tPattern = . ";
   for (int i=1; i<p.n.getSize(); i++) {
      cout << (int)p.n[i] << " ";
   }
   cout << "(" << (int)p.n[0] << ")";
   cout << " ioi: " << p.i[0];
   cout << endl;

   return eventBuffer.insert(tn);
}



///////////////////////////////
//
// storeParameters --
//

int storeParameters(SigCollection<TumbleParameters>& params, 
      TumbleParameters& p) {
   int start = rand() % PARAMSIZE;
   int position = start + 1;
   if (position >= PARAMSIZE) {
      position = 0;
   }
   while (position != start) {
      if (params[position].active == 0) {
         params[position] = p;  // copy parameters into storage
         return position;       // and return location of data
      }
      position++;
      if (position >= PARAMSIZE) {
         position = 0;
      }
   }

   // no free space found, abort search
   return -1;
}


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

void keyboardchar(int key) { 
   switch (key) {
      case 'p':
         cout << "current list in eventBuffer: " << endl;
         eventBuffer.print();
         cout << endl;
         cout << "Event[0] status: " << eventBuffer[0].getStatus() << endl;
         break;
      case '\\':
         direction *= -1;
         if (direction == 1) {
            cout << "Up" << endl;
         } else {
            cout << "Down" << endl;
         }
         break;
      case 'r':                       // random direction to current algorithms
         randomizeDirections(tparam);
         cout << "Random directions" << endl;
         break;
      case 'f':                       // normal direction of algorithms
         forwardDirections(tparam);
         cout << "Normal directions" << endl;
         break;
      case 'i':                       // invert direction of algorithms
         invertDirections(tparam);
         cout << "Inverted directions" << endl;
         break;
      case 'c':                       // reverse direction of algorithms
         reverseDirections(tparam);
         cout << "Changed directions" << endl;
         break;
      case 's':	                      // increase rhythmic sensitivity
         tolerance = limit(tolerance * 1.02, 0.01, 0.99);
         cout << "Sensitivity = " << tolerance << endl;
         break;
      case 'x':	                      // decrease rhythmic sensitivity
         tolerance = limit(tolerance / 1.02, 0.01, 0.99);
         cout << "Sensitivity = " << tolerance << endl;
         break;
      default:
         sillyKeyboard(key);
   }
}



//////////////////////////////
//
// randomizeDirections -- change the direction of algorithms in a random
//      fashion.
//

void randomizeDirections(SigCollection& p) {
   for (int i=0; i<p.getSize(); i++) {
      if (p[i].active) {
         if (rand() % 2) {
            p[i].dir = 1;
         } else {
            p[i].dir = -1;
         }
      }
   }
}



//////////////////////////////
//
// reverseDirections -- change the direction of algorithms from their
//     current states.
//

void reverseDirections(SigCollection& p) {
   for (int i=0; i<p.getSize(); i++) {
      if (p[i].active) {
         p[i].dir = -p[i].dir;
      }
   }
}



//////////////////////////////
//
// forwardDirections -- go to the original direction of algorithms 
//

void forwardDirections(SigCollection& p) {
   for (int i=0; i<p.getSize(); i++) {
      if (p[i].active) {
         p[i].dir = 1;
      }
   }
}



//////////////////////////////
//
// invertDirections -- change the direction of algorithms from their
//     natural states.
//

void invertDirections(SigCollection& p) {
   for (int i=0; i<p.getSize(); i++) {
      if (p[i].active) {
         p[i].dir = -1;
      }
   }
}



//////////////////////////////
//
// limit -- limit the range of a variable
//

template<class type>
type limit(type value, type min, type max) {
   if (value < min) {
      return min;
   } else if (value > max) {
      return max;
   } else {
      return value;
   }
}



//////////////////////////////
//
// sillyKeyboard -- ASCII music keyboard
//

void sillyKeyboard(int key, int chan /* = 0 */) {
   static int octave = 4;
   static int newkey = 0;
   static Voice voice;
   static MidiMessage message;

   // check to see if adjusting the octave:
   if (isdigit(key)) {
      octave = key - '0';
      return;
   }

   switch (key) {
      case 'z':  newkey = 12 * octave + 0;   break;   // C
      case 's':  newkey = 12 * octave + 1;   break;   // C#
      case 'x':  newkey = 12 * octave + 2;   break;   // D
      case 'd':  newkey = 12 * octave + 3;   break;   // D#
      case 'c':  newkey = 12 * octave + 4;   break;   // E
      case 'v':  newkey = 12 * octave + 5;   break;   // F
      case 'g':  newkey = 12 * octave + 6;   break;   // F#
      case 'b':  newkey = 12 * octave + 7;   break;   // G
      case 'h':  newkey = 12 * octave + 8;   break;   // G#
      case 'n':  newkey = 12 * octave + 9;   break;   // A
      case 'j':  newkey = 12 * octave + 10;  break;   // A#
      case 'm':  newkey = 12 * octave + 11;  break;   // B
      case ',':  newkey = 12 * octave + 12;  break;   // C
      case 'l':  newkey = 12 * octave + 12;  break;   // C#
      case '.':  newkey = 12 * octave + 12;  break;   // D
      case '\'': newkey = 12 * octave + 12;  break;   // D#
      case '/':  newkey = 12 * octave + 12;  break;   // E
      default: return;         // don't do anything if not a key
   }

   newkey = limit(newkey, 0, 127);

   // put note-off message in synth's input buffer:
   message.time = t_time;
   message.p0() = 0x90 | voice.getChan();
   message.p1() = voice.getKey();
   message.p2() = 0;
   synth.insert(message);

   // turn off the last note:
   voice.off();

   // set parameters for next note-on:
   voice.setChan(chan & 0x0f);      // limit channel to range from 0 to 15
   voice.setVel(rand() % 127 +1);   // random attack in range from 1 to 127
   voice.setKey(newkey);            // use the newly selected key number

   // play the MIDI note:
   voice.play();

   // insert the played note into synth's input MIDI buffer:
   message.command() = 0x90 | voice.getChan();
   message.p1() = voice.getKey();
   message.p2() = voice.getVel();
   synth.insert(message);

}


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


// md5sum: 484b50f19ae8f2e72564170a860aaba2 tumble.cpp [20090615]