//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Copyright (C) 2007-2011 Werner Schweer
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================


//=============================================================================
//  Keyfinder.cpp uses code from the "Melisma Music Analyzer"
//  project:
//
//  Copyright (C) 2000 Daniel Sleator and David Temperley
//  See http://www.link.cs.cmu.edu/music-analysis
//  for information about commercial use of this system
//=============================================================================

/* _Important comment about how pitches and keys are represented_

  Notes may be inputted in either Note format (with just a pitch) or
  TPCNote format (with a TPC). The user may also specify
  "npc_or_tpc_profile": 0 for npc, 1 for tpc. (A TPC profile is like
  an NPC profile except that a user-settable default value is used for
  TPC's outside the range of b2 to #4.) We assume that Note format
  will be used with an npc profile; if not, a fatal error is
  reported. TPCNote format could be used with either; if you use
  TPCNote input with an npc profile, the notes are simply mapped on to
  one cycle of the LOF.  (Scoring mode 0 or 3 requires an npc profile;
  otherwise a fatal error is reported.)

  Pitches (e.g. note[].tpc values) are always represented in
  line-of-fifths order, with C = 14. (This is done when the pitches
  are first read in). (If there's Note input, or TPCNote input and
  npc_or_tpc_profile = 0, all pitches are shifted into one cycle of
  the line of fifths, from 9 to 20 inclusive; again, this is done when
  the input is read.)

  Keys are also represented in line-of-fifths order. Major keys are 0
  to 27, C=14; minor keys are 28-55, C minor = 42. If
  npc_or_tpc_profile = 0, the search is nominally done on all keys,
  but only keys from 9 to 20 (inclusive) and 37 to 48 (inclusive) are
  looked at; others are given large negative values.

  Although key-profiles are read in in pitch-height order, they are
  then adjusted (in generate_tpc/npc_profiles) to line-of-fifths
  order, with the tonic as 5. */

#include <string.h>
#include <math.h>
#include "keyfinder.h"
#include "sig.h"
#include "pitchspelling.h"
#include "synthesizer/event.h"

