// // Programmer: Craig Stuart Sapp // Creation Date: Fri Mar 5 22:49:55 PST 2004 // Last Modified: Thu Jan 6 03:41:05 PST 2011 (fixed array out-of-bounds err) // Filename: tools/mid2hum.cpp // URL: https://github.com/craigsapp/midifile/blob/master/tools/mid2hum.cpp // Syntax: C++11 // vim: ts=3 // // Description: Converts a MIDI file into a Humdrum file. // // Recommendation: MIDI to notation (such as Humdrum) is a bit of an // open-ended problem. I am not going to go through the // trouble of writing a robust converter. For more complicated // MIDI to Humdrum conversions, I suggest the following method: // // If you want to convert MIDI files into Humdrum files, // I would suggest that you use the xml2hum program: // http://museinfo.sapp.org/examples/humdrum/xml2hum.cpp // First, import a MIDI file into Finale or Sibelius music // editing program. Second, edit the music to your liking. // Third, export the music to MusicXML via the Dolet plugin // for either of the two editors: // http://www.recordare.com/sibelius // http://www.recordare.com/finale // Then finally, use the xml2hum program (also available // on the web at: http://kern.humdrum.net/program/xml2hum) // to convert the music into the Humdrum format. // // Todo: tied notes // reading MIDI+ data from volume marker // lyrics // clean up rhythm on slightly shortened or lengthend notes. // Done: // key signatures // barlines (only for constant meters) // time signatures // chord notes (partly done, but not robust) // adding rests to ends of tracks to make all tracks same length // adding rests to start of tracks to make all tracks same length // // Reference: http://crystal.apana.org.au/ghansper/midi_introduction/midi_file_format.html // #include "MidiFile.h" #include "Options.h" #include "Convert.h" #include "HumdrumFile.h" #include #include #include #include using namespace std; using namespace smf; class MidiInfo { public: int track; int state; int index; int tickdur; int starttick; int key; // MIDI key number of note int chord; // boolean for chord notes MidiInfo(void) { clear(); } void clear(void) { key = -1; chord = track = state = index = tickdur = starttick = 0; } }; class MetaInfo { public: int type; int tempo; int starttick; int numerator; int denominator; int mode; int keysig; char text[512]; int tsize; // text size in bytes MetaInfo(void) { clear(); } void clear(void) { type = starttick = numerator = denominator = 0; tempo = keysig = mode = 0; text[0] = '\0'; tsize = 0; } }; void convertToHumdrum (MidiFile& midifile); void getMidiData (Array >& mididata, MidiFile& midifile); void storenote (MidiInfo& info, Array >& mididata, int i, int currtick); void printKernData (Array >& mididata, MidiFile& midifile, Array& metadata); void identifyChords (Array >& mididata); void correctdurations (Array >& mididata, int tpq); int MidiInfoCompare (const void* a, const void* b); void printRestCorrection (ostream& out, int restcorr, int tqp); void processMetaMessage(MidiFile& midifile, int track, int event, Array& metadata); void printMetaData (ostream& out, Array& metadata, int metaindex); void splitDataWithMeasure(ostream& out, HumdrumFile& hfile, int index, int& measurenum, double firstdur); void printHumdrumFileWithBarlines(ostream& out, HumdrumFile& hfile); void checkOptions (Options& opts, int argc, char** argv); void example (void); void usage (const char* command); // user interface variables Options options; int extracttrack = -1; // a single track to output double quantlevel = 0.25; // quatization level for durations. int serialQ = 0; // used with the -s option int reverseQ = 0; // used with the -r option int measurenumQ = 1; // used with the -M option double pickupbeat = 0.0; // used with the -p option double timesigtop = 4.0; // used to print barlines double timesigbottom = 4.0; // used to print barlines ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { checkOptions(options, argc, argv); MidiFile midifile(options.getArg(1)); convertToHumdrum(midifile); return 0; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // convertToHumdrum -- convert a MIDI file into Humdrum format. // void convertToHumdrum(MidiFile& midifile) { int ticksperquarter = midifile.getTicksPerQuarterNote(); cout << "!! Converted from MIDI with mid2hum" << endl; cout << "!! Ticks Per Quarter Note = " << ticksperquarter << endl; cout << "!! Track count: " << midifile.getNumTracks() << endl; Array > mididata; getMidiData(mididata, midifile); } ////////////////////////////// // // storenote -- store a MIDI note into the data array for later processing. // void storenote(MidiInfo& info, Array >& mididata, int i, int currtick) { if (info.state == 0) { // don't store a note already in the off state. cout << "Can not store an empty note" << endl; return; } info.tickdur = currtick - info.starttick; mididata[i].append(info); info.clear(); } ////////////////////////////// // // getMidiData -- // void getMidiData(Array >& mididata, MidiFile& midifile) { mididata.setSize(midifile.getNumTracks()); int i; int j; for (i=0; i metadata; metadata.setSize(1000); metadata.setGrowth(1000); metadata.setSize(0); Array > notestates; notestates.setSize(midifile.getNumTracks()); for (i=0; i 0) ) { // a note-on message. Store the state k = midifile.getEvent(i, j).data[1]; if (notestates[i][k].state == 1) { storenote(notestates[i][k], mididata, i, midifile.getEvent(i, j).time); } notestates[i][k].track = i; notestates[i][k].key = k; notestates[i][k].state = 1; notestates[i][k].index = j; notestates[i][k].starttick = midifile.getEvent(i, j).time; notestates[i][k].tickdur = -1; } else if (((midifile.getEvent(i, j).data[0] & 0xf0) == 0x80) || (((midifile.getEvent(i, j).data[0] & 0xf0) == 0x90) && (midifile.getEvent(i, j).data[2] == 0)) ) { // a note-off message. Print the previous stored note-on message k = midifile.getEvent(i, j).data[1]; storenote(notestates[i][k], mididata, i, midifile.getEvent(i, j).time); } else { processMetaMessage(midifile, i, j, metadata); } } } /* // test print the note information for (i=0; i >& mididata, int tpq) { int i, j; double duration = 0.0; double fraction = 0.0; int count = 0; int durationcorrection = 0; for (i=0; i 0.50) { durationcorrection = -(int)((1.0 - fraction) * tpq + 0.5); } else { durationcorrection = (int)(fraction * tpq + 0.5); } // cout << "tpq: " << tpq << endl; // cout << "\tFraction value: " << fraction << endl; // cout << "\tCorrection value: " << durationcorrection << endl; // cout << "\tDuration value: " << mididata[i][j].tickdur << endl; // if (j >& mididata) { int i, j; for (i=0; i(a); const MidiInfo& B = *static_cast(b); if (A.starttick < B.starttick) { return -1; } else if (A.starttick > B.starttick) { return +1; } else { // separate voices by duration: if (A.tickdur < B.tickdur) { return -1; } else if (A.tickdur > B.tickdur) { return +1; } else { // break ties by key number if (A.key < B.key) { return -1; } else if (A.key > B.key) { return +1; } else { return 0; } } } } ////////////////////////////// // // printKernData -- // void printKernData(Array >& mididata, MidiFile& midifile, Array& metadata) { int i; int j; Array kerntrack; kerntrack.setSize(mididata.getSize()); for (i=0; i restcorrection; restcorrection.setSize(mididata.getSize()); restcorrection.setGrowth(0); restcorrection.setAll(0); int maxticks = 0; int testticks = 0; for (i=0; i 0) { testticks = mididata[i][mididata[i].getSize()-1].starttick + mididata[i][mididata[i].getSize()-1].tickdur; if (testticks > maxticks) { maxticks = testticks; } } } for (i=0; i startrestcorrection; startrestcorrection.setSize(mididata.getSize()); startrestcorrection.setGrowth(0); startrestcorrection.setAll(0); int minstartticks = 999999; for (i=0; i mididata[i][0].starttick) { minstartticks = mididata[i][0].starttick; } } for (i=0; i -1) && (i != (extracttrack-1))) { continue; } buffstream = new stringstream; (*buffstream) << "**kern\n"; metaindex = 0; for (j=0; j0) { difference = mididata[i][j].starttick - mididata[i][j-1].starttick; difference -= mididata[i][j-1].tickdur; if (difference < 0) { (*buffstream) << "!funny timing: " << difference << "\n"; } else if (difference > 0) { // temporary fix for duration of rests while ((double)difference/tpq > 4.0) { (*buffstream) << "1r" << endl; difference -= tpq * 4; } (*buffstream) << Convert::durationToKernRhythm(buffer, (double)difference/tpq); (*buffstream) << "r" << endl; } } else { printRestCorrection(*buffstream, startrestcorrection[i], tpq); } starttime = mididata[i][j].starttick; chordnote = 0; while ((j < mididata[i].getSize()) && (starttime == mididata[i][j].starttick)) { if (chordnote) { (*buffstream) << ' '; } (*buffstream) << Convert::durationToKernRhythm(buffer, (double)mididata[i][j].tickdur/tpq); (*buffstream) << Convert::base12ToKern(buffer, mididata[i][j].key); chordnote = 1; j++; } (*buffstream) << endl; j--; } printRestCorrection(*buffstream, restcorrection[i], tpq); (*buffstream) << "*-\n"; (*buffstream) << ends; if (serialQ) { // cout << (*buffstream).str().c_str(); base.clear(); base.read(*buffstream); printHumdrumFileWithBarlines(cout, base); } else { tempfile.clear(); tempfile.read(*buffstream); delete buffstream; buffstream = new stringstream; printHumdrumFileWithBarlines(*buffstream, tempfile); (*buffstream) << ends; if (baseQ == 0) { base.clear(); base.read(*buffstream); baseQ = 1; } else { extra.clear(); extra.read(*buffstream); base.assemble(tempfile, 2, hpointer); base = tempfile; } } delete buffstream; } if (!serialQ) { cout << base; } } ////////////////////////////// // // printMetaData -- // void printMetaData(ostream& out, Array& metadata, int metaindex) { int ii; int count = 0; switch (metadata[metaindex].type) { case 0x06: // marker break; // ignore for now for (ii=0; ii 0) { out << "! "; for (ii=0; ii 0) { out << "! "; for (ii=0; ii= 1.0) { out << "4r\n"; totaldur -= 1.0; } char buffer[1024] = {0}; if (totaldur <= 0.0) { return; } out << Convert::durationToKernRhythm(buffer, totaldur); out << "r\n"; } ////////////////////////////// // // checkOptions -- // void checkOptions(Options& opts, int argc, char* argv[]) { opts.define("s|serial=b", "print tracks serially rather than assembled"); opts.define("r|reverse=b", "print spines in reverse order"); opts.define("M|no-measure-numbers=b", "don't print measure numbers"); opts.define("p|pickup=d:0.0","pickup beat at start of music before barline"); opts.define("t|track=i:-1", "track number to extract (offset from 1)"); opts.define("q|quantization=d:0.25", "quantization level"); 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, 5 March 2004" << endl; exit(0); } else if (opts.getBoolean("version")) { cout << argv[0] << ", version: March 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); } if (opts.getArgCount() != 1) { usage(opts.getCommand()); exit(1); } extracttrack = opts.getInteger("track"); quantlevel = opts.getDouble("quantization"); serialQ = opts.getBoolean("serial"); reverseQ = opts.getBoolean("reverse"); measurenumQ =!opts.getBoolean("no-measure-numbers"); pickupbeat = opts.getDouble("pickup"); } ////////////////////////////// // // example -- // void example(void) { } ////////////////////////////// // // usage -- // void usage(const char* command) { } ////////////////////////////// // // printHumdrumFileWithBarlines -- // void printHumdrumFileWithBarlines(ostream& out, HumdrumFile& hfile) { hfile.analyzeRhythm("4"); double firstdur = 0.0; int i; int measurenum = 1; int startmeasure = 0; int endmeasure = 0; double bpos = 0.0; for (i=0; i= 0) { // not a rest out << '['; out << Convert::durationToKernRhythm(buffer, firstdur); out << Convert::base40ToKern(buffer, base40); } else { out << Convert::durationToKernRhythm(buffer, firstdur); out << "r"; } if (i= 0) { // not a rest out << Convert::base40ToKern(buffer, base40); out << ']'; } else { out << "r"; } if (i