//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun May 16 12:54:28 PDT 1999
// Last Modified: Sun May 16 16:06:30 PDT 1999
// Filename:      .../improv/examples/synthImprov/keyan/keyan.cpp
// Syntax:        C++; synthImprov 2.1
//  
// Description:   analyze the key of musical input and output the determined
//                key as a low note
//
// References:    Key analysis method taken from the "key" program of Humdrum
//                The Key analysis technique is described in the book:
//                Krumhansel & Kessler
//


#include "synthImprov.h"      
#include <math.h>
#include <iomanip.h>
#include <iostream.h>


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


MidiMessage message;            // for extracting notes from the synthesizer

CircularBuffer<char> notes;     // storage for notes being played
CircularBuffer<long> times;     // storage for note times being played
double occurances[12] = {0};    // number of notes occurances
int fadeNote = 0;               // next to to go out of scope
int fadeTime = 0;               // next time to go out of scope
SigTimer metronome;             // for display period of key analysis
double analysisDuration;        // duration in seconds of analysis window
double tempo;                   // tempo of the keyanalysis
int keyoctave = 7;              // the analysis key performance octave
int displayKey2 = 0;            // display the second key possibility

double majorKey[12];            // weights for analysis of major keys
double minorKey[12];            // weights for analysis of minor keys

Voice  firstVoice;              // for primary key voice
Voice  secondVoice;             // for secondary key voice
double offFraction = 0.75;      // for turning off next keyvoice.

// function declarations:
void        analyzekey(void);
void        storeNote(char storeNote, long storeTime);
const char* name(int pitch_class);


