//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat May 27 20:16:58 PDT 2000
// Last Modified: Sat May 27 20:44:30 PDT 2000
// Filename:      ...sig/doc/examples/all/eco/eco.cpp
// Syntax:        C++; synthImprov 2.0
//  
// Description:   Generates echos
//                Echos are triggered whenever two notes of the same
//                pitch are played within 10 seconds of each other.
//
//                The duration of the echoed notes are related to the
//                duration of the first note that starts the echo
//
//                The decay amount of the echo is related to the
//                change in attack velocity between the two notes
//                which start the echo.
//

#include "synthImprov.h" 

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

#define MAXECHOTIME 10000   /* max time for activating echos */

EventBuffer eventBuffer;  // for future notes 
MidiMessage message;      // for reading keyno and velocity (and time)
int channel;              // channel to play the echos on
int key;                  // the current MIDI key number
int duration;             // duration of the echoing note
int period;               // time between echoing notes
int command;              // the current MIDI command
int velocity;             // the current MIDI attack velocity
int decay;                // decay rate for the echo algorithm

Array<int> ontimes(128);  // time when the last note turned on for each pitch
Array<int> offtimes(128); // time with the last note turned off for each pitch
Array<int> attack(128);   // attack velocity of the first note in the echo


// function declarations:
void sillyKeyboard(int key, int chan = 0);
void playecho(int key, int velocity, int decay, int channel, int duration, 
      int period);

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


//////////////////////////////
//
// EchoFunction -- 
//
// Global variables needed by this function:
//     None.
//
// Local variables needed by this function:
//     12 int   = duration
//     8 float  = decay
//     4 float  = newvelocity
//

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

   // set the parameters for the output note:
   note.setOnDur(t_time, p.intValue(12)); // duration in int(12)
   note.setVel(p.getVel());
   note.setChan(p.getChan());
   note.setKey(p.getKey());

   // update the parameters for the function:
   p.floatValue(4) = p.floatValue(4) * p.floatValue(8);
   short newvelocity = (short)p.floatValue(4);
   if (newvelocity <= 7) {
      p.off(midiOutput);
   } else {
      p.setVel(newvelocity);
      p.setOnTime(p.getOnTime() + p.getDur());  // OffTime stores duration
   
      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(
   "eco -- by Craig Stuart Sapp <craig@ccrma.stanford.edu> -- 28 May 2000");
   psl("");
   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);

   ontimes.zero();
   offtimes.zero();
   attack.zero();
}


void finishup(void) { }


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


void mainloopalgorithms(void) { 
   if (eventBuffer.checkPoll());        // see if any notes to play

   while (synth.getNoteCount() > 0) {
      message  = synth.extractNote();
      channel  = 0x0f & message.p0();
      command  = 0xf0 & message.p0();
      key      = message.p1();
      velocity = message.p2();

      if (command == 0x80 || velocity == 0) {
         // note off message
         offtimes[key] = t_time;
      } else {
         if (key == A0) {
            eventBuffer.off();
            return;
         }


         // note on message
         duration = offtimes[key] - ontimes[key];
         period = t_time - ontimes[key];
         decay = attack[key] - velocity;
         if (decay < 0) {
            decay = -decay;
            velocity = attack[key];
         }
         if (decay < 1) {
            decay = 1;
         }
         
         if (period < MAXECHOTIME && ontimes[key] != 0) {
            playecho(key, velocity, decay, channel, duration, period);
            ontimes[key] = 0;
         } else {
            ontimes[key] = t_time;
            attack[key] = message.p2(); 
         }
      }
   }
}



////////////////////////////
//
// playecho -- inserts a FunctionEvent into the eventBuffer which
//     plays an echo. 
//

void playecho(int key, int velocity, int decay, int channel, int duration, 
      int period) { 
   static FunctionEvent tn;   // a Temporary Note for copying into eventBuffer
   
   // setting the fields of the function note
   tn.intValue(12) = duration;
   tn.floatValue(8) = 1.0 - decay/128.0;
   tn.floatValue(4) = (float)velocity;

   tn.setFunction(EchoFunction);
   tn.setChannel(channel);
   tn.setKeyno(key);
   tn.setVelocity(velocity);
 
   tn.setStatus(EVENT_STATUS_ACTIVE);

   // start time of function and the duration between notes
   tn.setOnDur(t_time, period);

   eventBuffer.insert(tn);

   cout << "Key = "        << key
        << "\tLoudness = " << velocity
        << "\tdecay = "    << decay
        << "\tduration = " << duration
        << "\tperiod = "   << period
        << endl;
}



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

void keyboardchar(int key) { 
   switch (key) {
      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: bf0d0a22200a94eb60ed265119a3094f eco.cpp [20050403]