namespace Ms {

#if 0
//---------------------------------------------------------
//   SBeat
//---------------------------------------------------------

struct SBeat {
      int time;
      };

//---------------------------------------------------------
//   MidiSegment
//---------------------------------------------------------

struct MidiSegment {
      int start;
      int end;
      QList<Event> snote;
      int numnotes;           // number of notes in the segment
      qreal average_dur;     // average input vector value (needed for K-S algorithm)
      };

#define CHANGE_PENALTY        12

static float change_penalty   = CHANGE_PENALTY;
static int npc_or_tpc_profile = 1;
static int scoring_mode       = 1;
static int verbosity          = 1;

static qreal major_profile[12] = {5.0, 2.0, 3.5, 2.0, 4.5, 4.0, 2.0, 4.5, 2.0, 3.5, 1.5, 4.0};
static qreal minor_profile[12] = {5.0, 2.0, 3.5, 4.5, 2.0, 4.0, 2.0, 4.5, 3.5, 2.0, 1.5, 4.0};
static qreal default_profile_value=1.5;

/*

CBMS profiles:

major_profile = 5.0 2.0 3.5 2.0 4.5 4.0 2.0 4.5 2.0 3.5 1.5 4.0
minor_profile = 5.0 2.0 3.5 4.5 2.0 4.0 2.0 4.5 3.5 2.0 1.5 4.0

Bayesian profiles (based on frequencies in Kostka-Payne corpus):

major_profile =  0.748  0.060  0.488  0.082  0.670  0.460  0.096  0.715  0.104  0.366  0.057  0.400
minor_profile =  0.712  0.084  0.474  0.618  0.049  0.460  0.105  0.747  0.404  0.067  0.133  0.330

Krumhansl's profiles:

major_profile = 6.35 2.23 3.48 2.33 4.38 4.09 2.52 5.19 2.39 3.66 2.29 2.88
minor_profile = 6.33 2.68 3.52 5.38 2.60 3.53 2.54 4.75 3.98 2.69 3.34 3.17

Krumhansl's minor, normalized: 5.94 2.51 3.30 5.05 2.44 3.31 2.38 4.46 3.73 2.52 3.13 2.97

*/

static int firstbeat;
static QList<Event> note;
static QList<MidiSegment> segment;  // An array storing the notes in each segment
static int segtotal;                // total number of segments - 1

static qreal seglength;

static QList<int> seg_prof[28];
static QList<qreal> key_score[56];
static QList<SBeat> sbeat;
static QList<qreal> analysis[56][56];

static QList<int> best[56];
static QList<int> final;

// static int numnotes, numchords, num_sbeats;
static int num_sbeats;

static QList<int> pc_tally;
static QList<qreal> final_score;

static qreal key_profile[56][28];
static int final_timepoint;

//---------------------------------------------------------
//   print_keyname
//---------------------------------------------------------

static void print_keyname(int f)
      {
      static const char letter[] = "CGDAEBF";

      int mf = f % 27;
      mf -= 14;

      qDebug("(%d,%d)%c", f, mf, letter[f % 7]);
      if (f < 6 || (f >= 28 && f < 34))
            qDebug("-");
      if ((f >= 6 && f < 13) || (f >= 34 && f < 41))
            qDebug("b");
      if ((f >= 20 && f < 27) || (f >=48 && f < 55))
            qDebug("#");
      if (f == 27 || f == 55)
            qDebug("x");
      if (f >= 28)
            qDebug("m");
      if (f < 28)
            qDebug(" ");
      qDebug(" ");
      }

//---------------------------------------------------------
//   create_segments
//    Each segment starts at a sbeat and ends at the
//    following sbeat
//---------------------------------------------------------

static void create_segments()
      {
      segment.append(MidiSegment());

      segment[0].start = firstbeat; // Always start a segment at the very beginning of the piece (the first beat)
      for (int b = 0; b < num_sbeats; b++) {
            if (b == 0 && (sbeat[0].time - firstbeat) < ((sbeat[1].time-firstbeat) - (sbeat[0].time-firstbeat))/2)
                  continue;
            /* If it's the first beat of the piece, and the upbeat is
               less than half of the first beat interval, don't start a segment
            */
            else {
                  MidiSegment seg;
                  seg.start          = sbeat[b].time;
                  segment.back().end = sbeat[b].time;
                  segment.append(seg);
                  }
            }
      int s = segment.size() - 1;

    /* If final segment starts at or after final timepoint of piece, ignore it,
       decrementing number of segments by 1; if not, set that segment's ending
       to final timepoint of piece
       */
      if (segment[s].start >= final_timepoint) {
            s--;
            }
      else {
            segment[s].end = final_timepoint;
            /* qDebug("Final segment ends at %d", segment[s].end); */
            }
      segtotal = s; // index of final segment
      }

//---------------------------------------------------------
//   fill_segments
//---------------------------------------------------------

static void fill_segments()
      {
      for (int s = 0; s < segment.size(); ++s) {
            foreach (const Event& n, note) {
                  int ontime  = n.ontime();
                  int offtime = n.offtime();
                  int start   = segment[s].start;
                  int end     = segment[s].end;

                  if (ontime >= start && ontime < end && offtime <= end) {
                        // note begins and ends in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(n.duration());
                        segment[s].snote.append(sn);
                        }
                  if (ontime >= start && ontime < end && offtime > end) {
                        // note begins, doesn't end in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(end - ontime);
                        segment[s].snote.append(sn);
                        }
                  if (ontime < start && offtime > start && offtime <= end) {
                        // note ends, doesn't begin in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(offtime - start);
                        segment[s].snote.append(sn);
                        }
                  if (ontime < start && offtime > end) {
                        // note doesn't begin or end in segment
                        Event sn(ME_NOTE);
                        sn.setDataA(n.dataA());
                        sn.setTpc(n.tpc());
                        sn.setDuration(end - start);
                        segment[s].snote.append(sn);
                        }
                  }
            segment[s].numnotes = segment[s].snote.size();
            // qDebug("fillSegments %d: %d-%d  %d", s, segment[s].start,
            //   segment[s].end, segment[s].numnotes);
            }
      }

//---------------------------------------------------------
//   count_segment_notes
//    In each segment, tally up the notes of each TPC
//---------------------------------------------------------

static void count_segment_notes()
      {
      for (int s = 0; s <= segtotal; ++s) {
            pc_tally[s] = 0;

            for (int y = 0; y < 28; ++y)  // cycle through the pc's, make sure all the seg_prof values are zero
                  seg_prof[y].append(0);

            qreal total_dur = 0;

            for (int n = 0; n < segment[s].numnotes; ++n) {
                  if (scoring_mode == 0)
                        total_dur += segment[s].snote[n].duration();
                  for (int y=0; y<28; ++y) {
                        if (segment[s].snote[n].tpc() == y) {
                              if(seg_prof[y][s]==0)
                                    pc_tally[s]++;
                              /* This keeps track of how many different pc's the segment contains. This counts TPCs, not NPCs! */
                              /* If scoring_mode is > 1, set array value to 1. If 0, add the note's duration to the
                                 array value (as in the K-S algorithm) */
                              if (scoring_mode > 0)
                                    seg_prof[y][s] = 1;
                              else {
                                    seg_prof[y][s] += segment[s].snote[n].duration();
                                    }
                              }
                        }
                  }

            if(scoring_mode == 0) {
                  if(pc_tally[s]==0)
                        segment[s].average_dur = 0.0;
                  segment[s].average_dur = total_dur / 12.0;
                  /* qDebug("Segment %d total dur = %6.3f, average dur = %6.3f", s, total_dur, segment[s].average_dur); */
                  }

            if (verbosity>=2) {
                  qDebug("Segment %d: ", s);
                  for (int y=0; y<28; ++y) {
                        if(npc_or_tpc_profile == 0 && (y<9 || y>20))
                              continue;
                        qDebug("%d ", seg_prof[y][s]);
                        }
                  }
            /* qDebug("pc_tally = %d", pc_tally[s]); */
            }
      }

//---------------------------------------------------------
//   prepare_profiles
//    We're only here if scoring_mode is 0 (the K-S algorithm).
//    Sum all the profile values, take the mean,
//    and subtract that from each value
//---------------------------------------------------------

static void prepare_profiles()
      {
      qreal total = 0.0;
      for (int i = 0; i < 12; i++) {
            total += major_profile[i];
            }
      qreal average = total / 12.0;
      for (int i = 0; i < 12; i++)
            major_profile[i]=major_profile[i] - average;

      total = 0;
      for (int i = 0; i < 12; i++) {
            total += minor_profile[i];
            }
      average = total / 12.0;
      for (int i = 0; i < 12; i++)
            minor_profile[i] = minor_profile[i] - average;

      if (verbosity > 2) {
            qDebug("Adjusted major profile: ");
            for(int i = 0; i < 12; i++)
                  qDebug("%6.3f ", major_profile[i]);
            qDebug("Adjusted minor profile: ");
            for (int i = 0; i < 12; i++)
                  qDebug("%6.3f ", minor_profile[i]);
            }
      }

/* Here we generate the key profiles. (This is assuming tpc input.) Key_profile[key] numbers correspond to
   the line of fifths, with C = 14.  Major keys are 0-27, minor keys are 28-55. PCs are also numbered
   according to the line of fifths. The major_step_profile has the tonic in step 5. For a given key, the
   profile value for a given tpc is equal to the line of fifths difference between the tpc and the key, plus
   5. */

//---------------------------------------------------------
//   generate_tpc_profiles
//---------------------------------------------------------

static void generate_tpc_profiles()
      {
      int key, shift, tpc, i;
      float majp[12];
      float minp[12];

      /* First we rearrange the key profile values (inputted in pitch height order) into lof order, C = 5 */
      for(i=0; i<12; i++) {
            majp[((((i * 7) % 12) + 5) % 12)] = major_profile[i];
            minp[((((i * 7) % 12) + 5) % 12)] = minor_profile[i];
            }

      for (key=0, shift=0; key<28; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  if (tpc-shift >= -5 && tpc-shift <= 6) {
                        key_profile[key][tpc] = majp[5 + (tpc-shift)];    /* For example: for key 14 (C major) and tpc 17 (A),
                                                                             use profile step 5 + (17-14) = 8 */
                        }
                  if (tpc-shift < -5 || tpc-shift > 6) {
                        key_profile[key][tpc] = default_profile_value;
                        }
                  }
            }
      for (key=28, shift=0; key<56; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  if (tpc-shift >= -5 && tpc-shift <= 6 ) {
                        key_profile[key][tpc] = minp[5 + (tpc-shift)];
                        }
                  if (tpc-shift < -5 || tpc-shift > 6) {
                        key_profile[key][tpc] = default_profile_value;
                        }
                  }
            }
