//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Jun 24 20:05:15 PDT 2002
// Last Modified: Sun Aug 25 17:47:13 PDT 2002
// Last Modified: Sat Nov 30 15:46:46 PST 2002 (added triad spectrum display)
// Filename:      ...sig/examples/all/iwroot.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/iwroot.cpp
// Syntax:        C++; museinfo
//
// Description:   Determine the root of a pitch set according to
//                different root-interval class sizes.
// 

#define MYPI 3.141592653589793

#include "humdrum.h"

#include <string.h>
#include <math.h>

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   printScores(Array<double>& rootscores);
void   printDistances(IntervalWeight& distances, int type);
int    analyzeChromatic(Array<double>& rootscores, HumdrumFile& infile,
                                   IntervalWeight& distances, int startline, 
                                   int stopline, int correct = -1, 
                                   int debugQ = 0);
double base40ToBase7(int pitch);
char   base7pcToName(int value);
void   identifyErrorsChromatic(HumdrumFile& infile, int type, int debugQ);
void   generateDistances(IntervalWeight& distances, int type);
void   printNotes(int correct, Array<int>& pitches);
void   printNotes(int correct, Array<int>& pitches,
                                 Array<double>& durations,
                                 Array<double>& levels);
int    intcompare(const void* a, const void* b);
int    pdlcompare(const void* a, const void* b);
int    doublecompare(const void* a, const void* b);
void   setSortOrder(IntervalWeight& dist, Array<int>& index);
void   getStartingWeights(IntervalWeight& weights, 
                                 HumdrumFile& weightfile);
double octavescaling(int pitch, double scaling);
double durationscaling(double duration);
double metricscaling(double metriclevel);

// global variables
Options      options;            // database for command-line arguments
int          debugQ     = 0;     // used with --debug option
int          appendQ    = 0;     // used with -a option
int          rootQ      = 0;     // used with -R option
int          errorsQ    = 0;     // used with -e option
double       theta1     = 51.0;  // used with -t option
double       theta2     = 51.0;  // used with -u option
int          invertQ    = 0;     // used with -i option
int          distanceQ  = 0;     // used with -d option
int          notesQ     = 0;     // used with -n option
int          transposeQ = 0;     // used with -C option
int          singleQ    = 0;     // used with -s option
int          sortQ      = 0;     // used with -S option
int          locationQ  = 0;     // used with -l option
int          circularQ  = 0;     // used with -c option
double       power      = 1.0;   // used with -p option
double       circular   = 0.0;   // used with -c option
double       offset     = 0.0;   // used with -o option
int          weightQ    = 0;     // used with -w option
const char*  weightFile = "";    // used with -w option
int          verboseQ   = 0;     // used with -v option
int          octaveQ    = 0;     // used with -o option
double       octaveScale= 0.0;   // used with -o option
int          rhythmQ    = 0;     // used with -r option
double       durationfactor=0.8; // used with --dfactor
double       metricfactor=0.5;   // used with --mfactor
int          spectrumQ  = 0;     // used with --spectrum

const char* CurrentFile = "";    // current file being processed
int         CurrentLine = 0;     // current line in file being processed


///////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[]) {
   HumdrumFile infile;

   // process the command-line options
   checkOptions(options, argc, argv);

   int i;
   for (i=1; i<=options.getArgCount() || options.getArgCount()==0; i++) {
      infile.clear();

      // if no command-line arguments read data file from standard input
      if (options.getArgCount() < 1) {
         infile.read(cin);
      } else {
         CurrentFile = options.getArg(i);
         infile.read(CurrentFile);
      }

      if (errorsQ) {
         if (options.getBoolean("theta2")) {
            identifyErrorsChromatic(infile, 2, debugQ);
         } else if (circularQ) {
            identifyErrorsChromatic(infile, 4, debugQ);
         } else if (weightQ) {
            identifyErrorsChromatic(infile, 0, debugQ);
         } else {
            identifyErrorsChromatic(infile, 1, debugQ);
         }
      } else if (distanceQ) {
         IntervalWeight distances;
         if (options.getBoolean("theta2")) {
            generateDistances(distances, 2);
            printDistances(distances, 2);
         } else if (circularQ) {
            generateDistances(distances, 4);
            printDistances(distances, 4);
         } else if (weightQ) {
            generateDistances(distances, 0);
            printDistances(distances, 0);
         } else {
            generateDistances(distances, 1);
            printDistances(distances, 1);
         }
      }

   }
   return 0;
}


