// // Programmer: Craig Stuart Sapp // Creation Date: Fri Mar 5 22:49:55 PST 2004 // Last Modified: Sat Mar 6 11:28:05 PST 2004 // Last Modified: Thu Jan 6 03:41:05 PST 2011 (fixed array out-of-bounds err) // Filename: ...sig/examples/all/mid2hum.cpp // Web Address: http://sig.sapp.org/examples/museinfo/humdrum/mid2hum.cpp // Syntax: C++; museinfo // // Description: 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 #include #include #include #include #include "MidiFile.h" #include "Options.h" #include "Convert.h" #include "HumdrumFile.h" using namespace std; 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; } }; // 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 // function declarations: void convertToHumdrum (smf::MidiFile& midifile); void getMidiData (vector >& mididata, smf::MidiFile& midifile); void storenote (MidiInfo& info, vector >& mididata, int i, int currtick); void printKernData (vector >& mididata, smf::MidiFile& midifile, vector& metadata); void identifyChords (vector >& mididata); void correctdurations (vector >& mididata, int tpq); int MidiInfoCompare (const void* a, const void* b); void printRestCorrection (ostream& out, int restcorr, int tqp); void processMetaMessage(smf::MidiFile& midifile, int track, int event, vector& metadata); void printMetaData (ostream& out, vector& 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); ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { checkOptions(options, argc, argv); smf::MidiFile midifile(options.getArg(1)); convertToHumdrum(midifile); return 0; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // convertToHumdrum -- convert a MIDI file into Humdrum format. // void convertToHumdrum(smf::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; vector > mididata; getMidiData(mididata, midifile); } ////////////////////////////// // // storenote -- store a MIDI note into the data array for later processing. // void storenote(MidiInfo& info, vector >& 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].push_back(info); info.clear(); } ////////////////////////////// // // getMidiData -- // void getMidiData(vector >& mididata, smf::MidiFile& midifile) { mididata.resize(midifile.getNumTracks()); for (int i=0; i<(int)mididata.size(); i++) { mididata[i].reserve(10000); mididata[i].resize(0); } vector metadata; metadata.reserve(1000); metadata.resize(0); vector > notestates(midifile.getNumTracks()); for (int i=0; i<(int)notestates.size(); i++) { notestates[i].resize(128); } // extract a list of notes in the MIDI file along with their durations int k; for (int i=0; i 0) ) { // a note-on message. Store the state k = midifile.getEvent(i, j)[1]; if (notestates[i][k].state == 1) { storenote(notestates[i][k], mididata, i, midifile.getEvent(i, j).tick); } 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).tick; notestates[i][k].tickdur = -1; } else if (((midifile.getEvent(i, j)[0] & 0xf0) == 0x80) || (((midifile.getEvent(i, j)[0] & 0xf0) == 0x90) && (midifile.getEvent(i, j)[2] == 0)) ) { // a note-off message. Print the previous stored note-on message k = midifile.getEvent(i, j)[1]; storenote(notestates[i][k], mididata, i, midifile.getEvent(i, j).tick); } else { processMetaMessage(midifile, i, j, metadata); } } } /* // test print the note information for (i=0; i<(int)mididata.size(); i++) { cout << "Track " << i << endl; for (j=0; j<(int)mididata[i].size(); j++) { cout << "\tNote: pitch = " << (int)midifile.getEvent(i, mididata[i][j].index)[1] << "\tduration = " << (double)mididata[i][j].tickdur/midifile.getTicksPerQuarterNote() << endl; } } */ for (int i=0; i<(int)mididata.size(); i++) { qsort(mididata[i].data(), mididata[i].size(), sizeof(MidiInfo), MidiInfoCompare); } identifyChords(mididata); correctdurations(mididata, midifile.getTicksPerQuarterNote()); printKernData(mididata, midifile, metadata); } ////////////////////////////// // // processMetaMessage -- // void processMetaMessage(smf::MidiFile& midifile, int track, int event, vector& metadata) { MetaInfo tempmeta; tempmeta.type = midifile.getEvent(track, event)[1]; tempmeta.starttick = midifile.getEvent(track, event).tick; int tempo = 0; int d; // counter into data field of meta message switch (tempmeta.type) { case 0x00: // sequence number break; case 0x01: // text break; case 0x02: // copyright notice break; case 0x03: // sequence/track name break; case 0x04: // instrument name break; case 0x05: // lyric break; case 0x06: // marker tempmeta.tsize = (unsigned char)midifile.getEvent(track, event)[2]; for (d=0; d >& mididata, int tpq) { int i, j; double duration = 0.0; double fraction = 0.0; int count = 0; int durationcorrection = 0; for (i=0; i<(int)mididata.size(); i++) { if (mididata[i].size() == 0) { continue; } for (j=0; j<(int)mididata[i].size(); j++) { duration = (double)mididata[i][j].tickdur/tpq; fraction = duration/quantlevel; count = (int)fraction; fraction = fraction - count; if (fraction > 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<(int)mididata[i].size()-1) { // cout << "\tDifference value: " // << mididata[i][j+1].starttick - mididata[i][j].starttick // << endl; // cout << "\tIdeal correction: " // << mididata[i][j+1].starttick - mididata[i][j].starttick // - mididata[i][j].tickdur // << endl; // } if (-durationcorrection != mididata[i][j].tickdur) { mididata[i][j].tickdur += durationcorrection; } } } } ////////////////////////////// // // identifyChords -- // void identifyChords(vector >& mididata) { int i, j; for (i=0; i<(int)mididata.size(); i++) { for (j=1; j<(int)mididata[i].size(); j++) { if ((mididata[i][j].starttick == mididata[i][j-1].starttick) && (mididata[i][j].tickdur == mididata[i][j-1].tickdur) ) { mididata[i][j].chord = 1; } } } } ////////////////////////////// // // MidiInfoCompare -- // int MidiInfoCompare(const void* a, const void* b) { MidiInfo& A = *((MidiInfo*)a); MidiInfo& B = *((MidiInfo*)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(vector >& mididata, smf::MidiFile& midifile, vector& metadata) { vector kerntrack(mididata.size()); for (int i=0; i<(int)mididata.size(); i++) { if (mididata[i].size() == 0) { kerntrack[i] = 0; } else { kerntrack[i] = 1; } } vector restcorrection(mididata.size(), 0); int maxticks = 0; int testticks = 0; for (int i=0; i<(int)mididata.size(); i++) { if (mididata[i].size() > 0) { testticks = mididata[i][(int)mididata[i].size()-1].starttick + mididata[i][(int)mididata[i].size()-1].tickdur; if (testticks > maxticks) { maxticks = testticks; } } } for (int i=0; i<(int)mididata.size(); i++) { if (kerntrack[i] == 0) { continue; } testticks = mididata[i][(int)mididata[i].size()-1].starttick + mididata[i][(int)mididata[i].size()-1].tickdur; restcorrection[i] = maxticks - testticks; } vector startrestcorrection(mididata.size(), 0); int minstartticks = 999999; for (int i=0; i<(int)mididata.size(); i++) { if (kerntrack[i] == 0) { continue; } if (minstartticks > mididata[i][0].starttick) { minstartticks = mididata[i][0].starttick; } } for (int i=0; i<(int)mididata.size(); i++) { if (kerntrack[i] == 0) { continue; } startrestcorrection[i] = mididata[i][0].starttick - minstartticks; } HumdrumFile base; HumdrumFile extra; HumdrumFile tempfile; int baseQ = 0; stringstream *buffstream; HumdrumFile* hpointer[2]; hpointer[0] = &base; hpointer[1] = &extra; char buffer[1024] = {0}; int tpq = midifile.getTicksPerQuarterNote(); int difference = 0; int chordnote = 0; int starttime = 0; int metaindex = 0; for (int ii=0; ii<(int)mididata.size(); ii++) { int i; // go in reverse order with the tracks because the ordering is // usually from highest to lowest which should be reversed // in the Humdrum file according to the specification if (reverseQ) { i = ii; } else { i = (int)mididata.size() - 1 - ii; } if (kerntrack[i] == 0) { continue; } if ((extracttrack > -1) && (i != (extracttrack-1))) { continue; } buffstream = new stringstream; (*buffstream) << "**kern\n"; metaindex = 0; for (int j=0; j<(int)mididata[i].size(); j++) { while ((metaindex < (int)metadata.size()) && (metadata[metaindex].starttick <= mididata[i][j].starttick)) { printMetaData(*buffstream, metadata, metaindex); metaindex++; } // check for a rest if (j>0) { 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 < (int)mididata[i].size()) && (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, vector& 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().c_str()); exit(0); } else if (opts.getBoolean("example")) { example(); exit(0); } if (opts.getArgCount() != 1) { usage(opts.getCommand().c_str()); 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