//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Mar 10 11:55:09 PST 2001
// Last Modified: Thu Jan 31 18:12:02 PST 2002 (minor adjustments, improve key)
// Filename:      ...sig/doc/examples/sig/sigControl/kernin.cpp
// Syntax:        C++; improv 2.2
//

#include "improv.h"
#include "Convert.h"
#include "LineDisplay.h"
#include <string.h>
#include <math.h>
#include <ctype.h>

// command-line options variables:
Options     options;           // for command-line processing
const char* filename = NULL;   // file name to write data to
fstream     outfile;           // for writing output data
int         transpose = 0;     // -t option for transposition

// MIDI keyboard variables:
MidiIO midi;                  // MIDI I/O interface
int keystates[128] = {0};     // 0 = note off, 1 = note on
int keydurations[128] = {0};  // duration from last note state
int keyaction[128] = {0};     // time the note was turned on/off
int keylastaction[128] = {0}; // last time the note was turned on/off
double stepDur = 1.0;         // step record duration
double currDur = 1.0;         // duration of the current note
int octave = 0;               // octave adjustment
int controlOctave = -1;       // placement of the control octave
int keyoncount = 0;           // number of data keys being pressed
int chordkeys[128] = {0};     // for displaying chord keys
int currentKey = 0;

// Computer keyboard variables:
KeyboardInput keyboard;          // computer keyboard I/O interface
CircularBuffer<int> attackkeys;  // for control key attack numbers
CircularBuffer<int> attacktimes; // for control key attack times
CircularBuffer<int> durations;   // for control key attack times
CircularBuffer<int> iois;        // for inter-onset time intervals
Array<int> actiontimes;          // when note was last turned on/off
double speed = 70.0;             // shift keys Morse speed
int interpreted = 0;             // for interpreting shift keys

// screen display variables:
LineDisplay display;            // screen display object
char displayBuffer[1024] = {0}; // display buffer
int displayIndex = 0;           // how many characters are being displayed
int initData = 0;               // for adding **kern/*- symbols to the datadouble meterduration = 0;       // for auto display of measure lines
double maxmeterduration = 4.0;  // for auto display of measure lines
int measurenumber = 1;          // for display of measure number

// function declarations
void  checkOptions(Options& opts);
void  example(void);
void  usage(const char* command);
void  processComputerKeyboardInput(void);
void  processMidiKeyboardInput(void);
void  processNoteKey(int key, int vel, int mtime);
void  processControlKey(int key, int vel, int mtime);
int   generateCommand(int key, int vel, int mtime);
void  statusMessage(const char* string);
void  storeCommandEvent(int key, int vel, int mtime);
char  interpretShiftEvent(void);
char  convertShiftEvent(const char* string);
int   generateShiftCommand(int shiftkey, int event, int controlkey, 
                                          int vel);
void  executeCommand(int command, int p1 = 0);
void  checkInitialization(void);
int   convertMIDIToBase40(int note, int key = 0);


// Command enumerations:
#define C_IGNORE                      -1
#define C_UNKNOWN                      0
#define C_SETINCREMENTWHOLENOTE        1
#define C_SETINCREMENTHALFNOTE         2
#define C_SETINCREMENTQUARTERNOTE      3
#define C_SETINCREMENTEIGHTHNOTE       4
#define C_SETINCREMENTSIXTEENTHNOTE    5
#define C_SETINCREMENT32NDNOTE         6
#define C_KEYSIGCMAJOR                 7
#define C_KEYSIGFMAJOR                 8
#define C_KEYSIGBFLATMAJOR             9
#define C_KEYSIGEFLATMAJOR             10
#define C_KEYSIGAFLATMAJOR             11
#define C_KEYSIGDFLATMAJOR             12
#define C_KEYSIGGFLATMAJOR             13
#define C_KEYSIGCFLATMAJOR             14
#define C_KEYSIGGMAJOR                 15
#define C_KEYSIGDMAJOR                 16
#define C_KEYSIGAMAJOR                 17
#define C_KEYSIGEMAJOR                 18
#define C_KEYSIGBMAJOR                 19
#define C_KEYSIGFSHARPMAJOR            20
#define C_KEYSIGCSHARPMAJOR            21
#define C_WRITEMEASURE                 22
#define C_REDISPLAYCURRENTNOTES        23
#define C_UPDATECURRENTDURATION        24
#define C_DISPLAYNEWNOTE               25
#define C_WRITEORPARSECURRNETLINE      26
#define C_WRITEREST                    27
#define C_ENHARMONIC                   28
#define C_ENHARMONICDOUBLEFLAT         29
#define C_ENHARMONICDOUBLESHARP        30

