//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Jan 22 16:46:19 PST 2002
// Last Modified: Mon Feb  9 21:26:32 PST 2015 Updated for C++11.
// Filename:      tools/text2midi.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/text2midi.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Converts a text based notelist into a MIDI file.
//

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

#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;
using namespace smf;


void      convertTextToMidiFile (istream& textfile, MidiFile& midifile);
void      adjustbuffer          (char* buffer);
void      readvalues            (char* buffer, int& eventtype, double& start,
                                 double& dur, int& note, int& vel);
void      checkOptions          (Options& opts, int argc, char** argv);
void      example               (void);
void      usage                 (const char* command);

// User interface variables:
Options options;
int     tpq      = 480;     // ticks per quarter note
int     debugQ   = 0;       // use with --debug option
int     maxcount = 100000;  // maxiumum number of notes expected
double  tempo    = 120.0;   // time units will be in seconds
int     channel  = 0;       // default channel


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

int main(int argc, char* argv[]) {
	checkOptions(options, argc, argv);

	fstream textfile(options.getArg(1).c_str(), ios::in);
	if (!textfile.is_open()) {
		cout << "Error: cannot read input text file." << endl;
		usage(options.getCommand().c_str());
		exit(1);
	}
	MidiFile midifile;
	convertTextToMidiFile(textfile, midifile);


	midifile.sortTracks();
	midifile.write(options.getArg(2));

	return 0;
}

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


//////////////////////////////
//
// convertTextToMidiFile --
//

void convertTextToMidiFile(istream& textfile, MidiFile& midifile) {
	vector<uchar> mididata;
	midifile.setTicksPerQuarterNote(tpq);
	midifile.absoluteTicks();
	midifile.allocateEvents(0, 2 * maxcount + 500);  // pre allocate space for
                                                    // max expected MIDI events

	// write the tempo to the midifile
	mididata.resize(6);
	mididata[0] = 0xff;      // meta message
	mididata[1] = 0x51;      // tempo change
	mididata[2] = 0x03;      // three bytes to follow
	int microseconds = (int)(60.0 / tempo * 1000000.0 + 0.5);
	mididata[3] = (microseconds >> 16) & 0xff;
	mididata[4] = (microseconds >> 8)  & 0xff;
	mididata[5] = (microseconds >> 0)  & 0xff;
	midifile.addEvent(0, 0, mididata);

	char buffer[1024] = {0};
	int line = 1;
	double start = 0.0;
	double dur = 0.0;
	int ontick = 0;
	int offtick = 0;
	int note = 0;
	int vel = 0;
	int eventtype = 0;

	while (!textfile.eof()) {
		textfile.getline(buffer, 1000, '\n');
		if (textfile.eof()) {
			break;
		}
		adjustbuffer(buffer);
		if (debugQ) {
			cout << "line " << line << ":\t" << buffer << endl;
		}
		readvalues(buffer, eventtype, start, dur, note, vel);
		if (eventtype != 1) {
			continue;
		}

		// have a good note, so store it in the MIDI file
		ontick  = (int)(start * tpq * 2.0 + 0.5);
		offtick = (int)((start + dur) * tpq * 2.0 + 0.5);
		if (offtick <= ontick) {
			offtick = ontick + 1;
		}
		if (debugQ) {
			cout << "Note ontick=" << ontick << "\tofftick=" << offtick
			     << "\tnote=" << note << "\tvel=" << vel << endl;
		}

		mididata.resize(3);
		mididata[0] = 0x90 | channel;       // note on command
		mididata[1] = (uchar)(note & 0x7f);
		mididata[2] = (uchar)(vel & 0x7f);
		midifile.addEvent(0, ontick, mididata);
		mididata[0] = 0x80 | channel;       // note off command
		midifile.addEvent(0, offtick, mididata);

		line++;
	}
}



//////////////////////////////
//
// readvalues -- read parameter values from the input dataline.
//     returns 0 if no parameters were readable.
//

void readvalues(char* buffer, int& eventtype, double& start, double& dur,
	int& note, int& vel) {
	char *ptr = strtok(buffer, " \t\n");
	if (ptr == NULL) {
		eventtype = 0;
		return;
	}

	if (strcmp(ptr, "note") != 0) {
		eventtype = 0;
		return;
	} else {
		eventtype = 1;
	}

	// read the starttime
	ptr = strtok(NULL, " \t\n");
	if (ptr == NULL) {
		eventtype = 0;
		return;
	}
	start = atof(ptr);

	// read the duration
	ptr = strtok(NULL, " \t\n");
	if (ptr == NULL) {
		eventtype = 0;
		return;
	}
	dur = atof(ptr);

	// read the note number
	ptr = strtok(NULL, " \t\n");
	if (ptr == NULL) {
		eventtype = 0;
		return;
	}
	note = strtol(ptr, NULL, 10);
	if (note < 0 || note > 127) {
		eventtype = 0;
		return;
	}

	// read the starttime
	ptr = strtok(NULL, " \t\n");
	if (ptr == NULL) {
		eventtype = 0;
		return;
	}
	vel = strtol(ptr, NULL, 10);
	if (vel < 0 || vel > 127) {
		eventtype = 0;
		return;
	}

	eventtype = 1;

}



//////////////////////////////
//
// adjustbuffer -- remove comments and make lower characters
//

void adjustbuffer(char* buffer) {
	int i = 0;
	while (buffer[i] != '\0') {
		buffer[i] = tolower(buffer[i]);
		if (buffer[i] == ';') {
			buffer[i] = '\0';
			return;
		}
		i++;
	}
}



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

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("c|channel=i:0","MIDI Channel to play notes on (offset from 0)");
	opts.define("debug=b",  "debug mode to find errors in input file");
	opts.define("max=i:100000", "maximum number of notes expected in input");

	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: 22 Jan 2002" << 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");
	maxcount = opts.getInteger("max");
	channel  = opts.getInteger("channel");
	if (channel < 0) {
		channel = 0;
	} if (channel > 15) {
		channel = 15;
	}

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

}



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

void example(void) {

}



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

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




