// // Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu> // Creation Date: Mon Apr 6 13:54:09 PDT 2009 // Last Modified: Mon Apr 6 13:54:12 PDT 2009 // Filename: peep2midi.cpp // Syntax: C++; museinfo // // Description: Convert Performance Expression Extraction Program // output data into MIDI data. // #include "MidiFile.h" #include "humdrum.h" #include "Options.h" #include <ctype.h> #include <string.h> #include <stdio.h> typedef unsigned char uchar; // user interface variables Options options; int debugQ = 0; // use with --debug option int maxcount = 100000; int mindyn = 30; // use with -r option int maxdyn = 120; // use with -r option double duration = 0.1; // use with -d option const char* filename = ""; // use with -o option // function declarations: void checkOptions(Options& opts, int argc, char** argv); void createMidiFile(MidiFile& midifile, HumdrumFile& infile); int getMIDIKeyNum(const char* string); int getTrackNumber(const char* string); void example(void); void usage(const char* command); ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { checkOptions(options, argc, argv); HumdrumFile infile; infile.read(options.getArg(1)); MidiFile midifile; createMidiFile(midifile, infile); if (strcmp(filename, "") == 0) { cout << midifile; } else { midifile.write(filename); } return 0; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // createMidiFile -- // input humdrum file data should be in simple columns in this order: // // **data **data **data **data **data // 1.667 18.8 F3 -2 1 // 1.666 19 C4 -1 1 // 1.667 10.7 a4 -2 1 // 1.667 19.2 c5 -2 1 // 2.009 24 F3 +10 1 // 2.01 24.2 C4 +10 1 // // Column one is the time in seconds at which the note is started // Column two is a dynamic value (the range of which will be normalized) // Column three contains the pitch information. // void createMidiFile(MidiFile& midifile, HumdrumFile& infile) { Array<int> millitimes; Array<double> velocities; Array<int> keynum; Array<int> track; millitimes.setSize(infile.getNumLines()); velocities.setSize(infile.getNumLines()); keynum.setSize(infile.getNumLines()); track.setSize(infile.getNumLines()); millitimes.setSize(0); velocities.setSize(0); keynum.setSize(0); track.setSize(0); int intval; double floatval; double dmax = -100000; double dmin = +100000; int i; for (i=0; i<infile.getNumLines(); i++) { if (!infile[i].isData()) { continue; } sscanf(infile[i][0], "%lf", &floatval); intval = int(floatval * 1000.0 + 0.5); millitimes.append(intval); sscanf(infile[i][1], "%lf", &floatval); velocities.append(floatval); if (floatval < dmin) { dmin = floatval; } if (floatval > dmax) { dmax = floatval; } intval = getMIDIKeyNum(infile[i][2]); keynum.append(intval); intval = getTrackNumber(infile[i][2]); track.append(intval); } millitimes.allowGrowth(0); velocities.allowGrowth(0); keynum.allowGrowth(0); track.allowGrowth(0); // normalize the dynamics data into the range from 0 to 1 double diff = dmax - dmin; for (i=0; i<velocities.getSize(); i++) { if (diff > 0.0) { velocities[i] = (velocities[i] - dmin) / diff; } else { velocities[i] = 0.5; } } // now ready to write the data to the MIDI file: midifile.setMillisecondDelta(); // SMPTE 25 frames & 40 subframes midifile.absoluteTime(); // Time values inserted are absolute midifile.addTrack(2); // Right and Left hands Array<uchar> event; event.setSize(3); int intdur = int(duration * 1000.0 + 0.5); int lasttime = 0; int dyndiff = maxdyn - mindyn; int vel; for (i=0; i<millitimes.getSize(); i++) { if ((keynum[i] <= 10) || (keynum[i] > 127)) { continue; } vel = int(velocities[i] * dyndiff + mindyn + 0.5); if (vel < 1) { vel = 1; } if (vel > 127) { vel = 127; } event[0] = 0x90; // note-on event[1] = keynum[i]; event[2] = vel; midifile.addEvent(track[i], millitimes[i], event); event[2] = 0; lasttime = millitimes[i] + intdur; midifile.addEvent(track[i], lasttime, event); } // write the end of track marker event[0] = 0xff; event[1] = 0x2f; event[2] = 0; for (i=0; i<midifile.getTrackCount(); i++) { if (i>0) { // have to lengthen the last note in track due to bugs // in various MIDI playback programs which clip // the last chord of a file midifile.getEvent(i, midifile.getNumEvents(i)-1).time += 1500; } midifile.addEvent(i, lasttime+2000, event); } // add comments from header for (i=0; i<infile.getNumLines() && i<lasttime; i++) { if (infile[i].isBibliographic() || infile[i].isGlobalComment()) { // 0x01 is a text event midifile.addMetaEvent(0, i, 0x01, infile[i].getLine()); } } // sort the ontimes and offtimes so they are in correct time order: midifile.sortTracks(); } ////////////////////////////// // // getTrackNumber -- lowercase pitch name = right hand; uppercase = left hand // int getTrackNumber(const char* string) { if (islower(string[0])) { return 1; } else { return 2; } } ////////////////////////////// // // getMIDIKeyNum -- // int getMIDIKeyNum(const char* string) { int accid = 0; int octave = -1; int len = strlen(string); int diatonic = -1; switch (tolower(string[0])) { case 'c': diatonic = 0; break; case 'd': diatonic = 2; break; case 'e': diatonic = 4; break; case 'f': diatonic = 5; break; case 'g': diatonic = 7; break; case 'a': diatonic = 9; break; case 'b': diatonic = 11; break; } if (diatonic < 0) { return -1; } int i; for (i=0; i<len; i++) { if (string[i] == '#') { accid++; } else if (string[i] == '-') { accid--; } if (isdigit(string[i]) && (octave < 0)) { sscanf(&(string[i]), "%d", &octave); } } if (octave < 0) { return -1; } return diatonic + octave * 12 + accid; } ////////////////////////////// // // checkOptions -- // void checkOptions(Options& opts, int argc, char* argv[]) { opts.define("o|output=s", "output midi file name"); opts.define("r|range=s", "dynamics range"); 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: 22 Jan 2002" << 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); } filename = opts.getString("output"); int count; if (opts.getBoolean("range")) { count = sscanf(opts.getString("range"), "%d-%d", &mindyn, &maxdyn); if (count != 2) { count = sscanf(opts.getString("range"), "%d:%d", &mindyn, &maxdyn); } if (count != 2) { count = sscanf(opts.getString("range"), "%d,%d", &mindyn, &maxdyn); } if (count != 2) { count = sscanf(opts.getString("range"), "%d, %d", &mindyn, &maxdyn); } if (count != 2) { count = sscanf(opts.getString("range"), "%d %d", &mindyn, &maxdyn); } } if (mindyn > maxdyn) { int temp = mindyn; mindyn = maxdyn; maxdyn = temp; } if (mindyn == maxdyn) { mindyn = 20; maxdyn = 120; } } ////////////////////////////// // // example -- // void example(void) { } ////////////////////////////// // // usage -- // void usage(const char* command) { cout << "Usage: " << command << " midifile" << endl; } // md5sum: 5e0904633d37b09c5cfb9bbcccaa8ac6 peep2midi.cpp [20090419]