//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun May 16 12:54:28 PDT 1999
// Last Modified: Sun May 31 19:49:53 EDT 2009 (added alternative weights)
// Filename:      .../improv/examples/synthImprov/rtkey/rtkey.cpp
// Syntax:        C++; synthImprov 2.1
//  
// Description:   analyze the key of musical input and output the determined
//                key as a note on the tonic of the key.  The certainty of
//                the measurement is mapped to the loudness on the analysis
//                note.
//
// References:    Key analysis is done using the Krumhansl-Schmuckler
//                key-finding algorithm which measures Pearson correlation
//                of the musical data against prototype pitch distributions
//                of major and minor key profiles. The key which produces
//                the highest correlation is determined to be the key.
//

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

#ifndef OLDCPP
   #include <iostream>
   #include <iomanip>
   using namespace std;
#else
   #include <iostream.h>
   #include <iomanip.h>
#endif


/*----------------- 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 occurrences[12] = {0};    // number of notes occurrences
int fadeNote          = 0;      // next to to go out of scope
int fadeTime          = 0;      // next time to go out of scope
int keyoctave         = 7;      // the analysis key performance octave
int displayKey2       = 0;      // display the second key possibility
int octave            = 4;      // used for keyboard keyboard
int freezeQ           = 0;      // used with 'f' keyboard command
SigTimer metronome;             // for display period of key analysis
double analysisDuration;        // duration in seconds of analysis window
double tempo;                   // tempo of the rtkeyalysis

// key profile weights: weights repeated twice to make calculations
// simpler for input to pearsonCorrelation().
// Aarden 2003
double AardenEssenMajor[24] = 
   {17.7661, 0.145624, 14.9265, 0.160186, 19.8049, 11.3587, 
    0.291248, 22.062, 0.145624, 8.15494, 0.232998, 4.95122,
    17.7661, 0.145624, 14.9265, 0.160186, 19.8049, 11.3587, 
    0.291248, 22.062, 0.145624, 8.15494, 0.232998, 4.95122};
double AardenEssenMinor[24] = 
   {18.2648, 0.737619, 14.0499, 16.8599, 0.702494, 14.4362,
    0.702494, 18.6161, 4.56621, 1.93186, 7.37619, 1.75623,
    18.2648, 0.737619, 14.0499, 16.8599, 0.702494, 14.4362,
    0.702494, 18.6161, 4.56621, 1.93186, 7.37619, 1.75623};
// Bellman 2005
double BellmanBudgeMajor[24] = 
   {16.80, 0.86, 12.95, 1.41, 13.49, 11.93,
    1.25, 20.28, 1.80, 8.04, 0.62, 10.57,
    16.80, 0.86, 12.95, 1.41, 13.49, 11.93,
    1.25, 20.28, 1.80, 8.04, 0.62, 10.57};
double BellmanBudgeMinor[24] = 
   {18.16, 0.69, 12.99, 13.34, 1.07, 11.15,
    1.38, 21.07, 7.49, 1.53, 0.92, 10.21,
    18.16, 0.69, 12.99, 13.34, 1.07, 11.15,
    1.38, 21.07, 7.49, 1.53, 0.92, 10.21}; 
// Krumhansl & Kessler (1982) weightings
double KrumhanslKesslerMajor[24] = 
   {6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88,
    6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88};
double KrumhanslKesslerMinor[24] = 
   {6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17,
    6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17};
// Craig 2003
double SimpleMajor[24] = {2, 0, 1, 0, 1, 1, 0, 2, 0, 1, 0, 1,
                          2, 0, 1, 0, 1, 1, 0, 2, 0, 1, 0, 1};
double SimpleMinor[24] = {2, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0,
                          2, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0};
// Temperley 2007
double TemperleyMajor[24] = 
   {0.748, 0.060, 0.488, 0.082, 0.670, 0.460,
    0.096, 0.715, 0.104, 0.366, 0.057, 0.400,
    0.748, 0.060, 0.488, 0.082, 0.670, 0.460,
    0.096, 0.715, 0.104, 0.366, 0.057, 0.400};
double TemperleyMinor[24] = 
   {0.712, 0.084, 0.474, 0.618, 0.049, 0.460,
    0.105, 0.747, 0.404, 0.067, 0.133, 0.330,
    0.712, 0.084, 0.474, 0.618, 0.049, 0.460,
    0.105, 0.747, 0.404, 0.067, 0.133, 0.330};

double* majorKey = TemperleyMajor;  // weights for analysis of major keys
double* minorKey = TemperleyMinor;  // 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);
void        keyboard(int key);
const char* name(int pitch_class);
double      pearsonCorrelation(int size, double* x, double* y);


/*--------------------- 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) {
   printboxtop();
   psl("Keyan -- analyze musical input for key");
   psl("");
   psl(" < = Slow down analysis note tempo      > = Speed up analysis note tempo");
   psl(" @ = Toggle playing of secondary choice - = Clear note memory");
   psl(" A = Use Aarden-Essen key profiles      B = Use Bellman-Budge key profiles");
   psl(" P = Use Simple key profiles            T = Use Temperley key profiles");
   psl(" K = Use Krumhansl-Kessler key profiles");
   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();

} 



//////////////////////////////
//
// 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 rtkeyalysis
   metronome.setTempo(tempo);
   firstVoice.setChannel(0);
   secondVoice.setChannel(0);
   notes.setSize(10000);
   times.setSize(10000);

}



//////////////////////////////
//
// 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 -----------------------------*/


