//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Feb 18 10:12:41 PST 1999
// Last Modified: Thu Jul  8 15:12:29 PDT 1999
// Filename:      ...sig/doc/examples/all/perform/perform.cpp
// Syntax:        C++; Improv 2.1
//
// Description:   The original header from the C program is in the comment 
//                below.
//
//		  This version of the program with work in both
//		  Linux (gnu C++) and Windows 95/NT/98 (Microsoft
//		  Visual C++) without alterations.
//

/************************************  PERFORM: the perform command. Takes input from stdin and translates 
  it into MIDI track-ready data which it then sends to a MIDI device.
  Playback is interactive, as the user can change tempi, search for strings
  in the file, pause the playback, or goto individual measures using key
  presses.

   Revision history:

   Created: Summer 1993 - Kyle Dawkins
   Altered: June 10th, 1994 - KLD
      - Ported to Borland C++ 3.0 from
        Microsoft's Quick C.
      - Removed memory management code
   Altered: June 13th, 1994 - KLD
      - Rewrote command-line parser
      - Added help screen for 
        interactive mode
   Altered: February 1999 - Craig Sapp
      - Ported to Linux operating system using
        Gnu C 2.7.2 or later, with Intel Pentium 75 MHz CPU or better.
      - Ported to Windows 95/NT operating system 
        using Microsoft Visual C++ ver. 5/6,
        with Intel Pentium 75 MHz CPU or better.
      - Modified command line options slightly for
        new operating systems(-p, -l added -i removed).
   Altered: July 1999 - Craig Sapp
      - Changed internal data structure for performance data.
      - Converted to C++ program flow control style.

************************************/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "Performance.h"
#include "Options.h"
#include "KeyboardInput.h"
#include "LineDisplay.h"

typedef unsigned char uchar;
typedef unsigned long ulong;

// defines
#define DEFAULT_TEMPO    80    /* initial tempo if none specified          */
#define MAX_TEMPO       999    /* fastest that a quarter note can go       */
#define MIN_TEMPO         4    /* slowest that a quarter note can go       */
#define FORWARD_SEARCH    1
#define BACKWARD_SEARCH  -1

// defines for computer keyboard input modes
#define BUFF_STATUS_NOT_USED        0
#define BUFF_STATUS_SEARCH_BACKWARD 1
#define BUFF_STATUS_SEARCH_FORWARD  2

// function declarations
void          checkOptions(Options& opts);
void          eraseline(int count);
void          example(void);
void          help(void);
void          help_screen           (void);
int           keyboardchar(int key);
void          runloop(Performance& data);
void          processBufferStatus(int keybufferstatus);

// global variables
Performance   data;                  // where the MIDI data resides
KeyboardInput keyboard;              // the computer keyboard interface.
Options       options;               // the command-line interface.
LineDisplay   display;               // for displaying and erasing lines.
int           measure_flag = 0;      // for returning to a measure

#define KEY_BUFFER_SIZE 1024
char          keybuffer[KEY_BUFFER_SIZE] = {0}; // computer keyboard commands
int           keybufferindex  = 0;   // location in computer keyboard buffer
int           keybufferstatus = BUFF_STATUS_NOT_USED; 


// global variables associated with command-line options
double    default_tempo = DEFAULT_TEMPO; // for command line tempo setting
int       default_vel   = 64;            // velocity of note if none given
int       echo          = 1;             // true of echoing text or not.
istream*  input         = NULL;          // for input datafile to perform.


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

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

   if (options.getArgCount() == 0) {
      data.read(cin);
      runloop(data);
   } else {
      // perform each command-line input file in sequence
      for (int i=1; i<=options.getArgCount(); i++) {
         data.read(options.getArg(i));
         runloop(data);
   
         // pause between performances according to "wait" option
         if (i < options.getArgCount()) {
            millisleep((float)(1000.0 * options.getDouble("wait")));
         }
      }
   }

   return 0;
} 

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


//////////////////////////////
//
// runloop -- the main interpreter of computer keyboard input and 
//     performance realization.
//

void runloop(Performance& data) {
   int quitFlag = 0;

   while (!quitFlag && !data.eof()) {
      // determine if it is time to play another note
      data.perform();

      // check the computer keyboard for input
      if (keyboard.hit()) {
         quitFlag = keyboardchar(keyboard.getch());
      }

      // give unnecessary cpu time to other processes on computer
      millisleep(1);
   }

   cout << endl;

   if (!quitFlag) {
      data.stop();
      return;
   }
}


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

