//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Feb  8 11:56:42 GMT-0800 1998
// Last Modified: Sun Feb  8 14:59:09 GMT-0800 1998
// Filename:      ...sig/doc/examples/improv/synthImprov/arpeg/arpeg.cpp
// Syntax:        C++; synthImprov 0.5
//
// Description:  You press a key on the keyboard, and a chord will
//      be played according to the selected quality, onsets, and durations.
//
//      Chord Qualities are selected from the computer keyboard from these keys:
//              "q" = diminished triad
//              "w" = minor triad
//              "e" = major triad
//              "r" = augmented triad
//              "t" = fully-diminished seventh chord
//              "y" = half-diminished seventh chord
//              "u" = minor-minor seventh chord
//              "i" = minor-major seventh chord
//              "o" = major-minor (dominant) seventh chord
//              "p" = major-major seventh chord
//

#include "synthImprov.h"


#define  DIMINISHED_TRIAD (1)
#define  MINOR_TRIAD      (2)
#define  MAJOR_TRIAD      (3)
#define  AUGMENTED_TRIAD  (4)
#define  FULLY_DIM_7TH    (5)
#define  HALF_DIM_7TH     (6)
#define  mm_7TH           (7)
#define  mM_7TH           (8)
#define  Mm_7TH           (9)
#define  MM_7TH           (10)


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

int octave = 4;                 // octave range for computer keyboard notes
int keyboardnote = 0;           // computer keyboard note
MidiMessage noteMessage;        // for reading keyno and velocity (and time)
EventBuffer eventBuffer(2000);  // for future notes
int chordType = MAJOR_TRIAD;    // the type of chord to play when key pressed
int onset[4] = {0};             // the rhythm of the chord
int duration[4] = {0};          // the duration of the chord notes
int attack[4] = {0};            // attack velocity for chords
int offset = 0;                 // for delays (of Disklavier)


void description(void) {
   printboxtop();
   printstringline(
"   Arpeg -- by Craig Stuart Sapp <craig@ccrma.stanford.edu> -- 8 Feb 1998");
   printstringline("");
   printstringline(
"  Description:  You press a key on the keyboard, and a chord will");
   printstringline(
"    be played according to the selected quality, onsets, and durations.");
   printstringline(
"    Computer keyboard keys are assigned random attack velocities.");
   printintermediateline();
   printstringline("  User commands:");
   printstringline(
"            \"\\\" = record rhythms, durations     \"=\" = display rhythms");
   printstringline(
"            \"[\" = lower timing offset           \"]\" = raise timing offset");
   printstringline(
"    Chords: \"q\"=dim       \"w\"=minor   \"e\"=major   \"r\"=aug   \"t\"=full dim");
   printstringline(
"            \"y\"=half dim  \"u\"=mm7     \"i\"=mM7     \"o\"=Mm7   \"p\"=MM");
   printstringline(" ");
   printstringline(
"      \"0\"-\"9\" = octave number of computer keyboard notes");
   printstringline(
"      Notes:           s   d      g    h   j   ");
   printstringline(
"                     z   x   c   v   b   n   m  ");
   printboxbottom();
}



void initialization(void) {
   eventBuffer.setPollPeriod(10);

   onset[0] = 0;
   onset[1] = 100;
   onset[2] = 200;
   onset[3] = 300;

   duration[0] = 600;
   duration[1] = 500;
   duration[2] = 400;
   duration[3] = 300;
}



void finishup(void) { }


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