/*
      This routine just prints out the key profiles
      for(key=0; key<56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  qDebug("%1.2f ", key_profile[key][tpc]);
                  }
            }
*/
      }

//-----------------------------------------------------------------------------
//   generate_npc_profiles
//    This is an alternative function for generating profiles given NPC
//    profile. It's similar to the TPC version, except that only keys on
//    a certain range of the line are assigned non-zero values. (This is
//    really an unnecessary step, as steps outside the range will be
//    disqualified in "match_profiles" in any case.) Also, for those keys
//    considered, only key profile slots within the 9-to-20 range are
//    assigned non-zero values. (This is necessary, since the input profile
//    is within this range as well.) We begin by assigning default values
//    of ZERO to everything.
//-----------------------------------------------------------------------------

static void generate_npc_profiles()
      {
      int key, shift, tpc, tpc_to_use, i;
      float majp[12];
      float minp[12];

      /* First we rearrange the key profile values (inputted in pitch height order) into lof order, C = 5 */
      for(i=0; i<12; i++) {
            majp[((((i * 7) % 12) + 5) % 12)] = major_profile[i];
            minp[((((i * 7) % 12) + 5) % 12)] = minor_profile[i];
            }
      for (key=0; key<56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  key_profile[key][tpc]=0;
                  }
            }

      for (key=9, shift=9; key<21; ++key, ++shift) {
            for (tpc=0; tpc<28; ++tpc) {
                  /* tpc_to_use is the profile slot to use for a given value of the key-profile. In this way we keep
                     all profile slots within the 9-to-20 range
                  */
                  if(tpc<9)
                        tpc_to_use=tpc+12;
                  else if(tpc>20)
                        tpc_to_use=tpc-12;
                  else
                        tpc_to_use = tpc;
                  if (tpc-shift >= -5 && tpc-shift <= 6) {
                        /* For example: for key 14 (C major) and tpc 17 (A),
                        read from profile step 5 + (17-14) = 8. For degree
                        6 of B major (key 19), degree 6 (22) is outside the
                        9-to-20 range, so tpc_to_use is 22-12=10; still
                        read from profile step 5 + (22-19) = 8. */

                        key_profile[key][tpc_to_use] = majp[5 + (tpc-shift)];
                        }
                  }
            }
      for (key = 37, shift = 9; key < 49; ++key, ++shift) {
            for (tpc = 0; tpc < 28; ++tpc) {
                  if (tpc<9)
                        tpc_to_use = tpc + 12;
                  else if (tpc > 20)
                        tpc_to_use = tpc - 12;
                  else
                        tpc_to_use = tpc;
                  if (tpc - shift >= -5 && tpc - shift <= 6 ) {
                        key_profile[key][tpc_to_use] = minp[5 + (tpc-shift)];
                        }
                  }
            }