// MIDI keyboard defines:
// rhythm:
#define K_SETINCREMENTWHOLENOTE        0
#define K_SETINCREMENTHALFNOTE         2
#define K_SETINCREMENTQUARTERNOTE      4
#define K_SETINCREMENTEIGHTHNOTE       5
#define K_SETINCREMENTSIXTEENTHNOTE    7
#define K_SETINCREMENT32NDNOTE         9

#define K_REST                         11
#define K_MEASURE                      12
#define K_ENHARMONIC                   1

// numbers
#define K_NUMBER0                      8
#define K_NUMBER1                      0
#define K_NUMBER2                      2
#define K_NUMBER3                      4
#define K_NUMBER4                      5
#define K_NUMBER5                      7
#define K_NUMBER6                      9
#define K_NUMBER7                      11
#define K_NUMBER8                      12
#define K_NUMBER9                      10
// shift keys
#define K_SHIFT1                       3
#define K_SHIFT2                       6

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

int main(int argc, char** argv) {
   options.setOptions(argc, argv);
   checkOptions(options);

   attacktimes.setSize(1000);
   attackkeys.setSize(1000);
   durations.setSize(1000);
   iois.setSize(1000);
   attacktimes.reset();
   attackkeys.reset();
   durations.reset();
   iois.reset();
   actiontimes.setSize(128);
   actiontimes.zero();
   actiontimes.allowGrowth(0);

   statusMessage(">>> Press ESC to finish data entry.");
   statusMessage(">>> Press a MIDI key to place the control Octave.");
   while (1) {
      while (midi.getCount() > 0) {
         processMidiKeyboardInput();
      }

      if (keyboard.hit()) {
         processComputerKeyboardInput();
      }

      millisleep(1);   
   }

   return 0;
}


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

//////////////////////////////
//
// statusMessage -- display a status message, then redisplay the 
//    contents of the data line;
//

void statusMessage(const char* string) {
   static char oldstring[1024] = {0};
   strcpy(oldstring, display.getLine());
   display.setLine(string);
   display.newline();
   display.display(oldstring);
}



//////////////////////////////
//
// echoMessage -- echo MIDI input to MIDI output
//

void echoMessage(MidiMessage message) {
   int count = message.getArgCount() + 1;
   unsigned char array[4];
   array[0] = message.p0();
   array[1] = message.p1();
   array[2] = message.p2();
   array[3] = message.p3();
   midi.rawsend(array, count);
}



//////////////////////////////
//
// processMidiKeyboardInput -- interpret messages from the MIDI
//      keyboard.
//

void processMidiKeyboardInput(void) {
   MidiMessage message = midi.extract();

   int key = 0;
   int vel = 0;
   if ((message.p0() & 0xf0) == 0x90 || (message.p0() & 0xf0) == 0x80) {
      key = message.p1();
      vel = message.p2();
      if ((message.p0() & 0xf0) == 0x80) {
         vel = 0;
      }
   } else {
      echoMessage(message);
      return;
   }

   // set the control octave if it is not yet set
   if (vel != 0 && controlOctave < 0) {
      return;
   }
   if (vel == 0 && controlOctave < 0) {
      controlOctave = key / 12;
      cout << ">>> Control octave set to " << controlOctave << endl;
      return;
   }

   // determine if the key is in the control octave or not
   int control = 0;
   if (key >= controlOctave * 12 && key <= controlOctave * 12 + 12) {
      control = 1;
   } else {
      control = 0;
   }

   if (control) {
      processControlKey(key, vel, message.time);
   } else {

      if (vel == 0) {
         keystates[key] = 0; 
         keyoncount--;
         if (keyoncount < 0) {
            keyoncount = 0;
         }
      } else {
         keystates[key] = 1;
         keyoncount++;
      }

      echoMessage(message);
      processNoteKey(key, vel, message.time);
   }
}



