//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Apr  3 23:43:56 PDT 2013
// Last Modified: Mon Feb  9 21:26:32 PST 2015 Updated for C++11.
// Filename:      tools/midi2binasc.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/midi2binasc.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Converts a MIDI file into an ASCII format which can be
//                converted back into a MIDI file with the binasc program:
//                   https://github.com/craigsapp/binasc
//

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

#include <iostream>
#include <iomanip>

using namespace std;
using namespace smf;


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          (MidiEvent& 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.deltaTicks();

	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
	MidiEvent 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(MidiEvent& event) {
	// print the time:
	cout << "v" << event.tick << "\t";

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

	switch (commandbyte & 0xf0) {
	case 0x90:
	case 0x80:
		for (i=1; i<(int)event.size(); i++) {
			cout << " ";
			printDecByte(event[i]);
		}
		break;
	default:
		for (i=1; i<(int)event.size(); i++) {
			cout << " ";
			printHexByte(event[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);
	MidiEvent event;

	for (i=0; i<eventcount; i++) {
		event = midifile.getEvent(track, i);
		sum += getVlvSize(event.tick);
		sum += (int)event.size();
	}
	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\t\t\t; MIDI file header chunk marker" << endl;
	// print the number of bytes in the MIDI file to follow (always 6):
	cout << "4'6\t\t\t\t; 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().c_str());
		exit(0);
	} else if (opts.getBoolean("example")) {
		example();
		exit(0);
	}

	debugQ   = opts.getBoolean("debug");

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

}



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

void example(void) {
	// add example usage here
}



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

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



