/*
 * Krumhansl Key Finding Algorithmus
 * berechnet mit Hilfe der summierten Dauer der Pitches
 * (pro Note [C, C#, D, D#, E, F, F#, G, G#, A, A#, B])
 * die Korrelationskoeffizienten fuer alle Tonarten
 * und liefter die wahrscheinlichste Tonart zurueck.
 * 
 * Quelle: http://rnhart.net/articles/key-finding/
 */
package midi;

/**
 *
 * @author Tom
 */
public class KeyFinder {
   // page 35 of Krumhansl: Cognitive foundations of musical pitch
   private static double[] majorKeyKrumhansl = {
      6.35,    // C
      2.23,    // C#
      3.48,    // D
      2.33,    // D#
      4.38,    // E
      4.09,    // F
      2.52,    // F#
      5.19,    // G
      2.39,    // G#
      3.66,    // A
      2.29,    // A#
      2.88};   // B

   private static double[] minorKeyKrumhansl = {
      6.33,    // C
      2.68,    // C#
      3.52,    // D
      5.38,    // D#
      2.60,    // E
      3.53,    // F
      2.54,    // F#
      4.75,    // G
      3.98,    // G#
      2.69,    // A
      3.34,    // A#
      3.17};   // B
   
   private double[] noteDurations; //summierte Dauer der Noten
   private double[] coeffMajor = {0,0,0,0,0,0,0,0,0,0,0,0}; //Koeffizienten der Dur-Tonarten
   private double[] coeffMinor = {0,0,0,0,0,0,0,0,0,0,0,0}; //Koeffizienten der Moll-Tonarten
   private static String[] noteNames = {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"}; //Bezeichnungen der Tonarten
   private double meanNotes = 0; //Durchschnitt aller Noten
   private double meanMajor = 0; //Durchschnitt des Dur-Profils
   private double meanMinor = 0; //Durchschnitt des Moll-Profils
   
   /**
    * Berechnet die Koeffizienten zur Erkennung der Tonart des Musikstuecks
    * @param _noteDurations Array mit den summierten Dauern pro Note (C, C#, D, D#, E, F, ...)
    */
   public KeyFinder(double[] _noteDurations) {
      noteDurations = _noteDurations;
      
      meanNotes = getMean(noteDurations);
      meanMajor = getMean(majorKeyKrumhansl);
      meanMinor = getMean(minorKeyKrumhansl);
      
      calculateCoeffs();
   }
   
   /**
    * Berechnet die Durchschnitte und in weiterer Folge
    * die Koeffizienten zur Erkennung der Tonart des Musikstuecks
    */
   private void calculateCoeffs() {
      double[] xMajor = getNumeratorX(majorKeyKrumhansl, meanMajor);
      double[] xMinor = getNumeratorX(minorKeyKrumhansl, meanMinor);
      double numerator, denominator;
      
      //berechne alle Koeffizienten der Tonarten
      for (int i=0; i<coeffMajor.length; i++) {
         //Major
         numerator = getNumerator(xMajor, noteDurations, meanNotes, i);
         denominator = getDenominator(xMajor, noteDurations, meanNotes, i);
         coeffMajor[i] = numerator/denominator;
         //Minor
         numerator = getNumerator(xMinor, noteDurations, meanNotes, i);
         denominator = getDenominator(xMinor, noteDurations, meanNotes, i);
         coeffMinor[i] = numerator/denominator;
      }
   }
   
   /**
    * liefert den Nenner des aktuellen Koeffizienten
    * @param X Array mit den Werten von (xi - x_mean)
    * @param Duration Array mit den summierten Dauern pro Note (C, C#, D, D#, E, F, ...)
    * @param mean Durchschnitt des Arrays mit den Dauern der Note
    * @param startY Startindex im Array der Notendauern
    * @return der Nenner des Koeffizienten
    */
   private double getDenominator(double[] X, double[] Duration, double mean, int startY) {
      double left = 0.0, right = 0.0;
      
      for (int i=0; i<X.length; i++) {
         left += Math.pow(X[i],2);
         right += Math.pow((Duration[startY]-mean),2);
         startY++;
         if (startY == Duration.length) {
            startY = 0;
         }
      }
      return Math.sqrt(left*right);
   }
   
   /**
    * liefert den Zaehler des aktuellen Koeffizienten
    * @param X Array mit den Werten von (xi - x_mean)
    * @param Duration Array mit den summierten Dauern pro Note (C, C#, D, D#, E, F, ...)
    * @param mean Durchschnitt des Arrays mit den Dauern der Note
    * @param startY Startindex im Array der Notendauern
    * @return der Zaehler des Koeffizienten
    */
   private double getNumerator(double[] X, double[] Duration, double mean, int startY) {
      //Zaehler berechnen
      double d = 0.0;
      for (int i=0; i<X.length; i++) {
         d += X[i]*(Duration[startY]-mean);
         startY++;
         if (startY == Duration.length) {
            startY = 0;
         }
      }
      return d;
   }
   
   /**
    * liefert den Teil Summe[xi - x_mean] des Zaehlers
    * @param dArr Array mit dem Krumhanslprofil
    * @param mean Durchschnitt des Arrays des Krumhansprofils
    * @return Array mit den Werten (xi - x_mean) pro Eintrag im Krumhanslprofil
    */
   private double[] getNumeratorX(double[] dArr, double mean) {
      double[] d = {0,0,0,0,0,0,0,0,0,0,0,0};
      //xi - xmean
      for (int i=0; i<dArr.length; i++) {
         d[i] = dArr[i] - mean;
      }
      return d;
   }
   
   /**
    * liefert den Durchschnitt des uebergebenen Arrays
    * @param dArr Array, dessen Durchschnitt berechnet wird
    * @return Durchschnitt
    */
   private double getMean(double[] dArr) {
      double d = 0.0;
      for (int i=0; i<dArr.length; i++) {
         d += dArr[i];
      }
      d = d / dArr.length;
      return d;
   }
   
   /**
    * Ausgabe der berechneten Koeffizienten und der
    * resultierenden Tonart
    */
   public void printCoeffs() {
      double max = 0.0;
      String maxNote = "";
      System.out.println("MAJOR:");
      for (int i=0; i<coeffMajor.length; i++) {
         System.out.println(noteNames[i] +" " +coeffMajor[i]);
         if (coeffMajor[i] > max) {
            max = coeffMajor[i];
            maxNote = "Major " +noteNames[i];
         }
      }
      System.out.println("MINOR:");
      for (int i=0; i<coeffMinor.length; i++) {
         System.out.println(noteNames[i] +" " +coeffMinor[i]);
         if (coeffMinor[i] > max) {
            max = coeffMinor[i];
            maxNote = "Minor " +noteNames[i];
         }
      }
      System.out.println("expected Key: " +maxNote +" " +max);
   }
   
   /**
    * liefter die Tonart des Musikstuecks
    * zB.: Major C, Minor D, ...
    * @return Tonart
    */
   public String getKey() {
      double max = 0.0;
      String maxNote = null;

      for (int i=0; i<coeffMajor.length; i++) {
         if (coeffMajor[i] > max) {
            max = coeffMajor[i];
            maxNote = "Major " +noteNames[i];
         }
      }
      for (int i=0; i<coeffMinor.length; i++) {
         if (coeffMinor[i] > max) {
            max = coeffMinor[i];
            maxNote = "Minor " +noteNames[i];
         }
      }
      return maxNote;
   }
}