//////////////////////////////
//
// checkOptions -- check the command line options for any arguments.
//    Options are:
//      -t <double> sets the amount by which the tempo is multiplied
//      -g turns OFF global command echo
//      -i <num> sets the ioaddress of the card (deactivated)
//      -v <int> sets the default velocity for keys with unspecified velocities
//      -\? or -h prints the help screen
//   New options:
//      -p <num> set the MIDI output to port <num> (replaces -i functionality)
//      -l       lists possible MIDI output ports and then exits immediately
//      --help   same as -h option.
//      --wait <num>     wait <num> seconds after each file to perform.
//
//

void checkOptions(Options& opts) {
   opts.define("tempo-scale|t=d:1.0");       
   opts.define("tempo=d:80");       
   opts.define("global-echo|g=b");
   opts.define("ioaddress|i=i:330 hex");
   opts.define("velocity|v=i:64");
   opts.define("port|p=i:0");
   opts.define("list-ports|l=b");
   opts.define("help|?|h=b");
   opts.define("wait|w=d:1.0 seconds");
   opts.define("author:b");
   opts.define("version:b");
   opts.define("example:b");
   opts.process();

   if (opts.getBoolean("author")) {
      cout << "Initial author: Kyle Dawkins, 1993-94\n"
           << "Revised by: Craig Stuart Sapp (craig@ccrma.stanford.edu) 1999" 
           << endl;
      exit(0);
   }
   if (opts.getBoolean("version")) {
      cout << "Humdrum tool: perform created 1993, version 8 July 1999" 
           << endl;
      cout << "compiled: " << __DATE__ << endl;
      exit(0);
   }
   if (opts.getBoolean("help")) {
      help();
      exit(0);
   }
   if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   if (opts.getBoolean("list-ports")) {
      int count = data.getNumPorts();
      for (int i=0; i<count; i++) {
         data.setPort(i);
         cout << "\t" << i << ":\t" << data.getName() << endl;
      }
      exit(0);
   } else {
      data.setPort(opts.getInteger("port"));
      cout << "opening MIDI output port " 
           << opts.getInteger("port") << ":\t" << data.getName() << endl;
      data.open();
   }
   
   default_tempo = opts.getDouble("tempo") * opts.getDouble("tempo-scale");
   if (default_tempo > MAX_TEMPO) {
      default_tempo = MAX_TEMPO;
      cerr << "Warning: reset tempo to: " << MAX_TEMPO << endl;
   } else if (default_tempo < MIN_TEMPO) {
      default_tempo = MIN_TEMPO;
      cerr << "Warning: reset tempo to: " << MIN_TEMPO << endl;
   }

   default_vel = opts.getInteger("velocity");
   if (default_vel > 127) {
      cerr << "Warning: reset default velocity to: " << 127 << endl;
      default_vel = 127;
   } else if (default_vel < 1) {
      cerr << "Warning: reset default velocity to: " << 1 << endl;
      default_vel = 1;
   }

   if (opts.getBoolean("global-echo")) {
      echo = 0;
   } else {
      echo = 1;
   }

   if (opts.getBoolean("ioaddress")) {
      cout << "Warning: the -i option is disabled, use the -p option" << endl;
   }

}



//////////////////////////////
//
// example -- give an example useage of this program
//

void example(void) {
   cout << "No examples yet" << endl;
}



//////////////////////////////
//
// help -- prints out a help page for the perform command including
//     invocation information and options.
//

void help(void) {
   cout << 
   "\n"
   "PERFORM   : Play Humdrum **MIDI files\n"
   "\n"
   "This command provides an interactive, stream-oriented MIDI-sequencer for\n"
   "proof-listening and auditioning of **MIDI-format inputs.\n"
   "\n"
   "Some Interactive commands:                <cr>   : return to beginning\n"
   "<ESC>       : terminate perform            p     : panic reset\n"
   "<space>     : pause; suspend playback      <     : reduce tempo\n"
   "   /<string>: search forward for string    >     : increase tempo\n"
   "   ?<string>: search backwards for string <num>- : back <num> measures\n"
   "   <num><cr>: go to measure <num>         <num>+ : forward <num> measures\n"
   "\n"
   "Syntax:\n"
   "\n"
   "   perform [-g] [-i hex] [-t n.n] [-v n] [inputfile]\n"
   "\n"
   "Options:\n"
   "\n"
   "   -g     : suppress echoing of performance text to standard output\n"
   "   -p n   : use port n for MIDI output\n"
   "   -l     : list possible MIDI output ports, then exit\n"
   "   -t n.n : set initial tempo to n.n times the default tempo\n"
   "   -v n   : set default MIDI key-velocity value: 1 (slow) to 127 (fast)\n"
   << endl;
}