/*
      for(key = 0; key < 56; ++key) {
            for(tpc=0; tpc<28; ++tpc) {
                  qDebug("%1.2f ", key_profile[key][tpc]);
                  }
            }
*/
      }

//---------------------------------------------------------
//   match_profiles
//    Here we generate the "key scores" - the local score for each key
//
//    Notice that we generate profiles for all 56 keys and all 28
//    tpc's, even in the case where npc profiles are being used. In
//    this case, though, all keys outside the allowable NPC range are
//    given large negative values.  And within both the key profiles
//    and the segment profiles, only TPC's within the range 9 to 20
//    have been given nonzero values.
//---------------------------------------------------------

static void match_profiles()
      {
      int key, tpc, s, best_key, i;
      qreal major_sumsq, minor_sumsq, input_sumsq;
      qreal kprob[56];

      for (key = 0; key < 56; ++key) {
            for (s = 0; s <= segtotal; ++s)
                  key_score[key].append(0.0);
            }

      if (scoring_mode==0) {
            major_sumsq = 0.0;
            minor_sumsq = 0.0;
            for(i=0; i<12; i++)
                  major_sumsq += major_profile[i]*major_profile[i];
            for(i=0; i<12; i++)
                  minor_sumsq += minor_profile[i]*minor_profile[i];
            if (verbosity==3)
                  qDebug("major_sumsq = %6.3f, minor_sumsq = %6.3f", major_sumsq, minor_sumsq);
            }

      qreal total_prob[segtotal + 1];

      for (s = 0; s <= segtotal; ++s) {
            if (scoring_mode==0) {
                  input_sumsq = 0.0;
                  for (i = 9; i <= 20; i++) {
                        input_sumsq += pow((seg_prof[i][s]-segment[s].average_dur), 2.0);
                        /* qDebug("%d X %6.3f squared is %6.3f", seg_prof[i][s], segment[s].average_dur, pow((seg_prof[i][s]-segment[s].average_dur), 2.0)); */
                        }
                  if (verbosity==3)
                        qDebug("For segment %d: average_dur = %6.3f; input_sumsq = %6.3f", s, segment[s].average_dur, input_sumsq);
                  }
            best_key=0;

            for (key=0; key<56; ++key) {
                  kprob[key] = 0.0;
                  key_score[key][s] = -1000000.0;
                  if (npc_or_tpc_profile==0 && (key<9 || (key>20 && key<37) || key>48))
                        continue;
                  kprob[key] = 1.0;
                  key_score[key][s] = 0.0;
                  for (tpc=0; tpc<28; ++tpc) {

          /*
             If scoring mode is 0, this is the K-S algorithm (this works for npc mode only). Segment
             profile values represent total duration of each pc (in all other cases, they're just 1
             for present pc's and 0 for absent ones). Key-profiles have been normalized linearly
             around the average key-profile value. We normalize the input values similarly by taking
             (seg_prof[tpc][s]-segment[s].average_dur). Then we multiply each normalized KP value by
             the normalized input value, and sum these products; this gives us the numerator of the
             correlation expression (as commented below). We've summed the squares of the normalized
             key-profile value (major_sumsq and minor_sumsq above) and the normalized input values
             (input_sumsq above), so this allows us to calculate the denominator also.

             If scoring_mode is 1, the key score is the sum of key-profile values for all pc's present
             (this is the algorithm used in CBMS)

             If scoring_mode is 2, calculate key scores as above, but divide each one by the number
             of pc's in the segment

             If scoring_mode is 3: for each key, add the log of the key-profile value for all present pc's;
             subtract values for all absent pc's. (This is the Bayesian approach; assume key-profiles
             represent pc distribution's in a corpus, i.e. the number of segments containing each scale
             degree)
          */

                        if(scoring_mode == 0) {
                              if(tpc<9 || tpc>20)
                                    continue;
                              /* calculate numerator */
                              key_score[key][s] += key_profile[key][tpc] * (seg_prof[tpc][s]-segment[s].average_dur);
                              /* qDebug("x-X=%6.3f, y-Y=%6.3f, product=%6.3f, new total=%6.3f", key_profile[key][tpc], seg_prof[tpc][s]-segment[s].average_dur, key_profile[key][tpc] * (seg_prof[tpc][s]-segment[s].average_dur), key_score[key][s]); */
                              }

                        if(scoring_mode==1 || scoring_mode==2)
                              key_score[key][s] += (key_profile[key][tpc] * seg_prof[tpc][s]);

                        if(scoring_mode == 3) {
                              /* if(tpc>11) continue; */
                              /* if(tpc<9 || tpc>20) continue; */

                              if(seg_prof[tpc][s]==0) {
                                    key_score[key][s] += log(1.000 - key_profile[key][tpc]);
                              /* qDebug("kp value = %6.3f: log(1-p) = %6.3f: score = %6.3f", key_profile[key][tpc], log(1.000 - key_profile[key][tpc]), key_score[key][s]); */
                                    if(tpc>=9 && tpc<=20)
                                          kprob[key] *= (1.000 - key_profile[key][tpc]);
                                    }
                              else {
                                    key_score[key][s] += log(key_profile[key][tpc]);
                                    if(tpc>=9 && tpc<=20)
                                          kprob[key] *= key_profile[key][tpc];
                                    }

                              /* qDebug("kp value = %6.3f: log(p) = %6.3f: score = %6.3f", key_profile[key][tpc], log(key_profile[key][tpc]), key_score[key][s]); */
                              }
                        }

                  if(scoring_mode == 0) {
                        /* qDebug("sqrt(major_sumsq * input_sumsq) = %6.3f", sqrt(major_sumsq * input_sumsq)); */
                        /* calculate denominator */
                        if(key<28)
                              key_score[key][s] = key_score[key][s] / sqrt(major_sumsq * input_sumsq);
                        else
                              key_score[key][s] = key_score[key][s] / sqrt(minor_sumsq * input_sumsq);
                        }
                  if(scoring_mode == 2) {
                        if(pc_tally[s] == 0)
                              key_score[key][s] = 0;
                        else
                              key_score[key][s] = key_score[key][s] / pc_tally[s];
                        }

                  /* if(s==0) qDebug("local score for key %d on segment %d: %6.3f", key, s, key_score[key][s]); */
                  if (key_score[key][s] > key_score[best_key][s])
                        best_key = key;
                  }

            if(verbosity>=2) {
                  qDebug("The best local key for segment %d at time %d is ", s, segment[s].start);
                  print_keyname(best_key);
                  qDebug("with score %6.3f", key_score[best_key][s]);
                  }

            if(scoring_mode==3) {
                  total_prob[s]=0.0;
                  for(key=0; key<56; key++) {
                        total_prob[s] += kprob[key] / 24.0;
                        /* qDebug("  Prob of segment %d given key %d: %6.8f", s, key, kprob[key]);  */
                        }

                  /* Now total_prob[s] is the total probability of the segment occurring: its probability given
                     a key, summed over all keys. But suppose we want to know the probability of ANY major triad
                     occurring? Then we have to multiply this by 12. (But not for something like a diminished
                     seventh which is symmetrical! */

                  if (verbosity>=3) {
                        qDebug("Best key for segment %d = %d, score = %6.8f", s, best_key, kprob[best_key]);
                        qDebug("Total (local) probability of segment %d: %6.8f", s, total_prob[s]);
                        }
                  }
            }
      }

