// // Programmer: Craig Stuart Sapp // Creation Date: Sun Oct 24 10:39:47 PDT 1999 // Last Modified: Sat Nov 25 20:17:58 PST 2000 (added instrument selection) // Last Modified: Tue Feb 20 19:20:09 PST 2001 (add articulation interpretation) // Last Modified: Sat Aug 23 08:57:54 PDT 2003 (added *free/*strict control) // Last Modified: Wed Mar 24 19:56:04 PST 2004 (fixed initial *MM ignore) // Last Modified: Wed Apr 7 16:22:38 PDT 2004 (grace-note noteoff hack) // Last Modified: Tue Sep 7 03:31:49 PDT 2004 (added extra space at end) // Last Modified: Tue Sep 7 03:43:09 PDT 2004 (more grace-note noteoff fixing) // Last Modified: Tue Sep 7 20:58:38 PDT 2004 (initial dynamics processing) // Last Modified: Fri Sep 10 02:26:59 PDT 2004 (added padding option) // Last Modified: Fri Sep 10 19:46:53 PDT 2004 (added cresc. decresc. control) // Last Modified: Sun Sep 12 05:20:44 PDT 2004 (added human and metric volume) // Last Modified: Wed Mar 23 00:35:18 PST 2005 (added constant volume back) // Last Modified: Sat Dec 17 22:46:11 PST 2005 (added **time processing) // Last Modified: Sat Jun 3 10:35:29 PST 2005 (added **tempo processing) // Last Modified: Sun Jun 4 19:32:22 PDT 2006 (added PerfViz match files) // Last Modified: Tue Sep 12 19:36:08 PDT 2006 (added **idyn processing) // Last Modified: Sun Oct 1 21:04:35 PDT 2006 (continued work in **idyn) // Last Modified: Thu May 3 22:55:15 PDT 2007 (added *pan controls) // Last Modified: Thu Oct 30 12:42:35 PST 2008 (added --no-rest option) // Last Modified: Thu Nov 20 08:19:40 PST 2008 (added **Dcent interpretation) // Last Modified: Sun Nov 23 22:49:15 PST 2008 (added --temperament option) // Last Modified: Tue May 12 12:14:09 PDT 2009 (added rhythmic scaling factor) // Filename: ...sig/examples/all/hum2mid.cpp // Web Address: http://sig.sapp.org/examples/museinfo/humdrum/hum2mid.cpp // Syntax: C++; museinfo // // Description: Converts Humdrum **kern data into MIDI data in a // Standard MIDI File format. // #include "museinfo.h" #include #include #include #include // stringstream, or strstream in older CPP is needed only for PerfViz files: #ifndef OLDCPP #include #include #define SSTREAM stringstream #define CSTRING str().c_str() using namespace std; #else #include #ifdef VISUAL #include /* for Windows 95 */ #else #include #endif #define SSTREAM strstream #define CSTRING str() #endif #define TICKSPERQUARTERNOTE 120 // Dynamics to attack velocity conversions: // Set based on conversions to MIDI files made in Finale: // (Every 13 increments) #define DYN_FFFF 127 #define DYN_FFF 114 #define DYN_FF 101 #define DYN_F 88 #define DYN_MF 75 #define DYN_MP 62 #define DYN_P 49 #define DYN_PP 36 #define DYN_PPP 23 #define DYN_PPPP 10 // PerfViz data structure class PerfVizNote { public: int pitch; // pitch name of note (in base40 notation) int beat; // beat number in measure of note int vel; // MIDI attack velocity of note int ontick; // tick time for note on int offtick; // tick time for note off double scoredur; // score duration of notes in beats double absbeat; // absolute beat position of note in score double beatfrac; // starting fraction of beat double beatdur; // duration of note in beats static int sid; // serial number id static int bar; // current measure number static int key; // used to print key signatures static int tstop; // time signature top static int tsbottom; // time signature bottom static double approxtempo; // the approximate tempo marking }; int PerfVizNote::sid = 0; int PerfVizNote::bar = 0; int PerfVizNote::key = -1; int PerfVizNote::tstop = 0; int PerfVizNote::tsbottom = 0; double PerfVizNote::approxtempo = 0; // global variables: const char *outlocation = NULL; int trackcount = 0; // number of tracks in MIDI file int track = 0; // track number starting at 0 int offset = 0; // start-time offset in ticks int tempo = 60; // default tempo // user interface variables Options options; int storeCommentQ = 1; // used with -C option int storeTextQ = 1; // used with -T option int plusQ = 0; // used with --plus option int defaultvolume = 64; // used with -v option double tscaling = 1.0; // used with -s option int multitimbreQ = 1; // used with -c option int instrumentQ = 1; // used with -I option int fixedChannel = 0; // used with -c option int instrumentnumber = -1; // used with -f option int forcedQ = 0; // used with -f option int mine = 0; // used with -m option int shortenQ = 0; // used with -s option int shortenamount = 0; // used with -s option int plainQ = 0; // used with -p option int debugQ = 0; // used with --debug option int dynamicsQ = 1; // used with -D option int padQ = 1; // used with -P option int humanvolumeQ = 5; // used with --hv option int metricvolumeQ = 5; // used with --mv option int sforzando = 20; // used with --sf option int fixedvolumeQ = 0; // used with -v option int timeQ = 0; // used with --time option int tempospineQ = 0; // used with --ts option int perfvizQ = 0; // used with --pvm option int idynQ = 0; // used with -d option double idynoffset = 0.0; // used with -d option int timeinsecQ = 1; // needs an option int norestQ = 0; // used with --no-rest option int starttick = 0; // used with --no-rest option int bendQ = 0; // used with --bend option double bendamt = 200; // used with --bend option int bendpcQ = 0; // used with --temperament double bendbypc[12] = {0}; // used with --temperament double rhysc = 1.0; // used with -r option // for example, use -r 4.0 to make written sixteenth notes // appear as if they were quarter notes in the MIDI file. SSTREAM *PVIZ = NULL; // for storing individual notes in PerfViz data file. double tickfactor = 960.0 / 1000.0; // function declarations: void assignTracks (HumdrumFile& infile, Array& trackchannel); double checkForTempo (HumdrumRecord& record); void checkOptions (Options& opts, int argc, char** argv); void example (void); int makeVLV (uchar *buffer, int number); void reviseInstrumentMidiNumbers(const char* string); int setMidiPlusVolume (const char* kernnote); void storeMetaText (MidiFile& mfile, int track, const char* string, int tick, int metaType = 1); void storeMidiData (HumdrumFile& infile, MidiFile& outfile); void storeInstrument (int ontick, MidiFile& mfile, HumdrumFile& infile, int i, int j, int pcQ); void usage (const char* command); void storeFreeNote (Array >& array,int ptrack,int midinote); void getDynamics (HumdrumFile& infile, Array >& dynamics, int defaultdynamic); void getDynamicAssignments(HumdrumFile& infile, Array& assignments); void getStaffValues (HumdrumFile& infile, int staffline, Array >& staffvalues); void getNewDynamics (Array& currentdynamic, Array& assignments, HumdrumFile& infile, int line, Array >& crescendos, Array >& accentuation); void processCrescDecresc(HumdrumFile& infile, Array >& dynamics, Array >& crescendos); void interpolateDynamics(HumdrumFile& infile, Array& dyn, Array& cresc); void generateInterpolation(HumdrumFile& infile, Array& dyn, Array& cresc, int startline, int stopline, int direction); int findtermination (Array& dyn, Array& cresc, int start); char adjustVolumeHuman (int startvol, int delta); char adjustVolumeMetric (int startvol, int delta, double metricpos); char applyAccentuation (int dynamic, int accent); int getMillisecondTime (HumdrumFile& infile, int line); int getFileDurationInMilliseconds(HumdrumFile& infile); int getMillisecondDuration(HumdrumFile& infile, int row, int col, int subcol); void addTempoTrack (HumdrumFile& infile, MidiFile& outfile); void getBendByPcData (double* bendbypc, const char* filename); void insertBendData (MidiFile& outfile, double* bendbypc); // PerfViz related functions: void writePerfVizMatchFile(const char* filename, SSTREAM& contents); ostream& operator<< (ostream& out, PerfVizNote& note); void printPerfVizKey (int key); void printPerfVizTimeSig (int tstop, int tsbottom); void printPerfVizTempo (double approxtempo); void printRational (ostream& out, double value); void storePan (int ontime, MidiFile& outfile, HumdrumFile& infile, int row, int column); void adjustEventTimes (MidiFile& outfile, int starttick); void checkForBend (MidiFile& outfile, int notetick, int channel, HumdrumFile& infile, int row, int col, double scalefactor); Array tracknamed; // for storing boolean if track is named Array trackchannel; // channel of each track #define TICKSPERQUARTERNOTE 120 int tpq = TICKSPERQUARTERNOTE; ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { // for humanizing processes #ifndef VISUAL srand48(time(NULL)); # else srand(time(NULL)); #endif SSTREAM *perfviz = NULL; HumdrumFile infile; MidiFile outfile; // process the command-line options checkOptions(options, argc, argv); if (perfvizQ) { tpq = 480; tempo = 120; } outfile.setTicksPerQuarterNote(tpq); if (timeQ) { outfile.setMillisecondDelta(); } // figure out the number of input files to process // only the first argument will be processed. If there are // no arguments, then standard input will be used. int numinputs = options.getArgCount(); for (int i=0; i<1 || i==0; i++) { infile.clear(); // if no command-line arguments read data file from standard input if (numinputs < 1) { infile.read(cin); } else { infile.read(options.getArg(i+1)); } // analyze the input file according to command-line options infile.analyzeRhythm("4"); if (perfvizQ) { perfviz = new SSTREAM[1]; const char *filename = NULL; PVIZ = perfviz; perfviz[0] << "info(matchFileVersion,2.0).\n"; if (numinputs < 1) { perfviz[0] << "info(scoreFileName,'STDIN').\n"; } else { perfviz[0] << "info(scoreFileName,'"; filename = strrchr(options.getArg(i+1), '/'); if (filename == NULL) { filename = options.getArg(i+1); } else { filename = filename + 1; } perfviz[0] << filename; perfviz[0] << "').\n"; } if (options.getBoolean("output")) { perfviz[0] << "info(midiFileName,'"; filename = strrchr(options.getString("output"), '/'); if (filename == NULL) { filename = options.getString("output"); } else { filename = filename + 1; } perfviz[0] << filename; perfviz[0] << "').\n"; } else { perfviz[0] << "info(midifileName,'STDOUT').\n"; } perfviz[0] << "info(midiClockUnits,"; perfviz[0] << tpq << ").\n"; perfviz[0] << "info(midiClockRate,500000).\n"; } trackcount = infile.getMaxTracks(); tracknamed.setSize(trackcount + 1); trackchannel.setSize(trackcount + 1); for (int j=0; j gmsysex; gmsysex.setSize(6); gmsysex[0] = 0xf0; // Start of SysEx gmsysex[1] = 0x7e; // Universal (reserved) ID number gmsysex[2] = 0x7f; // Device ID (general transmission) gmsysex[3] = 0x09; // Means 'This is a message about General MIDI' gmsysex[4] = 0x01; // Means 'Turn General MIDI On'. 02 means 'Off' gmsysex[5] = 0xf7; // End of SysEx outfile.addEvent(0, 0, gmsysex); } */ if (bendpcQ) { insertBendData(outfile, bendbypc); } storeMidiData(infile, outfile); if (tempospineQ) { addTempoTrack(infile, outfile); } outfile.sortTracks(); if (norestQ) { adjustEventTimes(outfile, starttick); } if (outlocation == NULL) { cout << outfile; } else { outfile.write(outlocation); } if (perfvizQ) { // currently you cannot create multiple PerfViz files from // multiple inputs. writePerfVizMatchFile(options.getString("perfviz"), perfviz[0]); delete [] perfviz; } } return 0; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // insertBendData -- store pitch bend data, one channel per // pitch class. Channel 10 is skipped because it is used // as a drum channel on General MIDI synthesizers. // void insertBendData(MidiFile& outfile, double* bendbypc) { int i; int channel; int track = 0; double bendvalue; if (outfile.getNumTracks() > 1) { // don't store in first track if a multi-track file. track = 1; } Array mididata; mididata.setSize(2); if (instrumentnumber < 0) { mididata[1] = 0; } else { mididata[1] = instrumentnumber; } for (i=0; i<12; i++) { channel = i; if (channel >= 9) { channel++; } bendvalue = bendbypc[i]; outfile.addPitchBend(track, offset, channel, bendvalue); if (forcedQ) { mididata[0] = 0xc0 | (0x0f & channel); outfile.addEvent(track, offset, mididata); } } } ////////////////////////////// // // adjustEventTimes -- set the first event time to starttick and // also subtrace that value from all events in the MIDI file; // void adjustEventTimes(MidiFile& outfile, int starttick) { int i, j; _MFEvent* eventptr; int atime; int minval = 1000000000; for (i=0; idata.getSize() <= 0) { continue; } if ((eventptr->data[0] & 0xf0) == 0x90) { if (eventptr->time < minval) { minval = eventptr->time; } break; } } } if (minval > 1000000) { // minimum time is ridiculously large, so don't do anything return; } for (i=0; itime; atime = atime - minval; if (atime < 0) { atime = 0; } eventptr->time = atime; } } } ////////////////////////////// // // addTempoTrack -- // void addTempoTrack(HumdrumFile& infile, MidiFile& outfile) { int i, j; double tempo; double absbeat; int ontick; int ttempo; Array mididata; // should erase previous contents of tempo track first... for (i=0; i> 16) & 0xff); mididata[4] = (uchar)((ttempo >> 8) & 0xff); mididata[5] = (uchar)(ttempo & 0xff); absbeat = infile.getAbsBeat(i); ontick = int(absbeat * outfile.getTicksPerQuarterNote()); outfile.addEvent(0, ontick + offset, mididata); } } } } } } ////////////////////////////// // // assignTracks -- give a track number for each spine in the input file // trying to place the same instruments into the same channels if // there are not enough channels to go around. // void assignTracks(HumdrumFile& infile, Array& trackchannel) { int i, j; Array instruments; // MIDI instruments indicated in track trackchannel.setSize(infile.getMaxTracks() + 1); instruments.setSize(trackchannel.getSize()); for (i=0; i 16) { // channel allocation is over quota: squash over-allocations: for (i=0; i 15) { trackchannel[i] = 0; } } } } // don't conserve tracks if there is enough to go around if (trackchannel.getSize() < 13) { for (i=1; i9) { trackchannel[i] = i; } } } } ////////////////////////////// // // checkForTempo -- // double checkForTempo(HumdrumRecord& record) { int i; float tempo = 60.0; for (i=0; i 127) { defaultvolume = 127; } tscaling = opts.getDouble("tempo-scaling"); if (tscaling < 0.001) { tscaling = 1.0; } else if (tscaling > 1000.0) { tscaling = 1.0; } instrumentQ = !opts.getBoolean("noinstrument"); if (opts.getBoolean("channel")) { multitimbreQ = 0; fixedChannel = opts.getInteger("channel") - 1; if (fixedChannel < 0) { fixedChannel = 0; } if (fixedChannel > 15) { fixedChannel = 15; } instrumentQ = 0; } else { multitimbreQ = 1; fixedChannel = -1; } if (opts.getBoolean("output")) { outlocation = opts.getString("output"); } else { outlocation = NULL; } forcedQ = opts.getBoolean("forceinstrument"); if (forcedQ) { instrumentnumber = opts.getInteger("forceinstrument"); if (instrumentnumber < 0 || instrumentnumber > 127) { instrumentnumber = 0; } } else { instrumentnumber = -1; } mine = opts.getInteger("min"); if (mine < 0) { mine = 0; } debugQ = opts.getBoolean("debug"); shortenQ = opts.getBoolean("shorten"); shortenamount = opts.getInteger("shorten"); padQ = !opts.getBoolean("nopad"); humanvolumeQ = opts.getInteger("humanvolume"); fixedvolumeQ = opts.getBoolean("volume"); timeQ = opts.getBoolean("time"); tempospineQ = opts.getBoolean("tempo-spine"); perfvizQ = opts.getBoolean("perfviz"); metricvolumeQ = opts.getInteger("metricvolume"); sforzando = opts.getInteger("sforzando"); idynQ = opts.getBoolean("dyn"); idynoffset = opts.getDouble("dyn"); norestQ = opts.getBoolean("no-rest"); bendQ = opts.getBoolean("bend"); rhysc = opts.getDouble("rhythmic-scaling"); if (bendQ) { bendamt = opts.getDouble("bend"); if (bendamt <= 0.0) { bendamt = 200.0; } } bendpcQ = opts.getBoolean("temperament"); if (bendpcQ) { bendQ = 0; // disable other type of bending (but keep bendamt) forcedQ = 1; // force a timber setting for all channels (piano default) getBendByPcData(bendbypc, opts.getString("temperament")); // for different method, see: http://www.xs4all.nl/~huygensf/scala } } ////////////////////////////// // // getBendByPcData -- // void getBendByPcData(double* bendbypc, const char* filename) { int i, j; for (i=0; i<12; i++) { bendbypc[i] = 0.0; } HumdrumFile temperamentdata; temperamentdata.read(filename); HumdrumFile& td = temperamentdata; int pc; double dcent; for (i=0; i 0 && pc < 12) { bendbypc[pc] = dcent / bendamt; } } } } ////////////////////////////// // // example -- // void example(void) { } ////////////////////////////// // // storeMetaText -- // void storeMetaText(MidiFile& mfile, int track, const char* string, int tick, int metaType) { int i; int length = strlen(string); Array metadata; uchar size[23] = {0}; int lengthsize = makeVLV(size, length); metadata.setSize(2+lengthsize+length); metadata[0] = 0xff; metadata[1] = metaType; for (i=0; i= (1 << 28)) { cout << "Error: number too large to handle" << endl; exit(1); } buffer[0] = (value >> 21) & 0x7f; buffer[1] = (value >> 14) & 0x7f; buffer[2] = (value >> 7) & 0x7f; buffer[3] = (value >> 0) & 0x7f; int i; int flag = 0; int length = -1; for (i=0; i<3; i++) { if (buffer[i] != 0) { flag = 1; } if (flag) { buffer[i] |= 0x80; } if (length == -1 && buffer[i] >= 0x80) { length = 4-i; } } if (length == -1) { length = 1; } if (length < 4) { for (i=0; i >& dynamics, double idynoffset) { int i, j, k; int intdyn; char cdyn; double dyn; int notecount; char buffer[1024] = {0}; dynamics.setSize(infile.getNumLines()); for (i=0; i 127) { intdyn = 127; } cdyn = char(intdyn); dynamics[i].append(cdyn); } } } for (i=0; i mididata; int i, j, k; int ii; int tokencount = 0; char buffer1[1024] = {0}; double pvscoredur = 0.0; int staccatoQ = 0; int accentQ = 0; int sforzandoQ = 0; int volume = defaultvolume; int ttempo; Array freeQ; freeQ.setSize(infile.getMaxTracks()); freeQ.setAll(0); Array > freenotestate; freenotestate.setSize(freeQ.getSize()); for (i=0; i > dynamics; if (idynQ) { getIdynDynamics(infile, dynamics, idynoffset); } else { getDynamics(infile, dynamics, defaultvolume); } /* if (tscaling != 1.0) { ttempo = (int)(60 * tscaling); if (ttempo > 0) { mididata.setSize(6); mididata[0] = 0xff; mididata[1] = 0x51; mididata[2] = 3; ttempo = (int)(60000000.0 / ttempo + 0.5); mididata[3] = (uchar)((ttempo >> 16) & 0xff); mididata[4] = (uchar)((ttempo >> 8) & 0xff); mididata[5] = (uchar)(ttempo & 0xff); outfile.addEvent(0, 0 + offset, mididata); } } */ if (options.getBoolean("comment")) { storeMetaText(outfile, 0, options.getString("comment"), 0); } if (perfvizQ) { // set the tempo to MM 120.0 at tick time 0. mididata.setSize(6); mididata[0] = 0xff; mididata[1] = 0x51; mididata[2] = 3; ttempo = (int)(60000000.0 / 120.0 + 0.5); mididata[3] = (uchar)((ttempo >> 16) & 0xff); mididata[4] = (uchar)((ttempo >> 8) & 0xff); mididata[5] = (uchar)(ttempo & 0xff); outfile.addEvent(0, 0, mididata); } for (i=0; i 0 && !tempospineQ && !perfvizQ) { // cout << "The tempo read was " << tempo << endl; ttempo = (int)(tempo * tscaling + 0.5); mididata.setSize(6); mididata[0] = 0xff; mididata[1] = 0x51; mididata[2] = 3; ttempo = (int)(60000000.0 / ttempo + 0.5); mididata[3] = (uchar)((ttempo >> 16) & 0xff); mididata[4] = (uchar)((ttempo >> 8) & 0xff); mididata[5] = (uchar)(ttempo & 0xff); if (timeQ || perfvizQ) { ontick = getMillisecondTime(infile, i); if (perfvizQ) { ontick = int(ontick * tickfactor + 0.5); } } else { absbeat = infile.getAbsBeat(i); ontick = int(absbeat * outfile.getTicksPerQuarterNote()); } outfile.addEvent(0, ontick + offset, mididata); // outfile.addEvent(0, 10 + offset, mididata); tempo = -1; } for (j=0; j mididata; mididata.setSize(2); mididata[0] = 0xc0 | (0x0f & trackchannel[track]); mididata[1] = instrumentnumber; outfile.addEvent(track, ontick + offset, mididata); continue; } if (strncmp(infile[i][j], "*I", 2) == 0) { storeInstrument(ontick + offset, outfile, infile, i, j, instrumentQ); continue; } if (strncmp(infile[i][j], "*pan=", 5) == 0) { storePan(ontick + offset, outfile, infile, i, j); continue; } // capture info data for PerfViz: int length = strlen(infile[i][j]); int key; if (infile[i][j][length-1] == ':') { key = Convert::kernToBase40(infile[i][j]); if (key != PerfVizNote::key) { printPerfVizKey(key); PerfVizNote::key = key; } } int tstop; int tsbottom; if (sscanf(infile[i][j], "*M%d/%d", &tstop, &tsbottom) == 2) { if (tstop != PerfVizNote::tstop || tsbottom != PerfVizNote::tsbottom) { printPerfVizTimeSig(tstop, tsbottom); PerfVizNote::tstop = tstop; PerfVizNote::tsbottom = tsbottom; } } double approxtempo; if (sscanf(infile[i][j], "*MM%lf", &approxtempo) == 1) { if (approxtempo != PerfVizNote::approxtempo) { printPerfVizTempo(approxtempo); PerfVizNote::approxtempo = approxtempo; } } // info fields to be addressed in the future: // info(beatSubdivisions,[3]). // info(tempoIndication,[andante]). // info(subtitle,[]). if (strcmp(infile[i][j], "*free") == 0) { freeQ[infile[i].getPrimaryTrack(j)-1] = 1; } else if (strcmp(infile[i][j], "*strict") == 0) { freeQ[infile[i].getPrimaryTrack(j)-1] = 0; // turn off any previous notes in the track // started during a free rhythm section ptrack = infile[i].getPrimaryTrack(j) - 1; for (ii=0; ii= 0) { mididata.setSize(3); if (bendpcQ) { int pcchan = freenotestate[ptrack][ii] % 12; if (pcchan >= 9) { pcchan++; } mididata[0] = 0x80 | pcchan; } else { mididata[0] = 0x80 | trackchannel[track]; } mididata[1] = (uchar)freenotestate[ptrack][ii]; mididata[2] = 0; outfile.addEvent(track, ontick + offset + 1, mididata); // added 1 to the previous line for grace notes 7Apr2004 freenotestate[ptrack][ii] = -1; if (ii == freenotestate[ptrack].getSize() - 1) { // shrink-wrap the free note off array freenotestate[ptrack].setSize(ii); break; } } } } } } else if (infile[i].getType() == E_humrec_data_measure) { PerfVizNote::bar += 1; } else if (infile[i].getType() == E_humrec_data) { idyncounter = 0; for (j=0; j idyncounter) { volume = dynamics[i][idyncounter]; } else { // cout << "Warning: bad volume data on line " << i+1 << endl; // cout << "Size of dynamics array is: " // << dynamics[i].getSize() << endl; // cout << "Note counter on line: " << idyncounter << endl; // volume = 64; } } else { volume = dynamics[infile[i].getPrimaryTrack(j)-1][i]; } if (strcmp(infile[i][j], ".") == 0) { continue; } if (infile[i].getExInterpNum(j) != E_KERN_EXINT) { if (timeQ || perfvizQ) { ontick = getMillisecondTime(infile, i); if (perfvizQ) { ontick = int(ontick * tickfactor + 0.5); } } else { absbeat = infile.getAbsBeat(i); ontick = int(absbeat * outfile.getTicksPerQuarterNote()); } track = infile[i].getPrimaryTrack(j); if (storeTextQ) { storeMetaText(outfile, track, infile[i][j], ontick+offset); } continue; } else { // process **kern note events track = infile[i].getPrimaryTrack(j); tokencount = infile[i].getTokenCount(j); ptrack = infile[i].getPrimaryTrack(j) - 1; if (freeQ[ptrack] != 0) { // turn off any previous notes in the track // during a free rhythm section for (ii=0; ii= 0) { mididata.setSize(3); if (bendpcQ) { int pcchan = freenotestate[ptrack][ii] % 12; if (pcchan >= 9) { pcchan++; } mididata[0] = 0x90 | pcchan; } else { mididata[0] = 0x80 | trackchannel[track]; } mididata[1] = (uchar)freenotestate[ptrack][ii]; mididata[2] = 0; outfile.addEvent(track, ontick + offset, mididata); freenotestate[ptrack][ii] = -1; if (ii == freenotestate[ptrack].getSize() - 1) { // shrink-wrap the free note off array freenotestate[ptrack].setSize(ii); break; } } } } for (k=0; k 127) { volume = 127; } if (volume < 1) { volume = 1; } if (plusQ) { volume = setMidiPlusVolume(buffer1); } if (timeQ) { ontick = getMillisecondTime(infile, i); offtick = (int)(ontick + duration + 0.5); } else if (perfvizQ) { ontick = int(getMillisecondTime(infile,i)*tickfactor+0.5); offtick = int(getMillisecondTime(infile,i)*tickfactor + duration*tickfactor + 0.5); } else { absbeat = infile.getAbsBeat(i); ontick = int(absbeat * outfile.getTicksPerQuarterNote()); offtick = int(duration * outfile.getTicksPerQuarterNote()) + ontick; } if (shortenQ) { offtick -= shortenamount; if (offtick - ontick < mine) { offtick = ontick + mine; } } // fix for grace note noteoffs (7 Sep 2004): if (timeQ) { if (offtick <= ontick) { offtick = ontick + 100; } } else if (perfvizQ) { if (offtick <= ontick) { offtick = int(ontick + 100 * tickfactor + 0.5); } } else { if (offtick <= ontick) { offtick = ontick + (int)(infile[i].getDuration() * outfile.getTicksPerQuarterNote()+0.5); } // in case the duration of the line is 0: if (offtick <= ontick) { offtick = ontick + 12; } } if (volume < 0) { volume = 1; } if (volume > 127) { volume = 127; } mididata.setSize(3); if (bendpcQ) { int pcchan = midinote % 12; if (pcchan >= 9) { pcchan++; } mididata[0] = 0x90 | pcchan; } else { mididata[0] = 0x90 | trackchannel[track]; } mididata[1] = midinote; mididata[2] = volume; if (fixedvolumeQ) { mididata[2] = defaultvolume; } outfile.addEvent(track, ontick + offset, mididata); if (bendQ) { checkForBend(outfile, ontick + offset, trackchannel[track], infile, i, j, bendamt); } idyncounter++; if (perfvizQ && PVIZ != NULL) { PerfVizNote pvnote; pvnote.pitch = base40note; pvnote.scoredur = pvscoredur; pvnote.absbeat = infile[i].getAbsBeat(); pvnote.beat = int(infile[i].getBeat()); pvnote.beatfrac = infile[i].getBeat() - pvnote.beat; pvnote.beatdur = pvscoredur; pvnote.vel = volume; pvnote.ontick = ontick; pvnote.offtick = offtick; PVIZ[0] << pvnote; } if (freeQ[ptrack] == 0) { // don't specify the note off duration when rhythm is free. if (bendpcQ) { int pcchan = midinote % 12; if (pcchan >= 9) { pcchan++; } mididata[0] = 0x80 | pcchan; } else { mididata[0] = 0x80 | trackchannel[track]; } outfile.addEvent(track, offtick + offset, mididata); } else { // store the notes to be turned off later storeFreeNote(freenotestate, ptrack, midinote); } } } } } } // now add the end-of-track message to all tracks so that they // end at the same time. mididata.setSize(3); if (timeQ) { ontick = getFileDurationInMilliseconds(infile); ontick += 1000; } else if (perfvizQ) { ontick = getFileDurationInMilliseconds(infile); ontick += 1000; ontick = int(ontick * tickfactor + 0.5); } else { absbeat = infile.getAbsBeat(infile.getNumLines()-1); ontick = int(absbeat * outfile.getTicksPerQuarterNote()); // note that extra time is added so that the last note will not get // cut off by the MIDI player. ontick += 120; } // stupid Microsoft Media player (et al.) will still cut off early, // so add a dummy note-off at end as well... if (options.getBoolean("type0")) { outfile.joinTracks(); if (padQ) { mididata[0] = 0x90; mididata[1] = 0x00; mididata[2] = 0x00; outfile.addEvent(0, ontick-1+offset, mididata); } mididata[0] = 0xff; mididata[1] = 0x2f; mididata[2] = 0x00; outfile.addEvent(0, ontick+offset, mididata); } else { // type 1 MIDI file int trackcount = outfile.getNumTracks(); for (i=0; i 0 && padQ) { mididata[0] = 0x90; mididata[1] = 0x00; mididata[2] = 0x00; outfile.addEvent(i, ontick-1+offset, mididata); } mididata[0] = 0xff; mididata[1] = 0x2f; mididata[2] = 0x00; outfile.addEvent(i, ontick+offset, mididata); } } } ////////////////////////////// // // checkForBend -- // void checkForBend(MidiFile& outfile, int notetick, int channel, HumdrumFile& infile, int row, int col, double scalefactor) { double bendvalue = 0; int i; for (i=col+1; i >& array, int ptrack, int midinote) { int i; int loc = -1; for (i=0; i= 0) { array[ptrack][loc] = midinote; } else { array[ptrack].append(midinote); } } ////////////////////////////// // // reviseInstrumentMidiNumbers -- // void reviseInstrumentMidiNumbers(const char* string) { const char* spaces = " \t\n,:;"; char* buffer = new char[strlen(string) + 1]; strcpy(buffer, string); HumdrumInstrument x; char* pointer = buffer; pointer = strtok(buffer, spaces); int instnum = 0; while (pointer != NULL) { char* humdrumname = pointer; pointer = strtok(NULL, spaces); if (pointer == NULL) { break; } instnum = 0; sscanf(pointer, "%d", &instnum); if (instnum < 0 || instnum > 127) { continue; } x.setGM(humdrumname, instnum); pointer = strtok(NULL, spaces); } } ////////////////////////////// // // setMidiPlusVolume -- store slur and enharmonic spelling information. // based on definition in the book: // Beyond MIDI: The Handbook of Musical Codes. pp. 99-104. // int setMidiPlusVolume(const char* kernnote) { int output = 1 << 6; // check for slurs, but do not worry about separating multiple slurs if (strchr(kernnote, '(') != NULL) { // start of slur A output |= (1 << 2); } else if (strchr(kernnote, ')') != NULL) { // end of slur A output |= (1 << 4); } // set the accidental marking int base40class = Convert::kernToBase40(kernnote) % 40; int midinoteclass = Convert::kernToMidiNoteNumber(kernnote) % 12; int acheck = 0; switch (midinoteclass) { case 0: // C/Dbb/B# switch (base40class) { case 4: acheck = 1; break; // Dbb case 0: acheck = 2; break; // C case 36: acheck = 3; break; // B# default: acheck = 0; } break; case 1: // C#/Db/B## switch (base40class) { case 5: acheck = 1; break; // Db case 1: acheck = 2; break; // C# case 37: acheck = 3; break; // B## default: acheck = 0; } break; case 2: // D/C##/Ebb switch (base40class) { case 10: acheck = 1; break; // Ebb case 6: acheck = 2; break; // D case 2: acheck = 3; break; // C## default: acheck = 0; } break; case 3: // D#/Eb/Fbb switch (base40class) { case 15: acheck = 1; break; // Fbb case 11: acheck = 2; break; // Eb case 7: acheck = 3; break; // D# default: acheck = 0; } break; case 4: // E/Fb/D## switch (base40class) { case 16: acheck = 1; break; // Fb case 12: acheck = 2; break; // E case 8: acheck = 3; break; // D## default: acheck = 0; } break; case 5: // F/E#/Gbb switch (base40class) { case 21: acheck = 1; break; // Gbb case 17: acheck = 2; break; // F case 13: acheck = 3; break; // E# default: acheck = 0; } break; case 6: // F#/Gb/E## switch (base40class) { case 22: acheck = 1; break; // Gb case 18: acheck = 2; break; // F# case 14: acheck = 3; break; // E## default: acheck = 0; } break; case 7: // G/Abb/F## switch (base40class) { case 27: acheck = 1; break; // Abb case 23: acheck = 2; break; // G case 19: acheck = 3; break; // F## default: acheck = 0; } break; case 8: // G#/Ab/F### switch (base40class) { case 28: acheck = 1; break; // Ab case 24: acheck = 2; break; // G# default: acheck = 0; // F### } break; case 9: // A/Bbb/G## switch (base40class) { case 33: acheck = 1; break; // Bbb case 29: acheck = 2; break; // A case 25: acheck = 3; break; // G## default: acheck = 0; } break; case 10: // Bb/A#/Cbb switch (base40class) { case 38: acheck = 1; break; // Cbb case 34: acheck = 2; break; // Bb case 30: acheck = 3; break; // A# default: acheck = 0; } break; case 11: // B/Cf/A## switch (base40class) { case 39: acheck = 1; break; // Cb case 35: acheck = 2; break; // B case 31: acheck = 3; break; // A## default: acheck = 0; } break; } output |= acheck; return output; } ////////////////////////////// // // storePan -- // void storePan(int ontime, MidiFile& outfile, HumdrumFile& infile, int row, int column) { double value = 0.5; int mvalue = 64; sscanf(infile[row][column], "*pan=%lf", &value); if (value <= 1.0) { mvalue = int (value * 128.0); } else { mvalue = int(value + 0.5); } if (mvalue > 127) { mvalue = 127; } else if (mvalue < 0) { mvalue = 0; } int track = infile[row].getPrimaryTrack(column); int channel = 0x0f & trackchannel[track]; //ontime = ontime - 1; //if (ontime < 0) { // ontime = 0; //} Array mididata; mididata.setSize(3); mididata[0] = 0xb0 | channel; mididata[1] = 10; mididata[2] = mvalue; outfile.addEvent(track, ontime, mididata); } ////////////////////////////// // // storeInstrument -- // void storeInstrument(int ontick, MidiFile& mfile, HumdrumFile& infile, int i, int j, int pcQ) { static HumdrumInstrument huminst; int track = infile[i].getPrimaryTrack(j); const char* trackname = huminst.getName(infile[i][j]); int pc = huminst.getGM(infile[i][j]); if (forcedQ) { pc = instrumentnumber; } int channel = 0x0f & trackchannel[track]; // store the program change if requested: Array mididata; mididata.setSize(2); mididata[0] = 0xc0 | channel; mididata[1] = (uchar)pc; if (pcQ && pc >= 0 && !forcedQ) { mfile.addEvent(track, ontick + offset, mididata); } if (!tracknamed[track]) { tracknamed[track] = 1; storeMetaText(mfile, track, trackname, 0, 3); // Track Name } storeMetaText(mfile, track, trackname + offset, ontick, 4); // Inst. Name } ////////////////////////////// // // usage -- // void usage(const char* command) { } ////////////////////////////// // // getDynamics -- // void getDynamics(HumdrumFile& infile, Array >& dynamics, int defaultdynamic) { int maxtracks = infile.getMaxTracks(); Array currentdynamic; currentdynamic.setSize(maxtracks); currentdynamic.setAll(defaultdynamic); Array metlev; infile.analyzeMetricLevel(metlev); Array > crescendos; // -1=decrescendo, +1=crescendo, 0=none crescendos.setSize(maxtracks); Array > accentuation; // v = sf, sfz, fz, sffz accentuation.setSize(maxtracks); dynamics.setSize(maxtracks); int i; int j; for (i=0; i assignments; // dynamic data which controls kern data getDynamicAssignments(infile, assignments); for (i=0; i 1) { cout << "----------------------------------------------" << endl; cout << "Dynamics profile of file:" << endl; } for (i=0; i 1) { cout << (int)dynamics[j][i] << "\t"; } } if (dynamicsQ > 1) { cout << endl; } } if (dynamicsQ > 1) { cout << "----------------------------------------------" << endl; } } ////////////////////////////// // // applyAccentuation -- // char applyAccentuation(int dynamic, int accent) { switch (accent) { case 'v': dynamic += sforzando; break; } if (dynamic > 127) { dynamic = 127; } else if (dynamic <= 0) { dynamic = 1; } return (char)dynamic; } /////////////////////////////// // // adjustVolumeMetric -- adjust the attack volume based on the // metric position of the note (on the beat, off the beat, on // a metrically strong beat). // char adjustVolumeMetric(int startvol, int delta, double metricpos) { if (metricpos > 0.0) { startvol += delta; } else if (metricpos < 0) { startvol -= delta; } if (startvol <= 0) { startvol = 1; } else if (startvol > 127) { startvol = 127; } return (char)startvol; } //////////////////////////////// // // adjustVolumeHuman -- add a randomness to the volume // char adjustVolumeHuman(int startvol, int delta) { int randval; #ifndef VISUAL randval = lrand48() % (delta * 2 + 1) - delta; #else randval = rand() % (delta * 2 + 1) - delta; #endif startvol += randval; if (startvol <= 0) { startvol = 1; } if (startvol > 127) { startvol = 127; } return (char)startvol; } ////////////////////////////// // // processCrescDecresc -- adjust attack velocities based on the // crescendos and decrescendos found in the file. // void processCrescDecresc(HumdrumFile& infile, Array >& dynamics, Array >& crescendos) { int i; for (i=0; i& dyn, Array& cresc) { int direction = 0; int i; int ii; for (i=0; i 0) { direction = +1; } else if (cresc[i] < 0) { direction = -1; } else { direction = 0; } ii = findtermination(dyn, cresc, i); generateInterpolation(infile, dyn, cresc, i, ii, direction); // skip over calm water: i = ii - 1; } } } ////////////////////////////// // // generateInterpolation -- do the actual interpolation work. // void generateInterpolation(HumdrumFile& infile, Array& dyn, Array& cresc, int startline, int stopline, int direction) { double startbeat = infile[startline].getAbsBeat(); double stopbeat = infile[stopline].getAbsBeat(); if (startbeat == stopbeat) { // nothing to do return; } int startvel = dyn[startline]; int stopvel = dyn[stopline]; if (startvel < 0) startvel = -startvel; if (stopvel < 0) stopvel = -stopvel; if (stopvel == startvel) { if (direction > 0) { stopvel += 10; } else { stopvel -= 10; } } else if (stopvel>startvel && direction<0) { stopvel = startvel - 10; } else if (stopvel0) { stopvel = startvel + 10; } int i; double slope = (double)(stopvel-startvel)/(double)(stopbeat-startbeat); double currvel = 0.0; double currbeat = 0.0; for (i=startline+1; i 127.0) { currvel = 127.0; } if (currvel < 0.0) { currvel = 0.0; } dyn[i] = (char)(currvel+0.5); } } ////////////////////////////// // // findtermination -- // int findtermination(Array& dyn, Array& cresc, int start) { int i; for (i=start+1; i=dyn.getSize()) { i = dyn.getSize()-1; } return i; } ////////////////////////////// // // getNewDynamics -- // void getNewDynamics(Array& currentdynamic, Array& assignments, HumdrumFile& infile, int line, Array >& crescendos, Array >& accentuation) { if (infile[line].getType() != E_humrec_data) { return; } int i; int j; int k; int length; int track = -1; int dval = -1; int accent = 0; int cresval = 0; char buffer[2048] = {0}; for (i=0; i and < on the same line. if (strchr(infile[line][i], '<') != NULL) { cresval = +1; } else if (strchr(infile[line][i], '>') != NULL) { cresval = -1; } // look for accentuatnon if (strchr(infile[line][i], 'v') != NULL) { accent = 'v'; } if (dval > 0 || cresval !=0) { for (j=0; j 0) { currentdynamic[j] = -dval; } if (cresval != 0) { crescendos[j][line] = cresval; } if (accent != 0) { accentuation[j][line] = accent; } } } } } } ////////////////////////////// // // getDynamicAssignments -- // void getDynamicAssignments(HumdrumFile& infile, Array& assignments) { assignments.setSize(infile.getMaxTracks()); assignments.setAll(-1); // *staff assignment assumed to be all on one line, and before // any note data. int staffline = -1; int exinterp = -1; int i; int j; for (i=0; i=0; i--) { if (strcmp(infile[exinterp][i], "**dynam") == 0) { currdyn = i; } if (currdyn >= 0) { assignments[i] = infile[exinterp].getPrimaryTrack(currdyn)-1; } else { assignments[i] = -1; } } if (staffline == -1) { // there is no staff assignments in the file, so attach any // dynamics to the first **kern spine on the left. return; } // there is a *staff assignment line, so follow the directions found there Array > staffvalues; getStaffValues(infile, staffline, staffvalues); int k; // assignments.setSize(infile.getMaxTracks()); for (i=0; i 0) { if (!isdigit(cptr[0])) { break; } value = strtol(cptr, &newcptr, 10); staffvalues[i].append(value); cptr = newcptr; if (cptr[0] != '/') { break; } else { cptr = cptr + 1; } } } } } } ////////////////////////////// // // getMillisecondTime -- return the time in milliseconds found // on the current line in the file. // int getMillisecondTime(HumdrumFile& infile, int line) { double output = -100; int flag; int i; while ((line < infile.getNumLines()) && (infile[line].getType() != E_humrec_data)) { line++; } if (line >= infile.getNumLines() - 1) { return getFileDurationInMilliseconds(infile); } for (i=0; i= (stopbeat-0.0002)) { for (j=0; j 0) && (infile[lastdataline].getType() != E_humrec_data)) { lastdataline--; } double ctime = getMillisecondTime(infile, lastdataline); double cbeat = infile[lastdataline].getAbsBeat(); double nbeat = infile[infile.getNumLines()-1].getAbsBeat(); int preindex = -1; int i; for (i=lastdataline-1; i>=0; i--) { if (infile[i].getType() != E_humrec_data) { continue; } preindex = i; break; } if (preindex < 0) { return -1; } double pbeat = infile[preindex].getAbsBeat(); double ptime = -1.0; for (i=0; i> "; // cout << "<< DB2 = " << db2 << " >> "; // cout << "<< DT1 = " << dt1 << " >> "; double result = ctime + db2 * dt1 / db1; return (int)(result + 0.5); } /////////////////////////////////////////////////////////////////////////// // // PerfViz related functions // ostream& operator<<(ostream& out, PerfVizNote& note) { int anchor = ++note.sid; int measure = note.bar; int beat = note.beat; int velocity = note.vel; int onset = note.ontick; // tick on time for note int offset = note.offtick; // tick off time for note int adjoffset = offset; // not taking pedalling into account double absbeaton = note.absbeat; double absbeatoff = note.absbeat + note.scoredur; char pitchname[1024] = {0}; Convert::base40ToPerfViz(pitchname, note.pitch); double soffset = note.beatfrac; double dur = note.beatdur; out << "snote(n" << anchor << "," << pitchname << "," << measure << ":" << beat << ","; printRational(out, soffset); out << ","; printRational(out, dur); out << ","; out << absbeaton << "," << absbeatoff << "," << "[])-"; out << "note(" << anchor << "," << pitchname << "," << onset << "," << offset << "," << adjoffset << "," << velocity << ").\n"; return out; } ////////////////////////////// // // printRational -- // void printRational(ostream& out, double value) { if (value <= 0.0002) { out << "0"; return; } if (fabs(value-0.5) < 0.0002) { out << "1/2"; } else if (fabs(value - 0.25) < 0.0002) { out << "1/16"; } else if (fabs(value - 1.5) < 0.0002) { out << "3/2"; } else if (fabs(value - 2.5) < 0.0002) { out << "5/2"; } else if (fabs(value - 0.75) < 0.0002) { out << "3/16"; } else if (fabs(value - 0.16667) < 0.0002) { out << "1/8/3"; } else if (fabs(value - int(value)) <= 0.0002) { out << int(value); } else if (fabs(value - int(value)) >= 0.9998) { out << int(value)+1; } else if (value == 1.0) { out << "1"; } else if (fabs(value - 0.3333) < 0.0002) { out << "1/4/3"; } else if (fabs(value - 0.6667) < 0.0002) { out << "2/4/3"; } else { out << "{{" << value << "}}"; } } ////////////////////////////// // // writePerfVizMatchFile -- // void writePerfVizMatchFile(const char* filename, SSTREAM& contents) { ofstream outputfile(filename); contents << ends; outputfile << contents.CSTRING; outputfile.close(); } ////////////////////////////// // // printPerfVizKey -- // void printPerfVizKey(int key) { int octave = key / 40; int diatonic = Convert::base40ToDiatonic(key); int accidental = Convert::base40ToAccidental(key); if (PVIZ == NULL) { return; } PVIZ[0] << "info(keySignature,["; switch (diatonic) { case 0: PVIZ[0] << "c"; break; case 1: PVIZ[0] << "d"; break; case 2: PVIZ[0] << "e"; break; case 3: PVIZ[0] << "f"; break; case 4: PVIZ[0] << "g"; break; case 5: PVIZ[0] << "a"; break; case 6: PVIZ[0] << "b"; break; default: PVIZ[0] << "X"; } switch (accidental) { case -2: PVIZ[0] << "bb"; break; case -1: PVIZ[0] << "b"; break; case 0: PVIZ[0] << "n"; break; case 1: PVIZ[0] << "#"; break; case 2: PVIZ[0] << "##"; break; } PVIZ[0] << ","; if (octave == 3) { PVIZ[0] << "major"; } else if (octave == 4) { PVIZ[0] << "minor"; } else { PVIZ[0] << "locrian"; } PVIZ[0] << "]).\n"; } ////////////////////////////// // // printPerfVizTimeSig -- // void printPerfVizTimeSig(int tstop, int tsbottom) { if (PVIZ == NULL) { return; } PVIZ[0] << "info(timeSignature," << tstop << "/" << tsbottom << ").\n"; } ////////////////////////////// // // printPerfVizTempo -- // void printPerfVizTempo(double approxtempo) { if (PVIZ == NULL) { return; } PVIZ[0] << "info(approximateTempo," << approxtempo << ").\n"; } // md5sum: 2d643880b1eb43b89e42cb1ba5801fd7 hum2mid.cpp [20090518]