//////////////////////////////
//
// help_screen -- prints out all of the interactive key commands that 
//     are available during the perform process.
//

void help_screen(void) {
   cout <<
   "\n"
   "Key                  Function                      \n"
   "---                  --------                      \n"
   "<Esc> or q           Quit                          \n"
   "<space>              Pause playback                \n"
   "f                    Flag measure                  \n"
   "p                    Panic                         \n"
   "P                    Power panic                   \n"
   "< or ,               Slower tempo                  \n"
   "> or .               Faster tempo                  \n" 
   "<enter>              Restart from beginning of input file\n"
   "-                    Go to previous barline        \n"
   "+                    Go to next barline            \n"
   "<number><enter>      Go to measure <number>        \n"
   "<number>-            Go back <number> measures     \n"
   "<number>+            Go forward <number> measures  \n"
   "/<string>            Search forward for occurrence of <string>\n"
   "?<string>            Search backward for occurrence of <string>\n"
   "h                    Display this help screen      \n"
   << endl;
}



//////////////////////////////
//
// keyboardchar -- handle input from the computer keyboard.
//

int keyboardchar(int key) {
   int quitflag = 0;

   switch (key) {
      case 'p':                // panic
         data.tacet();
         break;

      case 'P':                // super panic
         data.silence();
         break;

      case 'F':                // flag a measure
      case 'f':                // flag a measure
         data.displayClear();
         measure_flag = data.getMeasure();
         cout << "Measure " << measure_flag << " flagged." << endl;
         break;

      case ' ':                // halt
      case 'h':                // halt
      case 'H':                // halt
         data.pause();
         data.displayClear();
         if (tolower(key) == 'h') {
            help_screen();
         }
         cout << "Press space to continue..." << flush;
         break;

      case '0':                // bar number
      case '1':                // bar number
      case '2':                // bar number
      case '3':                // bar number
      case '4':                // bar number
      case '5':                // bar number
      case '6':                // bar number
      case '7':                // bar number
      case '8':                // bar number
      case '9':                // bar number
         cout << "+++ Command Not ready" << endl;
         break;

      case '-':                // relative back new bar
      case '_':                // relative back new bar
         data.backBar(1);
         break;

      case '+':                // relative forward new bar
      case '=':                // relative forward new bar
         data.forwardBar(1);
         break;

      case 0x0d:               // return character
         processBufferStatus(keybufferstatus);
         break;
         
      case '.':                // speed up tempo
      case '>':                // speed up tempo
         {
         double current_tempo = data.getTempo() + 6;
         if (current_tempo > MAX_TEMPO) {
            current_tempo = MAX_TEMPO;
         } else if (current_tempo < MIN_TEMPO) {
            current_tempo = MIN_TEMPO;
         }
         data.setTempo(current_tempo);
         }
         break;

      case ',':                // slow down tempo
      case '<':                // slow down tempo
         {
         double current_tempo = data.getTempo() - 6;
         if (current_tempo > MAX_TEMPO) {
            current_tempo = MAX_TEMPO;
         } else if (current_tempo < MIN_TEMPO) {
            current_tempo = MIN_TEMPO;
         }
         data.setTempo(current_tempo);
         }
         break;

      case '/':                // search forward
         keybufferstatus = BUFF_STATUS_SEARCH_FORWARD;
         break;
      case '?':                // search backward
         keybufferstatus = BUFF_STATUS_SEARCH_BACKWARD;
         break;

      case 0x08:               // backspace
cout << "Not yet ready" << endl;
         break;

      case 0x1b:               // quit (Escape)
      case 'q':                // quit
      case 'Q':                // quit
         data.stop();
         quitflag = 1;
         break;

      default:
         break;
   }

   return quitflag;
}



//////////////////////////////
//
// processBufferStatus -- figure out what to do with the
//   keyboard buffer
//

void processBufferStatus(int keybufferstatus) {
   switch (keybufferstatus) {
      case BUFF_STATUS_SEARCH_BACKWARD:
         break;
      case BUFF_STATUS_SEARCH_FORWARD:
         break;
      case BUFF_STATUS_NOT_USED:
         break;
      default: 
         cout << "Unknown keyboard buffer status flag" << endl;
         exit(1);
   }

}



// md5sum: 0380f88b1461df552e30b38784a54f80 perform.cpp [20050403]