//////////////////////////////
//
// processNoteKey -- handle a data entry note message
//

void processNoteKey(int key, int vel, int mtime) {
   int i;
   if (vel == 0 && keyoncount == 0) {
      for (i=0; i<128; i++) {
         chordkeys[i] = 0;
      }
   }
   if (keyoncount != 0) {
      chordkeys[key] = 1;
   }

   checkInitialization();
   keylastaction[key] = keyaction[key];
   keyaction[key] = mtime;
   keydurations[key] = keyaction[key] - keylastaction[key];

   if (keyoncount == 1 && vel != 0) {
      executeCommand(C_DISPLAYNEWNOTE, key);
   } else if (keyoncount > 1 && vel != 0) {
      executeCommand(C_REDISPLAYCURRENTNOTES);
   }
   currDur = stepDur;

}



//////////////////////////////
//
// processControlKey --
//

void processControlKey(int key, int vel, int mtime) {
   storeCommandEvent(key, vel, mtime);
   
   int command = generateCommand(key, vel, mtime);
   executeCommand(command);
}


//////////////////////////////
//
// executeCommand --
//

void executeCommand(int command, int p1) {
   switch(command) {
      case C_SETINCREMENTWHOLENOTE:
         statusMessage(">>> Setting step duration to whole note");
         stepDur = 4.0;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_SETINCREMENTHALFNOTE:
         statusMessage(">>> Setting step duration to half note");
         stepDur = 2.0;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_SETINCREMENTQUARTERNOTE:
         statusMessage(">>> Setting step duration to quarter note");
         stepDur = 1.0;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_SETINCREMENTEIGHTHNOTE:
         statusMessage(">>> Setting step duration to eighth note");
         stepDur = 0.5;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_SETINCREMENTSIXTEENTHNOTE:
         statusMessage(">>> Setting step duration to sixteenth note");
         stepDur = 0.25;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_SETINCREMENT32NDNOTE:
         statusMessage(">>> Setting step duration to 32nd note");
         stepDur = 0.125;
         if (keyoncount == 0) {
            currDur = stepDur;
         }
         break;
      case C_UNKNOWN:
         statusMessage(">>> Unknown command");
         break;

      case C_WRITEMEASURE:
         if (strncmp(display.getLine(), "!", 1) == 0 || 
               strncmp(display.getLine(), "*", 1) == 0) {
            display.display("=");
         } else {
            if (strcmp(display.getLine(), "") != 0) {
               executeCommand(C_WRITEORPARSECURRNETLINE);
               display.display("=");
            }
         }
         break;
      case C_KEYSIGCMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[]");
         currentKey = 0;
         break;
      case C_KEYSIGFMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-]");
         currentKey = -1;
         break;
      case C_KEYSIGBFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-]");
         currentKey = -2;
         break;
      case C_KEYSIGEFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-a-]");
         currentKey = -3;
         break;
      case C_KEYSIGAFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-a-d-]");
         currentKey = -4;
         break;
      case C_KEYSIGDFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-a-d-g-]");
         currentKey = -5;
         break;
      case C_KEYSIGGFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-a-d-g-c-]");
         currentKey = -6;
         break;
      case C_KEYSIGCFLATMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[b-e-a-d-g-c-f-]");
         currentKey = -7;
         break;
      case C_KEYSIGGMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#]");
         currentKey = +1;
         break;
      case C_KEYSIGDMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#]");
         currentKey = +2;
         break;
      case C_KEYSIGAMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#g#]");
         currentKey = +3;
         break;
      case C_KEYSIGEMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#g#d#]");
         currentKey = +4;
         break;
      case C_KEYSIGBMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#g#d#a#]");
         currentKey = +5;
         break;
      case C_KEYSIGFSHARPMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#g#d#a#e#]");
         currentKey = +6;
         break;
      case C_KEYSIGCSHARPMAJOR:
         checkInitialization();
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("*k[f#c#g#d#a#e#b#]");
         currentKey = +7;
         break;

      case C_UPDATECURRENTDURATION:
         // add current step increment to current duration and redisplay
         currDur += stepDur;
         meterduration += stepDur;
         executeCommand(C_REDISPLAYCURRENTNOTES);
         break;

      case C_REDISPLAYCURRENTNOTES:
         {
         static char buffer1[1024] = {0};
         display.eraseline();
         int count = 0;
         for (int i=0; i<128; i++) {
            if (chordkeys[i] != 0) {
               if (count != 0) {
                  display.display(" ");
               }
               display.display(Convert::durationToKernRhythm(buffer1, currDur));
               display.display(Convert::base12ToKern(buffer1, i));
               count++;
            }
         }
         }
         break;

      case C_WRITEREST:
         {
         static char buffer1[1024] = {0};

         if (strcmp("", display.getLine()) != 0) {
            executeCommand(C_WRITEORPARSECURRNETLINE);
            meterduration += currDur;
            if (fabs(meterduration - maxmeterduration) < 0.01) {
               // cout << "Meter duration: " << meterduration
               //      << "\tmaxmeterdur:  " << maxmeterduration << endl;
               display.display("=");
               measurenumber++;            // statically fixed for now
               sprintf(buffer1, "%d", measurenumber);
               display.display(buffer1);
               executeCommand(C_WRITEORPARSECURRNETLINE);
               meterduration = 0.0;
            } else if (meterduration - maxmeterduration > 0) {
               display.display("=");
               measurenumber++;            // statically fixed for now
               sprintf(buffer1, "%d", measurenumber);
               display.display(buffer1);
               executeCommand(C_WRITEORPARSECURRNETLINE);
               meterduration -= maxmeterduration;
            }
         }
         display.display(Convert::durationToKernRhythm(buffer1, currDur));
         display.display("r");
         }
         break;

      case C_DISPLAYNEWNOTE:
         {
         static char buffer1[1024] = {0};

         if (strcmp("", display.getLine()) != 0) {
            executeCommand(C_WRITEORPARSECURRNETLINE);
            meterduration += currDur;
            if (fabs(meterduration - maxmeterduration) < 0.01) {
               // cout << "Meter duration: " << meterduration
               //      << "\tmaxmeterdur:  " << maxmeterduration << endl;
               display.display("=");
               measurenumber++;            // statically fixed for now
               sprintf(buffer1, "%d", measurenumber);
               display.display(buffer1);
               executeCommand(C_WRITEORPARSECURRNETLINE);
               meterduration = 0.0;
            } else if (meterduration - maxmeterduration > 0) {
               display.display("=");
               measurenumber++;            // statically fixed for now
               sprintf(buffer1, "%d", measurenumber);
               display.display(buffer1);
               executeCommand(C_WRITEORPARSECURRNETLINE);
               meterduration -= maxmeterduration;
            }
         }
         display.display(Convert::durationToKernRhythm(buffer1, currDur));
         p1 = convertMIDIToBase40(p1 + transpose, currentKey);
         display.display(Convert::base40ToKern(buffer1, p1));
         }
         break;
      case C_WRITEORPARSECURRNETLINE:
         {
         static char buffer1[1024] = {0};
         strncpy(buffer1, display.getLine(), 512);

         if (strcmp(buffer1, "") == 0) {
            // ignore line update if a blank line
            return;
         }

         // check for a meter signature
         if (strncmp(buffer1, "*M", 2) == 0 && isdigit(buffer1[2])) {
            int top = 0;
            int bottom=0;
            sscanf(buffer1, "*M%d/%d", &top, &bottom);
            maxmeterduration = top * 4.0/bottom;
            statusMessage(">>> Meter signature read");
         }

         // check for a measure line
         if (strncmp(buffer1, "=", 1) == 0 && isdigit(buffer1[1])) {
            int mno = 0;
            sscanf(buffer1, "=%d", &mno);
            if (measurenumber != mno) {
               measurenumber = mno;
               statusMessage(">>> Changing measure numbering");
            }
            meterduration = 0.0;
         }

         if (strncmp(">>>", display.getLine(), 3) != 0) {
            // don't save command directives from the user
            outfile << "\n" << display.getLine();
         } else {
            double newbeat = 0.0;
            if (sscanf(display.getLine(), ">> beat %lf", &newbeat)) {
               stepDur = 4.0/newbeat;
               currDur = stepDur;
               statusMessage(">>> Beat increment set");
            } else if (sscanf(display.getLine(), ">>> beat %lf", &newbeat)) {
               stepDur = 4.0/newbeat;
               currDur = stepDur;
               statusMessage(">>> Beat increment set");
            }
         }
         display.newline();

         }

      default:
         break;
   }

}



