//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Apr 22 17:12:00 PDT 2000
// Last Modified: Mon Apr 24 14:31:56 PDT 2000
// Filename:      ...sig/doc/examples/all/trill/trill.cpp
// Syntax:        C++; synthImprov 2.0
//  
// Description:   a trill is started when two notes occur within
//		  1 second of each other.  The trill is stopped
//		  when a note is played inside or on the boundary
//		  of the trill (for minor 2nd, within one note outside
//                the range of the trill.
//         
//                There are several control keys on the keyboard.
//                If a control key is hit harder, then its effect
//                will occur at a faster rate.
//                   A0  = turn off all current trills
//                   A7  = make all trills quieter
//                   A#7 = make all trills louder
//                   B7  = make all trills faster
//                   C8  = make all trills slower
//

#include "synthImprov.h" 

#define TRIGTIME     1000     /* maximum time between trill notes */
#define MINTRIGTIME    75     /* minimum time between trill notes */

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

EventBuffer eventBuffer;            // used to store algorithms for performance
CircularBuffer<int> notetimes(100); // history of the last 100 note-on times
Array<int> noteontimes(128);        // list of the last time a note was played
CircularBuffer<int> notes(100);     // history of the pitches played
CircularBuffer<int> trills(100);    // history of which notes create a trill
int trillcorrection = 0;            // global speed control of trills
int velcorrection = 0;              // golbal velocity control of trills

// function declarations:
void sillyKeyboard(int key, int chan = 0);
void createTrill(int key1, int key2, int velocity, int channel, int duration);
void processNote(MidiMessage message);



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


//////////////////////////////
//
// TrillAlgorithm -- Play the next note in the trill, adjusting
//    for the global control of 
//
// Global variables needed by this function:
//      notetimes : The time that keys are pressed on the keyboard
//      trillcorrection : adjustment to the trill speed
//      velcorrection : adjustment to the attack velocity 
//
// Local variables:
//    charValue(14) = upper key number of trill
//    charValue(15) = current note to play
//    intValue(10)  = start time of trill
//

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

   int key1 = p.getKey();           // lower key of trill
   int key2 = p.charValue(14);      // upper key of trill
   int state = p.charValue(15);     // which note to play next
   int starttime = p.intValue(10);  // when trill was started
   int i;
  
   // turn off the trill if there is a note played inside the trill
   int range1 = key1;
   int range2 = key2;
   if (range2 - range1 == 1) {
      range1--;
      range2++;
   }
   for (i=range1; i<=range2; i++) {
      if (noteontimes[i] > starttime) {
         p.off(midiOutput);
         return;
      }
   }

   // set the next note to play
   int key = state ? key2 : key1;
   state = !state;
   p.charValue(15) = state;

   // set the parameters for the output note:
   note.setOnDur(t_time, p.getDur()); 
   note.setVel(p.getVel());
   note.setChan(p.getChan());
   note.setKey(key);

   // update the parameters for the gliss function:
   p.setOnTime(p.getOnTime() + p.getDur());  
   
   int value = p.getVel() + velcorrection;
   if (value < 100 && value > 3) {
      p.setVel(value);
   }
   if (p.getDur() + trillcorrection > MINTRIGTIME) {
      p.setDur(p.getDur() + trillcorrection);
   }

   note.activate();
   note.action(midiOutput);       // start right now, avoiding any buffer delay
   midiOutput.insert(note);       // the note off message is being buffered
}



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

void description(void) {
   printboxtop();
   psl(
   "   TRILL -- by Craig Stuart Sapp <craig@ccrma.stanford.edu> -- 22 April 2000");
   psl("");
   printboxbottom();
} 


void initialization(void) { 
   eventBuffer.setPollPeriod(10);
   notetimes.reset();
   notetimes.insert(0);
   notetimes.insert(0);
   notes.reset();
   notes.insert(0);
   notes.insert(0);
   noteontimes.zero();
}


void finishup(void) { }


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


void mainloopalgorithms(void) { 
   eventBuffer.checkPoll();             // see if any notes need playing

   while (synth.getNoteCount() > 0) {
      processNote(synth.extractNote());
   }
}
      


////////////////////////////
//
// processNote -- 
//

void processNote(MidiMessage message) {
   int key = message.p1();
   int velocity = message.p2();
   int channel = message.p0() & 0x0f;

   int status = 1;
   if (message.p0() - channel == 0x80 || velocity == 0) {
      status = 0;
   }

   if (status == 0) {
      if (key == C8 || key == B7) {
         trillcorrection = 0;
      }
      if (key == As7 || key == A7) {
         velcorrection = 0;
      }
      return;
   }

   if (key == C8) {
      trillcorrection = velocity / 10;
      return;
   }

   if (key == B7) {
      trillcorrection = -(velocity / 10);
      return;
   }

   if (key == As7) {
      velcorrection = +(velocity / 10);
      return;
   }

   if (key == A7) {
      velcorrection = -(velocity / 10);
      return;
   }

   if (key == A0) {
      for (int j=0; j<128; j++) {
         noteontimes[j] = t_time;
      }
      return;
   }
  
   if (key == A0) {
      for (int j=0; j<128; j++) {
         noteontimes[j] = t_time;
      }
      return;
   }

   noteontimes[key] = t_time;

   notetimes.insert(message.time);
   notes.insert(key);

   if (notes[1] == 0) {
      trills.insert(0);
      return;
   }
  
   if (notes[2] != 0) {
      if (trills[0] == 1 && notetimes[1] - notetimes[2] < TRIGTIME) {
         trills.insert(0);
         return;
      }
   }

   trills.insert(1);

   int duration = notetimes[0] - notetimes[1];
   if (duration < TRIGTIME && duration > MINTRIGTIME &&
       notes[0] - notes[1] != 0) {
      createTrill(key, notes[1], velocity, channel, duration);
   }
}


////////////////////////////
//
// createTrill -- set up the parameters for a trill
//

void createTrill(int key1, int key2, int velocity, int channel, int duration) {
   static FunctionEvent tn;   // a Temporary Note for copying into eventBuffer

   // key1 should always be smaller than key2
   int temp;
   if (key1 > key2) {
      temp = key1;
      key1 = key2;
      key2 = temp;
   }

   // setting the fields of the function note
   tn.setFunction(TrillAlgorithm);
   tn.setChannel(channel);
   tn.setKeyno(key1);
   tn.setVelocity(velocity);

   // set extra parameters
   tn.charValue(15) = 0;        // 0 = play key1 next, 1 = play key2 next
   tn.charValue(14) = key2;     // secondary pitch
   tn.intValue(10) = t_time;    // initialization time

   tn.setStatus(EVENT_STATUS_ACTIVE);

   // start time of function and the duration between calling it
   tn.setOnDur(t_time + duration, duration);

   eventBuffer.insert(tn);

   cout << "Trill = " << key1 << " to " << key2 
        << "\tRate = " << duration
        << endl;
}



/*-------------------- 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;
      default:
         sillyKeyboard(key);
   }
}



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
   }

   // prevent any invalid key numbers:
   if (newkey < 0) {
      newkey = 0;
   } else if (newkey > 127) {
      newkey = 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: 967878f0601a855737b12812bbc08313 trill.cpp [20050403]