///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// generateDistances --
//

void generateDistances(IntervalWeight& distances, int type) { 

   switch (type) {
      case 0: // read weight file
         {
            HumdrumFile infile(weightFile);
            getStartingWeights(distances, infile);
         }
         break;
      case 1:
         distances.setChromatic1(theta1);
         break;
      case 2:
         distances.setChromatic2(theta1, theta2);
         break;
      case 4: 
         distances.setCircular(theta1, circular);
         break;
      default:
         cout << "Error: unknown type: " << type << endl;
         exit(1);
   }

   int i;
   if (power != 1.0) {
      for (i=0; i<40; i++) {
         distances[i] = pow(distances[i], power);
      }
   }
   if (offset != 0.0) {
      for (i=0; i<40; i++) {
         distances[i] += offset;
      }
   }
}



//////////////////////////////
//
// identifyErrorsChromatic -- compare root scores value to root in
//     humdrum file and print out errors.
//

void identifyErrorsChromatic(HumdrumFile& infile, int type, int debugQ) {
   int oldline = 0;
   int init = 0;
   int best = 0;      // best root according to algorithm
   int correct = 0;   // correct base40 root
   int i;
   int j;

   // get interval distances:
   IntervalWeight distances;
   generateDistances(distances, type);

   // go though the file and compare all chords to **root info:
   Array<double> rootscores(40);
   rootscores.setAll(0);
   for (i=0; i<infile.getNumLines(); i++) {
      CurrentLine = i+1;
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (strcmp(infile[i].getExInterp(j), "**root") == 0) {
            if (strcmp(infile[i][j], ".") == 0) {
               break;
            } 
            if (strchr(infile[i][j], '_') != NULL) {  // cont. ties
               break;
            }
            if (strchr(infile[i][j], ']') != NULL) {  // tie ends
               break;
            }
            if (init == 0) {
               init = 1;
               oldline = i;
               correct = Convert::kernToBase40(infile[i][j]) % 40;
               break;
            }
            best = analyzeChromatic(rootscores, infile, distances,
                      oldline, i-1, correct, debugQ);
            if ((best >= 0) && (best+2 != correct)) {
               char zbuffer[128] = {0};
               cout << "Error on line " << oldline + 1
                    << "\tcorrect=" 
                    << Convert::base40ToKern(zbuffer, correct+4*40);
               cout << "\tvalue=" 
                    << Convert::base40ToKern(zbuffer, best+2+4*40) << endl;
            }
            oldline = i;
            correct = Convert::kernToBase40(infile[i][j]) % 40;
            break;
         }
      }
      
   }
   best = analyzeChromatic(rootscores, infile, distances, oldline,
         i-1, correct, debugQ);
   if ((best >= 0) && (best+2 != correct)) {
      char zbuffer[128] = {0};
      cout << "Error on line " << oldline + 1
           << "\tcorrect=" << Convert::base40ToKern(zbuffer, correct+4*40);
      cout << "\tvalue=" << Convert::base40ToKern(zbuffer, best+2+4*40)
           << endl;

   }

}



//////////////////////////////
//
// analyzeChromatic --
//