/*--------------------- 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 << 
   "Keyan 1 -- analyze musical input for key\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) { 
   analysisDuration = 7.0;          // duration in seconds of analysis window
   tempo = 60.0;                     // tempo of the keyanalysis
   metronome.setTempo(tempo);
   firstVoice.setChannel(0);
   secondVoice.setChannel(0);
   notes.setSize(10000);
   times.setSize(10000);

   // initialize majorKey and minorKey arrays
   // The tonality coefficients listed here were determined by 
   // Krumhansl & Kessler (1982) and are defined for both 
   // the major and minor keys.
   majorKey[0]  = 6.35;       minorKey[0]  = 6.33;
   majorKey[1]  = 2.23;       minorKey[1]  = 2.68;
   majorKey[2]  = 3.48;       minorKey[2]  = 3.52;
   majorKey[3]  = 2.33;       minorKey[3]  = 5.38;
   majorKey[4]  = 4.38;       minorKey[4]  = 2.60;
   majorKey[5]  = 4.09;       minorKey[5]  = 3.53;
   majorKey[6]  = 2.52;       minorKey[6]  = 2.54;
   majorKey[7]  = 5.19;       minorKey[7]  = 4.75;
   majorKey[8]  = 2.39;       minorKey[8]  = 3.98;
   majorKey[9]  = 3.66;       minorKey[9]  = 2.69;
   majorKey[10] = 2.29;       minorKey[10] = 3.34;
   majorKey[11] = 2.88;       minorKey[11] = 3.17;
}



//////////////////////////////
//
// 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) { 
   firstVoice.off();
   secondVoice.off();
}


/*-------------------- 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) { 
   while (synth.getNoteCount() > 0) {
      message = synth.extractNote();
      if (message.p2() != 0) {
         storeNote(message.p1(), t_time);
      }
   }

   if (metronome.expired()) {
      analyzekey();
      metronome.reset();
   }

   if (metronome.getPeriodCount() > offFraction) {
      firstVoice.off();
      secondVoice.off();
   }

}
      

/*-------------------- 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) { 
   switch (key) {

      case '2':            // for playing the secondary key possibility
         displayKey2 = !displayKey2;         
         if (displayKey2) {
            cout << "Secondary key output turned ON" << endl;
         } else {
            cout << "Secondary key output turned OFF" << endl;
         }
         break;

      case ',':            // slow down the tempo which will be
         tempo /= 1.05;    // activated at the next note to be played
         if (tempo < 20) {
            tempo = 20;
         }
         metronome.setTempo(tempo);
         cout << "Tempo is: " << tempo << endl;
         break;

      case '.':            // speed up the tempo which will be
         tempo *= 1.05;    // activated at the next note to be played
         if (tempo < 240) {
            tempo = 240;
         }
         metronome.setTempo(tempo);
         cout << "Tempo is: " << tempo << endl;
         break;

   }
}


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



//////////////////////////////
//
// analyzekey -- figure out the key possibilities and then play the
//   best two choices.
//

void analyzekey(void) { 
   int i;

   // first, adjust the number of notes being analyzed
   while (times.getCount() > 0 && fadeTime < t_time - analysisDuration * 1000) {
      if (fadeTime != 0) {
         occurances[fadeNote%12]--;
      }
      fadeTime = times.extract();
      fadeNote = notes.extract();
   }
      
   // now analyze the keys
   double total = 0;
   cout << "Count:";
   for (i=0; i<12; i++) {
      cout.width(3);
      cout << occurances[i];
      total += occurances[i];
   }
   cout << flush;
   
   // Determine the mean frequency for the pitch distribution
   double mean       = total / 12.0;
   double major_mean = 3.4825;
   double minor_mean = 3.709167;

   if (total <= 0) {
      cout << endl;
      return;
   }
      
   double r_major[12];
   double r_minor[12];
   double maj_numerator;
   double min_numerator;
   double maj_denominator;
   double min_denominator;
   double maj_temp;
   double min_temp;
   double temp;
   int subscript;
   int j;
   for (i=0; i<12; i++) {
      maj_numerator = min_numerator = 0;
      maj_denominator = min_denominator = 0;
      maj_temp = min_temp = temp = 0;

      // Examine all pitches for each key.
      for (j=0; j<12; j++) {
         subscript = (i+j) % 12;

         temp += (occurances[subscript] - mean) *
                 (occurances[subscript] - mean);

         // For the major keys.
         maj_numerator += (majorKey[j]-major_mean) * 
                          (occurances[subscript]-mean);
         maj_temp += (majorKey[j] - major_mean) * 
                     (majorKey[j] - major_mean);
         
         // For the minor keys.
         min_numerator += (minorKey[j]-minor_mean) * 
                          (occurances[subscript]-mean);
         min_temp += (minorKey[j] - minor_mean) *
                     (minorKey[j] - minor_mean);

      }

      maj_denominator = sqrt(maj_temp * temp);
      min_denominator = sqrt(min_temp * temp);
      
      if (maj_denominator == 0 || min_denominator == 0) {
         return;
      }

      r_major[i] = maj_numerator / maj_denominator;
      r_minor[i] = min_numerator / min_denominator;
   }

   // Now determine which correlation is the greatest.
   // Start off with the assumption that C major is the best key.
   double best_key = 0.0; 
   char* mode = "unknown";
   int pitch_class = 0;            // tonic note of best key
   
   // Compare all the remaining key correlations.
   for (i=0; i<12; i++) {
      if (r_major[i] > best_key) {
         best_key = r_major[i]; 
         mode = "major"; 
         pitch_class = i;
      }
      if (r_minor[i] > best_key) {
         best_key = r_minor[i]; 
         mode = "minor"; 
         pitch_class = i;
      }
   }

   // A confidence measure can be determined by taking the difference
   // between the correlation for the "best key" and subtracting the
   // correlation for the "second best key".  The maximum confidence
   // score is 100; the minimum is zero.
   // First, having found the "best key", find the "second best key."
   double second_best_key = 0;
   char* secondmode = "unknown";
   int sec_pitch_class = 0;
   for (i=0; i<12; i++) {
      if (r_major[i] != best_key && r_major[i] > second_best_key) {
         second_best_key = r_major[i];
         secondmode = "major";
         sec_pitch_class = i;
      }
      if (r_minor[i] != best_key && r_minor[i] > second_best_key) {
         second_best_key = r_minor[i];
         secondmode = "minor";
         sec_pitch_class = i;
      }
   }

   // The value 3.0 below is a scaling factor.
   double confidence = (best_key - second_best_key) * 100 * 3.0;
   if (confidence > 100.0) confidence = 100.0;
   
   // Print the analysis results:
   cout << "   Key: " << name(pitch_class) << " " << mode 
        << " (";
   cout.width(3);
   cout << (int)confidence << "%), or " 
        << name(sec_pitch_class) << " " << secondmode << endl;

   // finally, play the determined keys according to confidence
   int velocity = (int)(127.0 * confidence/100.0);
   
   firstVoice.play(pitch_class + 12 * keyoctave, velocity);
   if (displayKey2) {
      secondVoice.play(sec_pitch_class + 12 * keyoctave, 127 - velocity);
   }

}


//////////////////////////////
//
// name -- return the name of the input pitch class
//

const char* name(int pitch_class) {
   pitch_class = pitch_class % 12;
   switch (pitch_class) {
      case 0:   return "C ";
      case 1:   return "C#";
      case 2:   return "D ";
      case 3:   return "Eb";
      case 4:   return "E ";
      case 5:   return "F ";
      case 6:   return "F#";
      case 7:   return "G ";
      case 8:   return "Af";
      case 9:   return "A ";
      case 10:  return "Bf";
      case 11:  return "B ";
   }
   
   return "unknown";
}
   




//////////////////////////////
//
// storeNote -- store the next note
//

void storeNote(char storeNote, long storeTime) {
   notes.insert(storeNote);
   times.insert(storeTime);
   occurances[storeNote%12]++;
}



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