//---------------------------------------------------------
//   choose_best_i
//---------------------------------------------------------

static void choose_best_i(int seg)
      {
      for (int j = 0; j < 56; ++j) {
            int k = 0;
            for (int i = 0; i < 56; ++i) {
                  if (analysis[i][j][seg] > analysis[k][j][seg])
                        k=i;
                  }
            /* For a given segment seg, and the key j at that segment,
               the best previous key is k
            */
            int size = best[j].size();
            for (int i = size; i < seg+1; ++i)
                  best[j].append(0);
            best[j][seg] = k;
            /* qDebug("For segment-%d-key %d, best segment-%d-key is %d, with score %d", seg, j, seg-1, k, analysis[k][j][seg]); */
            }
      }

//---------------------------------------------------------
//   make_first_table
//---------------------------------------------------------

static void make_first_table(int seg)
      {
      int i, j, s;
      qreal seg_factor, mod_factor, nomod_factor;

      if (scoring_mode==3) {
            mod_factor = log(change_penalty);
            nomod_factor = log(1.0 - change_penalty);
            seg_factor = 1.0;
            }
      else {
            mod_factor = -change_penalty;
            nomod_factor = 0.0;
            seg_factor = seglength;
            }

      for(s = 0; s <= segtotal; s++) {
            for(i = 0; i < 56; ++i) {
                  for(j = 0; j < 56; ++j)
                        analysis[i][j].append(-1000.0);
                  }
            }

      for(i = 0; i < 56; ++i) {
            for(j = 0; j < 56; ++j) {
                  if (j != i)
                        analysis[i][j][1] = ((key_score[i][0] + key_score[j][1]) * seg_factor) + mod_factor;
                  else
                        analysis[i][j][1] = ((key_score[i][0] + key_score[j][1]) * seg_factor) + nomod_factor;
                  }
            }
      choose_best_i(seg);
      }