void playchord(MidiMessage aMessage, int chordQuality, 
      int* rhythm, int* dur) {
   int numNotes = 0;             // the number of notes to play
   NoteEvent tempNote;           // temporary Note for copying into eventBuffer
   int chordNote[4];             // the notes of the chord to be calculated
   int rootNote = aMessage.p1(); // root of chord to be created

   chordNote[0] = rootNote;
   switch (chordQuality) {
      case DIMINISHED_TRIAD:
         chordNote[1] = rootNote + 3;  chordNote[2] = rootNote + 6;
         numNotes = 3;
         break;
      case MINOR_TRIAD:
         chordNote[1] = rootNote + 3;  chordNote[2] = rootNote + 7;
         numNotes = 3;
         break;
      case MAJOR_TRIAD:
         chordNote[1] = rootNote + 4;  chordNote[2] = rootNote + 7;
         numNotes = 3;
         break;
      case AUGMENTED_TRIAD:
         chordNote[1] = rootNote + 4;  chordNote[2] = rootNote + 8;
         numNotes = 3;
         break;
      case FULLY_DIM_7TH:
         chordNote[1] = rootNote + 3;
         chordNote[2] = rootNote + 6;
         chordNote[3] = rootNote + 9;
         numNotes = 4;
         break;
      case HALF_DIM_7TH:
         chordNote[1] = rootNote + 3;
         chordNote[2] = rootNote + 6;
         chordNote[3] = rootNote + 10;        
         numNotes = 4;
         break;
      case mm_7TH:
         chordNote[1] = rootNote + 3;
         chordNote[2] = rootNote + 7;
         chordNote[3] = rootNote + 10;        
         numNotes = 4;
         break;
      case mM_7TH:
         chordNote[1] = rootNote + 3;
         chordNote[2] = rootNote + 7;
         chordNote[3] = rootNote + 11;        
         numNotes = 4;
         break;
      case Mm_7TH:
         chordNote[1] = rootNote + 3;
         chordNote[2] = rootNote + 4;
         chordNote[3] = rootNote + 10;        
         numNotes = 4;
         break;
      case MM_7TH:
         chordNote[1] = rootNote + 4;
         chordNote[2] = rootNote + 7;
         chordNote[3] = rootNote + 10;        
         numNotes = 4;
         break;
      default:                                    // invalid quality
         return;
   }

   cout << "Chord: (";
   for (int i=0; i<numNotes; i++) {
      tempNote.setKeyno(chordNote[i]);
      if (tempNote.getKeyno() < 0 || tempNote.getKeyno() > 127)  continue;

      if (attack[i] == 0) {
         tempNote.setVelocity(aMessage.p2());
      } else {
         tempNote.setVelocity(attack[i]);
      }

      tempNote.setOnDur(t_time+rhythm[i]+offset, dur[i]);
      tempNote.setStatus(0);                   // note hasn't been played yet
      eventBuffer.insert(&tempNote);

      cout << tempNote.getKeyno();
      if (i != numNotes-1)  cout << ",";
   }
   cout << ")" << endl;
}



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

   while (synth.getNoteCount() > 0) {
      noteMessage = synth.extractNote();
      if (noteMessage.p2() != 0) {
         playchord(noteMessage, chordType, onset, duration);
      }
   }
}



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

void recordRhythms(void) {
   // remove all previous notes from input
   while (synth.getNoteCount() > 0) synth.extractNote();

   cout << "Play four notes to record rhythms and durations for chords" << endl;
   int oncount = 0;
   int offcount = 0;
   int finished[4] = {0};
   int offnote;
   int offtime;
   int playedNotes[4] = {0};
   int startTime = 0;
   MidiMessage noteMessage;
   while (oncount <= 4 && offcount <=4 ) {
      if (interfaceKeyboard.hit())  checkKeyboard();
      synth.processIncomingMessages();
      if (synth.getNoteCount() > 0) {
         noteMessage = synth.extractNote();
         if (oncount < 4 && noteMessage.p2() != 0) {
            if (oncount == 0)  startTime = noteMessage.time;
            playedNotes[oncount] = noteMessage.p1();
            attack[oncount] = noteMessage.p2();
            onset[oncount] = noteMessage.time - startTime;
            oncount++;
         } else if (noteMessage.p2() == 0) {
            offtime = noteMessage.time;
            offnote = noteMessage.p1();
            for (int i=0; i<oncount; i++) {
               if ((offnote == playedNotes[i]) && (finished[i] == 0)) {
                  duration[i] = offtime - onset[i] - startTime;
                  offcount++;
                  finished[i] = 1;
                  break;
               }
            }
         }
      }
      if ((oncount >= 4) && (offcount >=4))  break;
   }

   cout << "Finished recording new rhythms and durations" << endl;

   cout << "Onsets:    \t" << onset[0] << "\t" << onset[1]
        << "\t" << onset[2] << "\t" << onset[3] << endl;
   cout << "durations: \t" << duration[0] << "\t" << duration[1]
        << "\t" << duration[2] << "\t" << duration[3] << endl;

}



