//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Jun 18 12:14:03 PDT 2002
// Last Modified: Tue Jun 18 12:14:06 PDT 2002
// Filename:      ...sig/examples/all/perfid.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/midi/perfid.cpp
// Syntax:        C++; museinfo
//
// Description:   Determine if a MIDI file is a live performance or if
//                it is step edit.
//

#include "MidiFile.h"
#include "Options.h"

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

// user interface variables:
Options options;
int     track    = -1;         // track to extract from (starting from 0)
int     debugQ   = 0;          // use with --debug option
int     maxcount = 100000;     // maximum number of notes expected
int     rawQ     = 0;          // display raw data used to determine id
int     cutoff   = 1000000;    // maximum duration to consider
int     fileQ    = 0;          // print file name before id

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      getNoteOnDeltas(Array<int>& noteondeltas, 
                                 MidiFile& midifile);
void      addNoteOnEvents(Array<int>& noteondeltas, MidiFile& midifile,
                                 int track);
void      usage(const char* command);
int       intcompare(const void* a, const void* b);
void      sortArray(Array<int>& noteondeltas);
void      printDeltas(Array<int>& noteondeltas);
void      printID(Array<int>& noteondeltas);

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

int main(int argc, char* argv[]) {
   checkOptions(options, argc, argv);
   MidiFile midifile;
   midifile.read(options.getArg(1));
   midifile.absoluteTime();
   Array<int> noteondeltas;
   noteondeltas.setSize(maxcount);
   noteondeltas.setSize(0);
   midifile.joinTracks();
   getNoteOnDeltas(noteondeltas, midifile);
   if (rawQ) {
      cout << "// ";
      printID(noteondeltas);
      printDeltas(noteondeltas);
   } else {
      printID(noteondeltas);
   }
   return 0;
}

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


//////////////////////////////
//
// printDeltas --
//

void printDeltas(Array& noteondeltas) {
   int i;
   int count = 1;
   if (noteondeltas.getSize() == 0) {
      return;
   }
   for (i=1; i<noteondeltas.getSize(); i++) {
      if (noteondeltas[i] != noteondeltas[i-1]) {
         cout << count << "\t" << noteondeltas[i-1] << "\n";
         count = 1;
      } else {
         count++;
      }
   }
   cout << count << "\t" << noteondeltas.last() << "\n";
}



//////////////////////////////
//
// printID --
//

void printID(Array& noteondeltas) {
   if (fileQ) {
      cout << options.getArg(1) << "\t";
   }
   int i;
   int count = 1;
   if (noteondeltas.getSize() == 0) {
      cout << "Empty" << endl;
      return;
   }
   Array<int> deltas;
   Array<int> hist;
   deltas.setSize(noteondeltas.getSize());
   hist.setSize(noteondeltas.getSize());
   deltas.setSize(0);
   hist.setSize(0);
   for (i=1; i<noteondeltas.getSize(); i++) {
      if (noteondeltas[i] != noteondeltas[i-1]) {
         deltas.append(noteondeltas[i-1]);
         hist.append(count);
         count = 1;
      } else {
         count++;
      }
   }
   deltas.append(noteondeltas.last());
   hist.append(count);
   int size = deltas.getSize();
   if (size > 2) {
      if (deltas[0] == 0 && hist[0] > 10 && deltas[1] >= 10) {
         cout << "Quantized" << endl;
         return;
      }
   }

   if (size > 3 && deltas[5] < 10) {
      if (hist[0] < hist[1] + hist[2] + hist[3] + hist[4]) {
         cout << "Performance" << endl;
         return;
      } else {
         cout << "Quantized" << endl;
         return;
      }
   }

   cout << "Unknown" << endl;
}



//////////////////////////////
//
// getNoteOnDeltas --
//

void getNoteOnDeltas(Array& noteondeltas, MidiFile& midifile) {
   int i;
   for (i=0; i<midifile.getNumTracks(); i++) {
      addNoteOnEvents(noteondeltas, midifile, i);
   }

   // sort note ons here.
   sortArray(noteondeltas);
}


//////////////////////////////
//
// sortArray --
//

void sortArray(Array& noteondeltas) {
   qsort(noteondeltas.getBase(), noteondeltas.getSize(), sizeof(int), intcompare);

}



//////////////////////////////
//
// 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;
   }
}



//////////////////////////////
//
// addNoteOnEvents -- get the Note-On delta times from the specified
//                    track.
//

void addNoteOnEvents(Array<int>& noteondeltas, MidiFile& midifile,
      int track) {
   int i;
   int lasttime = -1;
   MFEvent event;
   int delta = 0;
   
   for (i=0; i<midifile.getNumEvents(track); i++) {
      event = midifile.getEvent(track, i);
      if ((event.data[0] & 0xf0) == 0x90) {
         if (event.data[2] > 0) {
            if (lasttime < 0) {
               lasttime = event.time;
            } else {
               delta = event.time - lasttime;
               if (delta < cutoff) {
                  noteondeltas.append(delta);
               }
               lasttime = event.time;
            }
         }
      }
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("h|help=b",  "short description");

   opts.define("t|track=i:-1", "which track to extract");
   opts.define("r|raw=b",      "print noteon deltas");
   opts.define("f|file=b",      "display filename");
   opts.define("max=i:100000", "maximum number of notes expected in input");
   opts.define("debug=b",  "debug mode to find errors in input file");

   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, 18 Jun 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 18 Jun 2002" << endl;
      cout << "compiled: " << __DATE__ << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   track    = opts.getInteger("track");
   debugQ   = opts.getBoolean("debug");
   maxcount = opts.getInteger("max"); 
   rawQ     = opts.getBoolean("raw");
   fileQ    = opts.getBoolean("file");

   if (opts.getArgCount() != 1) {
      usage(opts.getCommand());
      exit(1);
   }
}



//////////////////////////////
//
// example -- give example calls to the program.
//

void example(void) {
}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {
}



// md5sum: 21297c7be498dccf4d2c2d74f0065259 perfid.cpp [20050403]