int analyzeChromatic(Array<double>& rootscores, HumdrumFile& infile, 
      IntervalWeight& distances, int startline, int stopline, int correct, 
      int debugQ) {

   // extract note data
   Array<double> absbeat;
   Array<int>    pitches;
   Array<double> durations;
   Array<double> levels;
   infile.getNoteArray(absbeat, pitches, durations, levels, startline,
      stopline);

   if (pitches.getSize() == 0) {
      // give up if there are no notes in the specified region
      return -1;
   }

   if (notesQ) {
      if (rhythmQ) {
         printNotes(correct, pitches, durations, levels);
      } else {
         printNotes(correct, pitches);
      }
   }

   char buffer[64] = {0};
   if (debugQ) {
      int i;
      cout << "====================" << endl;
      for (i=startline; i<=stopline; i++) {
         cout << infile[i] << "\n";
      }
      cout << "--------------------" << endl;
      cout << "Start line=" << startline+1 
           << "\tEnd line=" << stopline+1 << endl;
      for (i=0; i<pitches.getSize(); i++) {
         cout << "pitch = " 
              << Convert::base40ToKern(buffer, pitches[i]) << endl;
      }
   }

   rootscores.setSize(40);
   rootscores.setAll(0.0);
   int i;
   int j;

   if (rhythmQ) {
      if (octaveQ) {
         for (i=0; i<rootscores.getSize(); i++) {
            for (j=0; j<pitches.getSize(); j++) {
               rootscores[i] += octavescaling(pitches[j], octaveScale) * 
                  distances[(pitches[j]-i+400)%40] * 
                  durationscaling(durations[j]) *
                  metricscaling(levels[j]);
            }
         }
      } else {
         for (i=0; i<rootscores.getSize(); i++) {
            for (j=0; j<pitches.getSize(); j++) {
               rootscores[i] += distances[(pitches[j]-i+400)%40] *
                  durationscaling(durations[j]) *
                  metricscaling(levels[j]);
            }
         }
      }
   } else {
      if (octaveQ) {
         for (i=0; i<rootscores.getSize(); i++) {
            for (j=0; j<pitches.getSize(); j++) {
               rootscores[i] += octavescaling(pitches[j], octaveScale) * 
                  distances[(pitches[j]-i+400)%40];
            }
         }
      } else {
         for (i=0; i<rootscores.getSize(); i++) {
            for (j=0; j<pitches.getSize(); j++) {
               rootscores[i] += distances[(pitches[j]-i+400)%40];
            }
         }
      }
   }

   int min = 0;
   for (i=1; i<rootscores.getSize(); i++) {
      if (rootscores[i] < rootscores[min]) {
         min = i;
      }
   }

   if (spectrumQ) {
      cout << "======================\n";
      for (i=0; i<rootscores.getSize(); i++) {
         cout << Convert::base40ToKern(buffer, i+2+3*40) 
              << "\t" << 1.0/rootscores[i] << "\n";
      }
      cout << "======================\n";
   }

   if (verboseQ) {
      cout << "!! BEST ROOT = " << Convert::base40ToKern(buffer, min+2+3*40) 
           << endl;
      printScores(rootscores);
   }

   return min;
}



//////////////////////////////
//
// durationscaling -- scaling value from duration information
//

double durationscaling(double duration) {
   if (duration <= 0.0) {
      return 0.0;
   }
   double alpha = durationfactor;
   double value = duration;
   return pow(value, alpha);
}



//////////////////////////////
//
// metricscaling -- scaling value from metric level information
//

double metricscaling(double metriclevel) {
   offset = 0.0;
   metriclevel += offset;
   if (metriclevel <= 0.0) {
      return 0.0;
   }
   double alpha = metricfactor;
   double value = metriclevel;
   return pow(value, alpha);
}



//////////////////////////////
//
// octavescaling -- generate a octave weighting.
//

double octavescaling(int pitch, double scaling) {
   int base12 = Convert::base40ToMidiNoteNumber(pitch);
   return pow(2.0, (base12 - 60) * scaling);
}



////////////////////////////////
//
// printNotes -- print pitch classes and given root.
//