///////////////////////////////
//
// checkInitialization --
//

void checkInitialization(void) {
   if (initData == 0) {
      if (strcmp("", display.getLine()) == 0) {
         display.display("**kern");
         outfile << "!! data";
      } else {
         executeCommand(C_WRITEORPARSECURRNETLINE);
         display.display("**kern");
      }
      initData =1;
   }
}



//////////////////////////////
//
// checkForShiftKey -- determines if a shift key has been pressed
//

int checkForShiftKey(void) {
   if (attackkeys[0] != K_SHIFT1 && attackkeys[1] == K_SHIFT1) {
      return 1;
   }
   if (attackkeys[0] != K_SHIFT2 && attackkeys[1] == K_SHIFT2) {
      return 2;
   }

   return 0;
}



//////////////////////////////
//
// generateCommand -- figure out what command to do.
//

int generateCommand(int key, int vel, int mtime) {
   if (vel == 0) {
      return C_IGNORE;
   }

   int controlkey = key - controlOctave * 12;

   int shiftkey = checkForShiftKey();

   if (shiftkey) {
      return generateShiftCommand(shiftkey, interpretShiftEvent(), 
            controlkey, vel);
   }

   // process update to the durations of current notes
   if (vel > 0 && keyoncount > 0) {
      return C_UPDATECURRENTDURATION; 
   }

   // process changes in the step duration
   if (vel > 0) {
      switch (controlkey) {
         case K_SETINCREMENTWHOLENOTE:
            return C_SETINCREMENTWHOLENOTE;
            break;
         case K_SETINCREMENTHALFNOTE:
            return C_SETINCREMENTHALFNOTE;
            break;
         case K_SETINCREMENTQUARTERNOTE:
            return C_SETINCREMENTQUARTERNOTE;
            break;
         case K_SETINCREMENTEIGHTHNOTE:
            return C_SETINCREMENTEIGHTHNOTE;
            break;
         case K_SETINCREMENTSIXTEENTHNOTE:
            return C_SETINCREMENTSIXTEENTHNOTE;
            break;
         case K_SETINCREMENT32NDNOTE:
            return C_SETINCREMENT32NDNOTE;
            break;
         case K_MEASURE:
            return C_WRITEMEASURE;
            break;
         case K_REST:
            return C_WRITEREST;
            break;

         default:
            return C_IGNORE;
      }
   }

   return C_IGNORE;
}