void keyboard(int key) {
   synth.play(0, keyboardnote, 0);
   noteMessage.time = mainTimer.getTime();
   noteMessage.command() = 0x90;
   noteMessage.p1() = keyboardnote;
   noteMessage.p2() = 0;
   synth.insert(noteMessage);
   switch (key) {
      case 'z': keyboardnote = 12 * octave + 0;  break;    // C
      case 's': keyboardnote = 12 * octave + 1;  break;    // C#
      case 'x': keyboardnote = 12 * octave + 2;  break;    // D
      case 'd': keyboardnote = 12 * octave + 3;  break;    // D#
      case 'c': keyboardnote = 12 * octave + 4;  break;    // E
      case 'v': keyboardnote = 12 * octave + 5;  break;    // F
      case 'g': keyboardnote = 12 * octave + 6;  break;    // F#
      case 'b': keyboardnote = 12 * octave + 7;  break;    // G
      case 'h': keyboardnote = 12 * octave + 8;  break;    // G#
      case 'n': keyboardnote = 12 * octave + 9;  break;    // A
      case 'j': keyboardnote = 12 * octave + 10;  break;   // A#
      case 'm': keyboardnote = 12 * octave + 11;  break;   // B
      case ',': keyboardnote = 12 * octave + 12;  break;   // C
      default: return;
   }
   if (keyboardnote < 0)  keyboardnote = 0;
   else if (keyboardnote > 127)  keyboardnote = 127;
   noteMessage.time = mainTimer.getTime();
   noteMessage.command() = 0x90;
   noteMessage.p1() = keyboardnote;
   noteMessage.p2() = rand()%47 + 80;      // random int from 1 to 127
   synth.play(0, noteMessage.p1(), noteMessage.p2());
   synth.insert(noteMessage);
}



void keyboardchar(int key) { 
   if (isdigit((char)key)) {
      octave = key - '0';   
      return;
   }
   switch (key) {
      case 'q':
         chordType = DIMINISHED_TRIAD;
         break;
      case 'w':
         chordType = MINOR_TRIAD;
         break;
      case 'e':
         chordType = MAJOR_TRIAD;
         break;
      case 'r':
         chordType = AUGMENTED_TRIAD;
         break;
      case 't':
         chordType = FULLY_DIM_7TH;
         break;
      case 'y':
         chordType = HALF_DIM_7TH;
         break;
      case 'u':
         chordType = mm_7TH;
         break;
      case 'i':
         chordType = mM_7TH;
         break;
      case 'o':
         chordType = Mm_7TH;
         break;
      case 'p':
         chordType = MM_7TH;
         break;
      case '[':
         offset--;
         break;
      case ']':
         offset++; 
         break;
      case '\\':
         recordRhythms();
         break;
      case '=':
         cout << "Onsets:    \t" << onset[0] << "\t" << onset[1]
              << "\t" << onset[2] << "\t" << onset[3] << endl;
         cout << "durations: \t" << duration[0] << "\t" << duration[1]
              << "\t" << duration[2] << "\t" << duration[3] << endl;
         cout << "Offset: " << offset << endl;
         break;
      default:
         keyboard(key);
   }
}


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



// md5sum: 811c54faae4ee5940c11c0a7dc1f5edb arpeg.cpp [20050403]