void printNotes(int correct, Array& pitches) {
   Array<int> pc = pitches;
   int i; 
   for (i=0; i<pc.getSize(); i++) {
      if (pc[i] < 0) {
         continue;
      }
      pc[i] = pc[i] % 40;
   }
   qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);

   char buffer[128] = {0};

   if (locationQ) {
      cout << CurrentFile << "\tline " << CurrentLine << "\t";
   }
   if (transposeQ && singleQ) {
      // resort by length
      for (i=0; i<pc.getSize(); i++) {
         if (pc[i] < 0) {
            continue;
         }
         pc[i] = (pc[i]-correct+2+400)%40;
      }
      qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);
      // remove duplicate pitch classes
      for (i=1; i<pc.getSize(); i++) {
         if (pc[i-1] == pc[i]) {
           pc[i-1] = -1;
         } 
      }
      qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);
 
      cout << "R=" << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pc.getSize(); i++) {
         if (pc[i] < 0) {
            continue;
         }
         cout << Convert::base40ToKern(buffer, pc[i]+4*40);
         if (i<pc.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";

   } else if (transposeQ) {

      // resort by length
      for (i=0; i<pc.getSize(); i++) {
         if (pc[i] < 0) {
            continue;
         }
         pc[i] = (pc[i]-correct+2+400)%40;
      }
      qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);

      cout << "R=" << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pc.getSize(); i++) {
         cout << Convert::base40ToKern(buffer, pc[i]+4*40);
         if (i<pc.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";

   } else {
      cout << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pc.getSize(); i++) {
         cout << Convert::base40ToKern(buffer, (pc[i]%40)+4*40);
         if (i<pc.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";
   }
}


class PDL {
   public:
      int pitch;
      double duration;
      double level;
};

void printNotes(int correct, Array<int>& pitches, 
      Array<double>& durations, Array<double>& levels) {
   Array<int> pc = pitches;
   int i; 
   for (i=0; i<pc.getSize(); i++) {
      if (pc[i] < 0) {
         continue;
      }
      pc[i] = pc[i] % 40;
   }
   Array<PDL> pitchinfo;
   pitchinfo.setSize(pc.getSize());
   for (i=0; i<pc.getSize(); i++) {
      pitchinfo[i].pitch    = pc[i]; 
      pitchinfo[i].duration = durations[i]; 
      pitchinfo[i].level    = levels[i]; 
   }
   // qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);
   qsort(pitchinfo.getBase(), pitchinfo.getSize(), sizeof(PDL), pdlcompare);

   char buffer[128] = {0};

   if (locationQ) {
      cout << CurrentFile << "\tline " << CurrentLine << "\t";
   }
   if (transposeQ && singleQ) {
      // resort by length
      for (i=0; i<pitchinfo.getSize(); i++) {
         if (pitchinfo[i].pitch < 0) {
            continue;
         }
         pitchinfo[i].pitch = (pitchinfo[i].pitch-correct+2+400)%40;
      }
      qsort(pitchinfo.getBase(), pitchinfo.getSize(), sizeof(PDL), pdlcompare);
      // remove duplicate pitch classes
      for (i=1; i<pitchinfo.getSize(); i++) {
         if (pitchinfo[i-1].pitch == pitchinfo[i].pitch) {
           pitchinfo[i-1].pitch = -1;
         } 
      }
      // qsort(pc.getBase(), pc.getSize(), sizeof(int), intcompare);
      qsort(pitchinfo.getBase(), pitchinfo.getSize(), sizeof(PDL), pdlcompare);
 
      cout << "R=" << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pitchinfo.getSize(); i++) {
         if (pitchinfo[i].pitch < 0) {
            continue;
         }
         cout << Convert::base40ToKern(buffer, pitchinfo[i].pitch+4*40);
         if (i<pitchinfo.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";

   } else if (transposeQ) {

      // resort by length
      for (i=0; i<pitchinfo.getSize(); i++) {
         if (pitchinfo[i].pitch < 0) {
            continue;
         }
         pitchinfo[i].pitch = (pitchinfo[i].pitch-correct+2+400)%40;
      }
      qsort(pitchinfo.getBase(), pitchinfo.getSize(), sizeof(PDL), pdlcompare);

      cout << "R=" << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pitchinfo.getSize(); i++) {
         cout << Convert::base40ToKern(buffer, pitchinfo[i].pitch+4*40);
         cout << "(" << pitchinfo[i].duration << ","
              << pitchinfo[i].level << ")";
         if (i<pitchinfo.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";

   } else {
      cout << Convert::base40ToKern(buffer, correct+3*40) << ":\t";
      for (i=0; i<pitchinfo.getSize(); i++) {
         cout << Convert::base40ToKern(buffer, (pitchinfo[i].pitch%40)+4*40);
         cout << "(" << pitchinfo[i].duration << ","
              << pitchinfo[i].level << ")";
         if (i<pitchinfo.getSize()-1) {
            cout << " ";
         }
      }
      cout << "\n";
   }
}



//////////////////////////////
//
// setSortOrder --
//

void setSortOrder(IntervalWeight& dist, Array& index) {
   Array<int> used;
   used.setSize(40);
   used.allowGrowth(0);
   used.setAll(0);

   index.setSize(40);
   index.setAll(10000);
   
   int i, j;
   Array<double> values;
   values.setSize(40);
   for (i=0; i<40; i++) {
      values[i] = dist[i];
   }
   qsort(values.getBase(), values.getSize(), sizeof(double), doublecompare);

   for (i=0; i<35; i++) {
      for (j=0; j<40; j++) {
         if ((values[i] == dist[j]) && (used[j] == 0)) {
            used[j] = 1;
            index[i] = j;
            break;
         }
      }
   }

}



//////////////////////////////
//
// printDistances --
//

void printDistances(IntervalWeight& distances, int type) {
   int i;
   char buffer[64] = {0};
   switch (type) {
      case 1:
         cout << "!! theta: " << theta1 << "\n";
         break;
      case 2:
         cout << "!! theta1: " << theta1 << "\n";
         cout << "!! theta2: " << theta2 << "\n";
         break;
      case 4:
         cout << "!! theta:   " << theta1 << "\n";
         cout << "!! scaling: " << circular << "\n";
         break;
   }
   cout << "**kern\t**weight\n";

   if (sortQ) {
      Array<int> sorter(40);
      setSortOrder(distances, sorter);
      for (i=0; i<distances.getSize(); i++) {
         if (sorter[i] > 40) {
            continue;
         }
         cout << Convert::base40ToKern(buffer, sorter[i]+4*40);
         cout << "\t" << distances[sorter[i]] << "\n";
      }
   } else {
      for (i=0; i<distances.getSize(); i++) {
         if (i==5 || i==11 || i==22 || i==28 || i==34) {
            continue;
         }
         cout << Convert::base40ToKern(buffer, i+4*40);
         cout << "\t" << distances[i] << "\n";
      }
   }

   cout << "*-\t*-\n";

}



//////////////////////////////
//
// printScores --
//

void printScores(Array& rootscores) {
   int i;
   int findex;
   char buffer[64] = {0};
   for (i=0; i<rootscores.getSize(); i++) {
      findex = (17 + i * 23) % 40;
      Convert::base40ToKern(buffer, findex+4*40);
      if (buffer[0] == '\0') {
         continue;
      }
      cout << buffer << "\t" << i << "\t" << findex
           << "\t";
      if (invertQ) {
         cout << 1.0/rootscores[(findex-2+40)%40] << endl;
      } else {
         cout << rootscores[(findex-2+40)%40] << endl;
      }
   }
}



//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b",      "append analysis to data in output");   
   opts.define("i|invert=b",      "invert scores");   
   opts.define("d|distance=b",    "display interval distances");   
   opts.define("t|theta|theta1=d:51.0", "chromatic plane angle");   
   opts.define("u|theta2=d:51.0", "chromatic plane angle 2");   
   opts.define("e|errors=b",      "identify root errors in the input file");   
   opts.define("n|notes=b",       "display note set for each chord");   
   opts.define("l|location=b",    "display file and line for note sets");
   opts.define("C|transpose=b",   "transpose note displays to C root");   
   opts.define("s|single=b",      "note display single pitch class");   
   opts.define("S|sort=b",        "sort display of note by weights");   
   opts.define("c|circular=d:0.1","use circular space");   
   opts.define("p|power=d:1.0",   "power of space");   
   opts.define("f|offset=d:0.0",  "space weights offset");   
   opts.define("o|octave=d:0.0",  "octave scaling");   
   opts.define("w|weight=s",      "interval weight file");   
   opts.define("v|verbose=b",     "display individual scores for each chord");
   opts.define("spectrum=b",      "root spectrum listing");
   opts.define("r|rhythm=b",      "apply rhythm scaling");   
   opts.define("dfactor|durationfactor=d:0.8", "duration scaling factor");   
   opts.define("lfactor|mfactor|metricfactor=d:0.5", "metric scaling factor");

   opts.define("debug=b",         "trace input parsing");   
   opts.define("author=b",        "author of the program");   
   opts.define("version=b",       "compilation information"); 
   opts.define("example=b",       "example usage"); 
   opts.define("h|help=b",        "short description"); 
   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, June 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 18 June 2002" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   debugQ     =  opts.getBoolean("debug");
   errorsQ    =  opts.getBoolean("errors");
   distanceQ  =  opts.getBoolean("distance");
   notesQ     =  opts.getBoolean("notes");
   transposeQ =  opts.getBoolean("transpose");
   singleQ    =  opts.getBoolean("single");
   sortQ      =  opts.getBoolean("sort");
   locationQ  =  opts.getBoolean("location");
   appendQ    =  opts.getBoolean("append");
   theta1     =  opts.getDouble("theta");
   theta2     =  opts.getDouble("theta2");
   invertQ    =  opts.getBoolean("invert");
   circularQ  =  opts.getBoolean("circular");
   circular   =  opts.getDouble("circular");
   power      =  opts.getDouble("power");
   offset     =  opts.getDouble("offset");
   weightQ    =  opts.getBoolean("weight");
   weightFile =  opts.getString("weight");
   verboseQ   =  opts.getBoolean("verbose");
   spectrumQ  =  opts.getBoolean("spectrum");
   octaveQ    =  opts.getBoolean("octave");
   octaveScale=  opts.getDouble("octave");
   rhythmQ    =  opts.getBoolean("rhythm");
   durationfactor = opts.getDouble("durationfactor");
   metricfactor   = opts.getDouble("metricfactor");
}



//////////////////////////////
//
// example -- example usage of the maxent program
//

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// usage -- gives the usage statement for the quality program
//

void usage(const char* command) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// base40ToBase7 -- Convert pitch to diatonic pitch
//   .0 = natural
//   .1 = sharp
//   .2 = double sharp
//   .9 = flat
//   .8 = double flat
//

double base40ToBase7(int pitch) {
   int octave = pitch / 40;
   if (octave < 0) {
      // rest
      return -1;
   }

   switch (pitch % 40) {
      case  0:  return octave * 7 + 0.9;     // C--
      case  1:  return octave * 7 + 0.8;     // C-
      case  2:  return octave * 7 + 0.0;     // C
      case  3:  return octave * 7 + 0.1;     // C#
      case  4:  return octave * 7 + 0.2;     // C##
      case  5:  return -1;                   // X
      case  6:  return octave * 7 + 1.9;     // D--
      case  7:  return octave * 7 + 1.8;     // D-
      case  8:  return octave * 7 + 1.0;     // D
      case  9:  return octave * 7 + 1.1;     // D#
      case 10:  return octave * 7 + 1.2;     // D##
      case 11:  return -1;                   // X
      case 12:  return octave * 7 + 2.9;     // E--
      case 13:  return octave * 7 + 2.8;     // E-
      case 14:  return octave * 7 + 2.0;     // E
      case 15:  return octave * 7 + 2.1;     // E#
      case 16:  return octave * 7 + 2.2;     // E##
      case 17:  return octave * 7 + 3.9;     // F--
      case 18:  return octave * 7 + 3.8;     // F-
      case 19:  return octave * 7 + 3.0;     // F
      case 20:  return octave * 7 + 3.1;     // F#
      case 21:  return octave * 7 + 3.2;     // F##
      case 22:  return -1;                   // X
      case 23:  return octave * 7 + 4.9;     // G--
      case 24:  return octave * 7 + 4.8;     // G-
      case 25:  return octave * 7 + 4.0;     // G
      case 26:  return octave * 7 + 4.1;     // G#
      case 27:  return octave * 7 + 4.2;     // G##
      case 28:  return -1;                   // X
      case 29:  return octave * 7 + 5.9;     // A--
      case 30:  return octave * 7 + 5.8;     // A-
      case 31:  return octave * 7 + 5.0;     // A
      case 32:  return octave * 7 + 5.1;     // A#
      case 33:  return octave * 7 + 5.2;     // A##
      case 34:  return -1;                   // X
      case 35:  return octave * 7 + 6.9;     // B--
      case 36:  return octave * 7 + 6.8;     // B-
      case 37:  return octave * 7 + 6.0;     // B
      case 38:  return octave * 7 + 6.1;     // B#
      case 39:  return octave * 7 + 6.2;     // B##
   }

   return -1;

}



//////////////////////////////
//
// base7pcToName -- base 7 pitch class name: C D E F G A B
//

char base7pcToName(int value) {
   value = (value + 70) % 7;
   switch (value) {
      case 0:  return 'C';
      case 1:  return 'D';
      case 2:  return 'E';
      case 3:  return 'F';
      case 4:  return 'G';
      case 5:  return 'A';
      case 6:  return 'B';
      default: return 'X';
   }
   return 'X';
}



//////////////////////////////
//
// intcompare -- compare two integers for ordering
//

int intcompare(const void* a, const void* b) {
   if (*((int*)a) < *((int*)b)) {
      return -1;
   } else if (*((int*)a) > *((int*)b)) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// pdlcompare -- compare two pitches for ordering in PDL structure
//

int pdlcompare(const void* a, const void* b) {
   PDL& A = *((PDL*)a);
   PDL& B = *((PDL*)b);

   if (A.pitch < B.pitch) {
      return -1;
   } else if (A.pitch > B.pitch) {
      return +1;
   } 

   // pitches are the same, so sort by duration
   if (A.duration < B.duration) {
      return -1;
   } else if (A.duration > B.duration) {
      return +1;
   }

   // durations are the same, so sort by metric level
   if (A.level < B.level) {
      return -1;
   } else if (A.level > B.level) {
      return +1;
   } 

   // notes are identical
   return 0;

}



//////////////////////////////
//
// doublecompare -- compare two doubles for ordering
//

int doublecompare(const void* a, const void* b) {
   if (*((double*)a) < *((double*)b)) {
      return -1;
   } else if (*((double*)a) > *((double*)b)) {
      return 1;
   } else {
      return 0;
   }
}



//////////////////////////////
//
// getStartingWeights --
//

void getStartingWeights(IntervalWeight& weights, HumdrumFile& weightfile) {
   weights.setAll(100000);
   int i, j;
   int root;
   double weight;
   for (i=0; i<weightfile.getNumLines(); i++) {
      root = -1;
      weight = 10000;
      if (!weightfile[i].isData()) {
         continue;
      }
      for (j=0; j<weightfile[i].getFieldCount(); j++) {
         if (strcmp("**kern", weightfile[i].getExInterp(j)) == 0) {
            root = Convert::kernToBase40(weightfile[i][j]) % 40;
         } else if (strcmp("**weight", weightfile[i].getExInterp(j)) == 0) {
            weight = strtod(weightfile[i][j], NULL);
         }
      }
      if (root >= 0) {
         weights[root] = weight;
      }
   }

}
// md5sum: 1c40094e93425238fda57be67493bd5a iwroot.cpp [20050403]