//////////////////////////////
//
// generateShiftCommand --
//

int generateShiftCommand(int shiftkey, int event, int controlkey, int vel) {

   if (event == 'K' && shiftkey == 1) {
      switch (controlkey) {
         case K_ENHARMONIC: return C_ENHARMONICDOUBLEFLAT;
         case K_NUMBER0: return C_KEYSIGCMAJOR;
         case K_NUMBER1: return C_KEYSIGFMAJOR;
         case K_NUMBER2: return C_KEYSIGBFLATMAJOR;
         case K_NUMBER3: return C_KEYSIGEFLATMAJOR;
         case K_NUMBER4: return C_KEYSIGAFLATMAJOR;
         case K_NUMBER5: return C_KEYSIGDFLATMAJOR;
         case K_NUMBER6: return C_KEYSIGGFLATMAJOR;
         case K_NUMBER7: return C_KEYSIGCFLATMAJOR;
      }
   }

   if (event == 'K' && shiftkey == 2) {
      switch (controlkey) {
         case K_ENHARMONIC: return C_ENHARMONICDOUBLESHARP;
         case K_NUMBER0: return C_KEYSIGCMAJOR;
         case K_NUMBER1: return C_KEYSIGGMAJOR;
         case K_NUMBER2: return C_KEYSIGDMAJOR;
         case K_NUMBER3: return C_KEYSIGAMAJOR;
         case K_NUMBER4: return C_KEYSIGEMAJOR;
         case K_NUMBER5: return C_KEYSIGBMAJOR;
         case K_NUMBER6: return C_KEYSIGFSHARPMAJOR;
         case K_NUMBER7: return C_KEYSIGCSHARPMAJOR;
      }
   }
 
   return C_UNKNOWN;
}