//////////////////////////////
//
// keyboard -- simulate a MIDI keyboard on the computer keyboard.
//

void keyboard(int key) {
   static int keyboardnote = -1;   // computer keyboard note
   if (keyboardnote < -1) {
      synth.play(0, keyboardnote, 0);
   }
   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;
   synth.play(0, keyboardnote, 100);
   storeNote(keyboardnote, t_time);
}



///////////////////////////////
//
// 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 'A':            // Use Aarden-Essen key profiles
         majorKey = AardenEssenMajor;
         minorKey = AardenEssenMinor;
         cout << "Aarden-Essen weights" << endl;
         break;
      case 'B':            // Use Bellman-Budge key profiles
         majorKey = BellmanBudgeMajor;
         minorKey = BellmanBudgeMinor;
         cout << "Bellman-Budge weights" << endl;
         break;
      case 'K':            // Use Krumhansl-Kessler key profiles
         majorKey = KrumhanslKesslerMajor;
         minorKey = KrumhanslKesslerMinor;
         cout << "Krumhansl-Kessler weights" << endl;
         break;
      case 'P':            // Use simPle key profiles
         majorKey = SimpleMajor;
         minorKey = SimpleMinor;
         cout << "Simple weights" << endl;
         break;
      case 'T':            // Use Temperley key profiles
         majorKey = TemperleyMajor;
         minorKey = TemperleyMinor;
         cout << "Temperley weights" << endl;
         break;
      case '-':            // Clear note memory
         notes.reset();
         times.reset();
         fadeTime = 0;
         fadeNote = 0;
         { for (int i=0; i<12; i++) { occurrences[i] = 0; } }
         cout << "Note memory cleared" << endl;
         break;
      case 'f':            // freeze histogram (no deleteing of notes in buffer)
         freezeQ = !freezeQ;
         if (freezeQ) {
            cout << "ANALYSIS HISTOGRAM IS FROZEN" << endl;
         } else {
            cout << "ANALYSIS HISTOGRAM IS UNFROZEN" << endl;
         }
         break;
      case '@':            // 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;
      case '{': case '[':  // shorten analysis window by one second
         analysisDuration -= 1.0;
         if (analysisDuration < 1.0) { analysisDuration = 1.0; }
         cout << "ANALYSIS HISTORY: " << analysisDuration << " sec." << endl;
         break;
      case '}': case ']':  // lengthen analysis window by one second
         analysisDuration += 1.0;
         cout << "ANALYSIS HISTORY: " << analysisDuration << " sec." << endl;
         break;
      default:
         keyboard(key);
   }

   if (isdigit(key)) {
      keyoctave = key - '0';
      cout << "ANALYSIS OCTAVE: " << keyoctave << endl;
   }

}


/*------------------ 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
   if (!freezeQ) {
      while ((times.getCount() > 0) 
             && (fadeTime < t_time - analysisDuration * 1000)) {
         if (fadeTime > 0) {
            occurrences[fadeNote%12]--;
         }
         fadeTime = times.extract();
         fadeNote = notes.extract();
      }
      if ((fadeTime > 0) && (fadeTime < t_time - analysisDuration * 1000)) {
         occurrences[fadeNote%12]--;
         fadeTime = 0;
         fadeNote = 0;
      }
   }
      
   // now analyze the keys
   double total = 0;
   cout << "Count:";
   for (i=0; i<12; i++) {
      cout.width(3);
      cout << occurrences[i];
      total += occurrences[i];
   }
   cout << flush;

   if (total <= 0) {
      cout << endl;
      return;
   }
      
   double r_major[12];
   double r_minor[12];
   for (i=0; i<12; i++) {
      r_major[i] = pearsonCorrelation(12, occurrences, majorKey + 12 - i);
      r_minor[i] = pearsonCorrelation(12, occurrences, minorKey + 12 - i);
   }

   // Now determine which correlation is the greatest.
   // Start off with the assumption that C major is the best key.
   double best_key = 0.0; 
   const 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;
   const 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;
   cout << 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, int(velocity*0.75 + 0.5));
   }

}



//////////////////////////////
//
// pearsonCorrelation --
//

double pearsonCorrelation(int size, double* x, double* y) {

   double sumx  = 0.0;
   double sumy  = 0.0;
   double sumco = 0.0;
   double meanx = x[0];
   double meany = y[0];
   double sweep;
   double deltax;
   double deltay;

   int i;
   for (i=2; i<=size; i++) {
      sweep = (i-1.0) / i;
      deltax = x[i-1] - meanx;
      deltay = y[i-1] - meany;
      sumx  += deltax * deltax * sweep;
      sumy  += deltay * deltay * sweep;
      sumco += deltax * deltay * sweep;
      meanx += deltax / i;
      meany += deltay / i;
   }

   double popsdx = sqrt(sumx / size);
   double popsdy = sqrt(sumy / size);
   double covxy  = sumco / size;

   return covxy / (popsdx * popsdy);
}



//////////////////////////////
//
// 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 "Ab";
      case 9:   return "A ";
      case 10:  return "Bb";
      case 11:  return "B ";
   }
   
   return "unknown";
}
   




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

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



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



// md5sum: 37e8b26f4e2bda279991a5247715ff18 rtkey.cpp [20090626]