//---------------------------------------------------------
//   make_tables
//---------------------------------------------------------

static void make_tables()
      {
      qreal seg_factor, mod_factor, nomod_factor;

      /* When scoring_mode = 3, the change_penalty represents the probability of changing key. So raising
         the penalty actually _increases_ the likelihood of modulations. */

      if (scoring_mode == 3) {
            mod_factor = log(change_penalty / 23.0);
            nomod_factor = log(1.0 - change_penalty);
            seg_factor = 1.0;
            }
      else {
            mod_factor = -change_penalty;
            nomod_factor = 0.0;
            seg_factor = seglength;
            }

      for (int seg = 2; seg <= segtotal; ++seg) {
            /* qDebug("mod_factor = %6.6f; ; nomod_factor = %6.6f", mod_factor, nomod_factor);  */
            for(int j = 0; j < 56; ++j) {
                  for(int i = 0; i < 56; ++i) {
                        int n = best[i][seg-1];
                        if (j != i)
                              analysis[i][j][seg] = analysis[n][i][seg-1] + (key_score[j][seg] * seg_factor) + mod_factor;
                        else
                              analysis[i][j][seg] = analysis[n][i][seg-1] + (key_score[j][seg] * seg_factor) + nomod_factor;
                        }
                  }
            choose_best_i(seg);
            }
      }