//////////////////////////////
//
// processComputerKeyboardInput --
//

void processComputerKeyboardInput(void) {
   int keych = keyboard.getch();
   switch (keych) {
      case 27:                   // escape key 
         if (initData == 1) {
            if (strcmp("", display.getLine()) != 0) {
               executeCommand(C_WRITEORPARSECURRNETLINE);
               display.display("*-");
               executeCommand(C_WRITEORPARSECURRNETLINE);
               outfile << endl;
            } else {
               display.display("*-");
               executeCommand(C_WRITEORPARSECURRNETLINE);
               outfile << endl;
            }
            outfile.close();
            initData = 0;
         }
         keyboard.deinitialize();
         exit(0);      
         break;
      case 0x08:                 // backspace key
      case 0x7f:                 // delete key
         display.backspace();
         break;
      case 0x0a:                 // enter key only
         executeCommand(C_WRITEORPARSECURRNETLINE);
         break;
      case 13:                   // line feed
         break;
      case 0x0c:                 // ^L key (redraw input)
         break;
      case '=':                  // measure line key (probably)
         executeCommand(C_WRITEMEASURE);
         meterduration = -currDur;
         break;
      default:                   // normal key
         { 
            char tempbuffer[2] = {0};
            tempbuffer[0] = (char)keych;
            display.display(tempbuffer);
         }
   }
}



//////////////////////////////
//
// checkOptions -- process the command-line options
//

void checkOptions(Options& opts) {  
   opts.define("inport=i:0",   "MIDI input port to use");
   opts.define("outport=i:0",  "MIDI output port to use");
   opts.define("t|transpose=i:0",  "MIDI transposition to use");

   opts.define("author=b");
   opts.define("version=b");
   opts.define("example=b");
   opts.define("h|help=b");
   opts.process();

   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
              "craig@ccrma.stanford.edu, March 2001" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << "kernin, version 1.0 (10 March 2001)\n"
              "compiled: " << __DATE__ << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   midi.setInputPort(opts.getInteger("inport"));
   midi.setOutputPort(opts.getInteger("outport"));

   transpose = opts.getInteger("transpose");

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

   filename = opts.getArg(1);
   cout << ">>> Saving data to: " << filename << endl;
   #ifndef OLDCPP
      outfile.open(filename, ios::out);
   #else
      outfile.open(filename, ios::out | ios::noreplace); 
   #endif
   if (!outfile.is_open()) {
      cout << "Error: cannot open file for writing." << endl;
      cout << "Perhaps it already exists?" << endl;
      exit(1);
   }
}



//////////////////////////////
//
// example -- shows various command-line option calls to program.
//

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



//////////////////////////////
//
// usage -- how to run this program from the command-line.
//

void usage(const char* command) {
   cout << 
   "\n"
   "Data entry for Humdrum **kern.\n"
   "\n"
   "Usage: " << command << " [-i num][-o num] output-file\n"
   "\n"
   "Options:\n"
   "   -i port = use the specified MIDI input port\n"
   "   -o port = use the specified MIDI output port\n"
   "   --options = list all options, default values, and aliases\n"
   "\n"
   << endl;
}


//////////////////////////////
//
// storeCommandEvent --
//

void storeCommandEvent(int key, int vel, int mtime) {
   if (vel > 0 && attacktimes.getSize() > 0) {
      iois.insert(mtime - attacktimes[0]);
   }

   if (vel > 0) {
      attacktimes.insert(mtime);
      attackkeys.insert(key - controlOctave * 12);
   } else {
      durations.insert(mtime - actiontimes[key]);
      actiontimes[key] = mtime;
   }
   interpreted = 0;
}


