//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Apr  3 23:43:56 PDT 2013
// Last Modified: Thu Apr  4 01:39:09 PDT 2013
// Filename:      ...sig/examples/all/midi2binasc.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/midi/midi2binasc.cpp
// Syntax:        C++; museinfo
//
// Description:   Converts a MIDI file into an ASCII format which can be
//                converted back into a MIDI file with the binasc program:
//                   http://binasc.googlecode.com
//

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

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

typedef unsigned char uchar;


void    convertMidiFile(MidiFile& midifile);
void    printMidiHeader(MidiFile& midifile);
void    checkOptions(Options& opts, int argc, char* argv[]);
void    printTrack(MidiFile& midifile, int track);
int     getVlvSize(int value);
int     getTrackByteCount(MidiFile& midifile, int track);
void    printDecByte(int value);
void    printMidiEvent(MFEvent& event);
void    printHexByte(int value);
void    usage(const char* command);
void    example(void);


// user interface variables
Options options;
int     debugQ   = 0;           // use with --debug option
int     type0Q   = 0;           // force MIDI file to type 0 (single track)

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

int main(int argc, char* argv[]) {
   checkOptions(options, argc, argv);
   MidiFile midifile(options.getArg(1));
   convertMidiFile(midifile);
   return 0;
}

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



//////////////////////////////
//
// convertMidiFile --
//

void convertMidiFile(MidiFile& midifile) {
   if (type0Q) {
      midifile.joinTracks();
   }
   midifile.deltaTime();

   printMidiHeader(midifile);
   int trackcount = midifile.getTrackCount();
   for (int i=0; i<trackcount; i++) {
      printTrack(midifile, i);  
   }
}



//////////////////////////////
//
// printTrack --
//

void printTrack(MidiFile& midifile, int track) {
   cout << endl;

   // first print track header
   cout << "+M +T +r +k\t\t\t; Track chunk marker" << endl;
   // print number of bytes in track
   int trackbytes = getTrackByteCount(midifile, track);
   cout << "4'" << trackbytes << "\t\t\t\t; number of bytes to follow in track"
        << endl;

   cout << endl;
   
   // print the list of events in the track
   MFEvent event;
   int eventcount = midifile.getEventCount(track);
   for (int i=0; i<eventcount; i++) {
      event = midifile.getEvent(track, i);
      printMidiEvent(event);
   }
}



//////////////////////////////
//
// printMidiEvent -- print a time/MIDI message pair.
//

void printMidiEvent(MFEvent& event) {
   // print the time:
   cout << "v" << event.time << "\t";

   // print the command byte in hex format (two digits):
   int commandbyte = event.data[0];
   printHexByte(commandbyte);
   int i;

   switch (commandbyte & 0xf0) {
   case 0x90:
   case 0x80:
      for (i=1; i<event.data.getSize(); i++) {
         cout << " ";
         printDecByte(event.data[i]);
      }
      break;
   default:
      for (i=1; i<event.data.getSize(); i++) {
         cout << " ";
         printHexByte(event.data[i]);
      }
   }
   
   cout << endl;
}



//////////////////////////////
//
// printDecByte -- 
//

void printDecByte(int value) {
   cout << "'" << value;
}



//////////////////////////////
//
// printHexByte --
//

void printHexByte(int value) {
   if (value < 16) {
      cout << "0";
   } else if (value > 255) {
      cerr << "ERROR: value is too large: " << value << endl;
      exit(1);
   }
   cout << hex << value << dec;
}



//////////////////////////////
//
// getTrackByteCount -- number of by the track after the track header.
//    Might need to keep track of End-of-track meta message.
//

int getTrackByteCount(MidiFile& midifile, int track) {
   int sum = 0;
   int i;
   int eventcount = midifile.getEventCount(track);
   MFEvent event;

   for (i=0; i<eventcount; i++) {
      event = midifile.getEvent(track, i); 
      sum += getVlvSize(event.time);
      sum += event.data.getSize();
   }
   return sum;
}



//////////////////////////////
//
// getVlvSize -- return the number of bytes in the VLV format for the
//    integer.
//

int getVlvSize(int value) {
   if (value < 0x80) {
      return 1;
   } if (value < 0x4000) {
      return 2;
   } else if (value < 0x200000) {
      return 3;
   } else if (value < 0x10000000) {
      return 4;
   } else {
      return 5;
   }
}



//////////////////////////////
//
// printMidiHeader --
//
// Example header:
//
// +M +T +h +d             ; MIDI file header chunk marker
// 4'6                     ; bytes in header to follow
// 2'0                     ; format: Type-0 (single track)
// 2'1                     ; track count: 1 track
// '-25 '40                ; divisions: SMPTE: 25 frames/sec 40 subframes/frame
//                         ;    in other words: 25 * 40 = 1000 ticks per second.
//

void printMidiHeader(MidiFile& midifile) {
   // print MIDI file header marker
   cout << "+M +T +h +d			; MIDI file header chunk marker" << endl;
   // print the number of bytes in the MIDI file to follow (always 6):
   cout << "4'6				; bytes in header to follow" << endl;
   // print the format (0 = single track, 1 = multitrack)
   // The MidiFile class does not exactly keep track of this value.
   // It will presume that a single track file is a type-0 MIDI file
   // (type-1 MIDI files can theoretically have a single track, but not
   // usually).
   if (midifile.getTrackCount() == 0) {
      cout << "2'0\t\t\t\t; format: Type-0 (single track)";
   } else {
      cout << "2'1\t\t\t\t; format: Type-1 (multi-track)";
   }
   cout << endl;
   // print track count
   cout << "2'" << midifile.getTrackCount();
   cout << "\t\t\t\t; track count: ";
   cout << midifile.getTrackCount() << " track";
   if (midifile.getTrackCount() != 1) {
      cout << "s";
   }
   cout << endl;

   // print the ticks per quarter note.  The ticks per quarter note 
   // can be SMPTE or regular.  Assuming regular at the moment.
   int ticks = midifile.getTicksPerQuarterNote();
   cout << "2'" << ticks << "\t\t\t\t; ticks per quarter note" << endl;
}



//////////////////////////////
//
// checkOptions -- 
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",  "debug mode to find errors in input file");

   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.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, 22 Jan 2002" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 12 Nov 2003" << 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);
   }

   debugQ   = opts.getBoolean("debug");

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

}



//////////////////////////////
//
// example --
//

void example(void) {

}



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

void usage(const char* command) {
   cout << "Usage: " << command << " midifile" << endl;
}

// md5sum: a1a30253aa8cf823d34ba8acff2d05c0 midi2binasc.cpp [20130420]