//---------------------------------------------------------
//   best_key_analysis
//---------------------------------------------------------

static void best_key_analysis()
      {
      int n, m, f, tie1=-1, tie2=-1;
      int s = segtotal;
      int k = 0;
      for(int j = 0; j < 56; ++j) {
            n = best[j][s];
            m = best[k][s];

            if (analysis[n][j][s] < analysis[m][k][s] + .001 && analysis[n][j][s] > analysis[m][k][s] - .001 && j!=k) {
                  tie1=j;
                  tie2=k;
                  }

            if (verbosity>1 && !(npc_or_tpc_profile == 0 && (j<9 || (j>20 && j<37) || j>48))) {
                  qDebug("Final score for ");
                  print_keyname(j);
                  /* qDebug("is %6.3f", analysis[n][j][s] * 1000 / (segment[segtotal].end - segment[0].start));   */
                  qDebug("is %6.3f", analysis[n][j][s]);
                  }
            if (analysis[n][j][s] > analysis[m][k][s] + .000001) {
                  /* The .000001 is to fix a strange bug: sometimes it thinks the conditional is satisfied in the case of ties */
                  k = j;   /* compute best key of final segment */
                  }
            }

      // To force a key choice at the final segment, insert key number here

      final[s] = k;
      if (verbosity > 1)
            if (k==tie1 || k==tie2)
                  qDebug("Tie at the end between %d and %d", tie1, tie2);

      // Here's where we take the best key choices and put them into final[s]

      for(s = segtotal; s >= 1; --s) {
            final[s-1] = best[k][s];
            k = final[s-1];
            }

      if (verbosity >= 2) {
            qDebug("Segment 0: key choice %d; total score %6.3f; segment score %6.3f", final[0], key_score[final[0]][0],
               key_score[final[0]][0]);
            qDebug("Segment 1: key choice %d; total score %6.3f; segment score %6.3f", final[1], analysis[final[0]][final[1]][1], analysis[final[0]][final[1]][1] - key_score[final[0]][0]);
            for(s = 2; s <= segtotal; s++) {
                  qDebug("Segment %d: key choice %d; total score %6.3f; segment score %6.3f", s, final[s], analysis[final[s-1]][final[s]][s], analysis[final[s-1]][final[s]][s] - analysis[final[s-2]][final[s-1]][s-1]);
                  }
            }

      if (verbosity > 1)
            qDebug("'Key-fit' scores for preferred analysis:");

      /* This routine calculates the key-fit scores for the final analysis. These are really per-second scores.
         Key-profile scores are not multiplied by seglength (as they would be in actually computing the analyses);
         change penalties are divided by seglength. */

      for (s = 0; s <= segtotal; ++s) {
            f = final[s];
            if (s > 0 && final[s] != final[s-1])
                  final_score[s] = (key_score[f][s]) - (change_penalty / seglength);
            else
                  final_score[s]=key_score[f][s];
            if (verbosity > 1) {
                  qDebug(" segment %d: %6.3f", s, final_score[s]);
                  }
            }
      }