//////////////////////////////
//
// interpretShiftEvent --
//

char interpretShiftEvent(void) {
   static char buffer[1024] = {0};
   int eventcount = 1;
   int ioicount = iois.getCount();
   int i = 0;
   while (i < ioicount && i < iois.getCount()) {
      if (iois[i] < 6 * speed) {
         eventcount++;
      } else {
         break;
      }
      i++;
   }

   // cout << "Number of events = " << eventcount << endl;

   if (eventcount < 1 || eventcount > 100) {
      // nothing to do
      interpreted = 1;
      buffer[0] = '\0';
      return convertShiftEvent(buffer);
   }
   
   // event count keeps track of how many events occurred, now
   // now identify each event as a dot or a dash.
   buffer[0] = '\0';
   for (i=eventcount; i>0; i--) {
      if (i-1 < iois.getSize() && iois[i-1] < speed * 3) {
         strcat(buffer, ".");
      } else {
         strcat(buffer, "-");
      }
   }

   if (durations.getCount() == 0) {
      // cout << "Duration size is zero" << endl;
      return convertShiftEvent(buffer);
   } else {
      // cout << "Duration = " << durations[0] << endl;
   }

   return convertShiftEvent(buffer);
}



//////////////////////////////
//
// convertShiftEvent --
//

char convertShiftEvent(const char* string) {
   if (strcmp(string, ".-"  ) == 0)   return 'A';
   if (strcmp(string, "-...") == 0)   return 'B';
   if (strcmp(string, "-.-.") == 0)   return 'C';
   if (strcmp(string, "-.." ) == 0)   return 'D';
   if (strcmp(string, "."   ) == 0)   return 'E';
   if (strcmp(string, "..-.") == 0)   return 'F';
   if (strcmp(string, "--." ) == 0)   return 'G';
   if (strcmp(string, "....") == 0)   return 'H';
   if (strcmp(string, ".."  ) == 0)   return 'I';
   if (strcmp(string, ".---") == 0)   return 'J';
   if (strcmp(string, "-.-" ) == 0)   return 'K';
   if (strcmp(string, ".-..") == 0)   return 'L';
   if (strcmp(string, "--"  ) == 0)   return 'M';
   if (strcmp(string, "-."  ) == 0)   return 'N';
   if (strcmp(string, "---" ) == 0)   return 'O';
   if (strcmp(string, ".--.") == 0)   return 'P';
   if (strcmp(string, "--.-") == 0)   return 'Q';
   if (strcmp(string, ".-." ) == 0)   return 'R';
   if (strcmp(string, "..." ) == 0)   return 'S';
   if (strcmp(string, "-"   ) == 0)   return 'T';
   if (strcmp(string, "..-" ) == 0)   return 'U';
   if (strcmp(string, "...-") == 0)   return 'V';
   if (strcmp(string, ".--" ) == 0)   return 'W';
   if (strcmp(string, "-..-") == 0)   return 'X';
   if (strcmp(string, "-.--") == 0)   return 'Y';
   if (strcmp(string, "--..") == 0)   return 'Z';

   return '\0';
}


//////////////////////////////
//
//  convertMIDIToBase40 -- print the most likely spelling for the note
//     when in the given key.  Optimized for Major keys only.
//     Default value: key = 0;
//

int convertMIDIToBase40(int note, int key) {
   if (key > 7) key  = 7;
   if (key < -7) key = -7;
   int base[12] = {0, 1, 6, 11, 12, 17, 18, 23, 24, 29, 34, 35};
   int keyt[15] = {-1, 22, 5, 28, 11, 34, 17, 0, 23, 6, 29, 12, 35, 18, 1};
   int keym[15] = {-1, 6, 1, 8, 3, 10, 5, 0, 7, 2, 9, 4, 11, 6, 1};
   int pc       = (note - keym[key + 7]) % 12;
   int octave   = (note - keym[key + 7]) / 12 - 1;
   int output   = base[pc] + keyt[key + 7] + 40 * octave + 2;
   return output;
}



// md5sum: e4c48e8a621b9d1f810303b17908dcaa kernin.cpp [20050403]