//---------------------------------------------------------
//   findKey
//---------------------------------------------------------

int findKey(MidiTrack* mt, TimeSigMap* sigmap)
      {
      int tpc_found, npc_found;

      if ((scoring_mode == 0 || scoring_mode == 3) && npc_or_tpc_profile == 1) {
            qDebug("Error: scoring mode %d requires an npc profile", scoring_mode);
            exit(1);
            }

      final_timepoint=0;

      tpc_found = 0;
      npc_found = 0;

      int lastTick = 0;
      const EventList el = mt->events();

      foreach (Event e, el) {
            if (e.type() != ME_NOTE)
                  continue;
            if (e.offtime() > lastTick)
                  lastTick = e.offtime();
            // For note input, generate TPC labels within the 9-to-20 range
            e.setTpc((((((e.pitch() % 12) * 7) % 12) + 5) % 12) + 9);
            note.append(e);
            }
      spell(note, 0);
      npc_found = 1;

      // create one segment for every measure
      for (int i = 0;; ++i) {
            int tick = sigmap->bar2tick(i, 0, 0);
            SBeat b;
            b.time = tick;
            sbeat.append(b);
            if (tick > lastTick)
                  break;
            }

      firstbeat       = sbeat.first().time;
      final_timepoint = sbeat.last().time;
      num_sbeats      = sbeat.size();

      numnotes   = note.size();
      numchords  = 0;

      if (note.isEmpty()) {
            qDebug("Error: No notes in input.");
            return 0;
            }

      seglength  = (sbeat[1].time - sbeat[0].time) / 1000.0; /* define segment length as the length of the first segment */
      if (verbosity > 1)
            qDebug("seglength = %3.3f", seglength);

      create_segments();
      for (int i = 0; i < segtotal+1; ++i) {
            final.append(0);
            pc_tally.append(0);
            final_score.append(0.0);
            }

      fill_segments();
      count_segment_notes();
      if (scoring_mode==0)
            prepare_profiles();
      if (npc_or_tpc_profile == 1)
            generate_tpc_profiles();
      else
            generate_npc_profiles();
      match_profiles();
      if (segtotal > 0) {
            make_first_table(1);
            make_tables();
            best_key_analysis();
            }

      QList<int> keys;
      for (int i = 0; i < 27; ++i)
            keys.append(0);
      for (int i = 0; i <= segtotal; ++i) {
            keys[final[i] % 27]++;        // fold major/minor
//            qDebug("key %d: %d  %d", i, final[i], (final[i] % 27) - 14);
            }
      int xkey   = 0;
      int xcount = 0;
      for (int i = 0; i < 27; ++i) {
            if (keys[i] > xcount) {
                  xcount = keys[i];
                  xkey   = i;
                  }
            }

      xkey -= 14;
      if (xkey < -7 || xkey > 7) {
            qDebug("findKey(): illegal key %d found", xkey);
            xkey = 0;
            }

      spell(note, xkey);      // spell again with found key

      //
      // clear all arrays
      //
      note.clear();
      segment.clear();
      sbeat.clear();
      for (int i = 0; i < 28; ++i)
            seg_prof[i].clear();
      for (int i = 0; i < 56; ++i) {
            key_score[i].clear();
            best[i].clear();
            for (int k = 0; k < 56; ++k)
                  analysis[i][k].clear();
            }
      final.clear();
      pc_tally.clear();
      final_score.clear();

      return xkey;
      }
#endif

}

