923 lines
26 KiB
C++
923 lines
26 KiB
C++
//
|
|
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
|
// 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 <cctype>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
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<Array<MidiInfo> >& mididata,
|
|
MidiFile& midifile);
|
|
void storenote (MidiInfo& info, Array<Array<MidiInfo> >& mididata,
|
|
int i, int currtick);
|
|
void printKernData (Array<Array<MidiInfo> >& mididata,
|
|
MidiFile& midifile, Array<MetaInfo>& metadata);
|
|
void identifyChords (Array<Array<MidiInfo> >& mididata);
|
|
void correctdurations (Array<Array<MidiInfo> >& 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<MetaInfo>& metadata);
|
|
void printMetaData (ostream& out, Array<MetaInfo>& 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<Array<MidiInfo> > mididata;
|
|
getMidiData(mididata, midifile);
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// storenote -- store a MIDI note into the data array for later processing.
|
|
//
|
|
|
|
void storenote(MidiInfo& info, Array<Array<MidiInfo> >& 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<Array<MidiInfo> >& mididata, MidiFile& midifile) {
|
|
mididata.setSize(midifile.getNumTracks());
|
|
int i;
|
|
int j;
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
mididata[i].setSize(10000);
|
|
mididata[i].setGrowth(10000);
|
|
mididata[i].setSize(0);
|
|
}
|
|
|
|
Array<MetaInfo> metadata;
|
|
metadata.setSize(1000);
|
|
metadata.setGrowth(1000);
|
|
metadata.setSize(0);
|
|
|
|
Array<Array<MidiInfo> > notestates;
|
|
notestates.setSize(midifile.getNumTracks());
|
|
for (i=0; i<notestates.getSize(); i++) {
|
|
notestates[i].setSize(128);
|
|
notestates[i].allowGrowth(0);
|
|
}
|
|
|
|
// extract a list of notes in the MIDI file along with their durations
|
|
int k;
|
|
for (i=0; i<midifile.getNumTracks(); i++) {
|
|
for (j=0; j<midifile.getNumEvents(i); j++) {
|
|
if (((midifile.getEvent(i, j).data[0] & 0xf0) == 0x90) &&
|
|
(midifile.getEvent(i, j).data[2] > 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.getSize(); i++) {
|
|
cout << "Track " << i << endl;
|
|
for (j=0; j<mididata[i].getSize(); j++) {
|
|
cout << "\tNote: pitch = "
|
|
<< (int)midifile.getEvent(i, mididata[i][j].index).data[1]
|
|
<< "\tduration = "
|
|
<< (double)mididata[i][j].tickdur/midifile.getTicksPerQuarterNote()
|
|
<< endl;
|
|
}
|
|
}
|
|
*/
|
|
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
qsort(mididata[i].getBase(), mididata[i].getSize(), sizeof(MidiInfo),
|
|
MidiInfoCompare);
|
|
}
|
|
identifyChords(mididata);
|
|
correctdurations(mididata, midifile.getTicksPerQuarterNote());
|
|
|
|
printKernData(mididata, midifile, metadata);
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// processMetaMessage --
|
|
//
|
|
|
|
void processMetaMessage(MidiFile& midifile, int track, int event,
|
|
Array<MetaInfo>& metadata) {
|
|
MetaInfo tempmeta;
|
|
tempmeta.type = midifile.getEvent(track, event).data[1];
|
|
tempmeta.starttick = midifile.getEvent(track, event).time;
|
|
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 = (uchar)midifile.getEvent(track, event).data[2];
|
|
for (d=0; d<tempmeta.tsize; d++) {
|
|
tempmeta.text[d] = midifile.getEvent(track, event).data[3+d];
|
|
}
|
|
tempmeta.text[tempmeta.tsize] = '\0';
|
|
break;
|
|
case 0x07: // cue point
|
|
tempmeta.tsize = (uchar)midifile.getEvent(track, event).data[2];
|
|
for (d=0; d<tempmeta.tsize; d++) {
|
|
tempmeta.text[d] = midifile.getEvent(track, event).data[3+d];
|
|
}
|
|
tempmeta.text[tempmeta.tsize] = '\0';
|
|
break;
|
|
case 0x20: // MIDI channel prefix
|
|
break;
|
|
case 0x54: // SMPTE Offset
|
|
break;
|
|
case 0x2F: // end of track marker
|
|
break;
|
|
case 0x7F: // sequencer-specific meta event
|
|
break;
|
|
case 0x51: // tempo marking
|
|
tempo = midifile.getEvent(track, event).data[3];
|
|
tempo = (tempo << 8) | midifile.getEvent(track, event).data[4];
|
|
tempo = (tempo << 8) | midifile.getEvent(track, event).data[5];
|
|
tempmeta.tempo = (int)(60.0/tempo*1000000.0 + 0.5);
|
|
break;
|
|
case 0x58: // time signature
|
|
tempmeta.numerator = midifile.getEvent(track, event).data[3];
|
|
tempmeta.denominator = (int)pow(2.0,
|
|
midifile.getEvent(track, event).data[4]);
|
|
break;
|
|
case 0x59: // key signature
|
|
tempmeta.keysig = midifile.getEvent(track, event).data[3];
|
|
tempmeta.mode = midifile.getEvent(track, event).data[4];
|
|
}
|
|
|
|
metadata.append(tempmeta);
|
|
// cout << "!!meta:" << hex << tempmeta.type << dec << endl;
|
|
}
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// correctdurations -- if notes are turned off earlier than the
|
|
// next note (or the next note is turned on before the current
|
|
// note is turned off, then adjust the tick values so that the
|
|
// rhythm values might make sense.
|
|
//
|
|
|
|
void correctdurations (Array<Array<MidiInfo> >& mididata, int tpq) {
|
|
int i, j;
|
|
double duration = 0.0;
|
|
double fraction = 0.0;
|
|
int count = 0;
|
|
int durationcorrection = 0;
|
|
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
if (mididata[i].getSize() == 0) {
|
|
continue;
|
|
}
|
|
for (j=0; j<mididata[i].getSize(); 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<mididata[i].getSize()-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(Array<Array<MidiInfo> >& mididata) {
|
|
int i, j;
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
for (j=1; j<mididata[i].getSize(); 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) {
|
|
const MidiInfo& A = *static_cast<const MidiInfo*>(a);
|
|
const MidiInfo& B = *static_cast<const 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(Array<Array<MidiInfo> >& mididata, MidiFile& midifile,
|
|
Array<MetaInfo>& metadata) {
|
|
int i;
|
|
int j;
|
|
|
|
Array<int> kerntrack;
|
|
kerntrack.setSize(mididata.getSize());
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
if (mididata[i].getSize() == 0) {
|
|
kerntrack[i] = 0;
|
|
} else {
|
|
kerntrack[i] = 1;
|
|
}
|
|
}
|
|
|
|
Array<int> restcorrection;
|
|
restcorrection.setSize(mididata.getSize());
|
|
restcorrection.setGrowth(0);
|
|
restcorrection.setAll(0);
|
|
int maxticks = 0;
|
|
int testticks = 0;
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
if (mididata[i].getSize() > 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<mididata.getSize(); i++) {
|
|
if (kerntrack[i] == 0) {
|
|
continue;
|
|
}
|
|
testticks = mididata[i][mididata[i].getSize()-1].starttick +
|
|
mididata[i][mididata[i].getSize()-1].tickdur;
|
|
restcorrection[i] = maxticks - testticks;
|
|
}
|
|
|
|
Array<int> startrestcorrection;
|
|
startrestcorrection.setSize(mididata.getSize());
|
|
startrestcorrection.setGrowth(0);
|
|
startrestcorrection.setAll(0);
|
|
int minstartticks = 999999;
|
|
for (i=0; i<mididata.getSize(); i++) {
|
|
if (kerntrack[i] == 0) {
|
|
continue;
|
|
}
|
|
if (minstartticks > mididata[i][0].starttick) {
|
|
minstartticks = mididata[i][0].starttick;
|
|
}
|
|
}
|
|
for (i=0; i<mididata.getSize(); 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;
|
|
|
|
int ii;
|
|
for (ii=0; ii<mididata.getSize(); ii++) {
|
|
// 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 = mididata.getSize() - 1 - ii;
|
|
}
|
|
|
|
if (kerntrack[i] == 0) {
|
|
continue;
|
|
}
|
|
if ((extracttrack > -1) && (i != (extracttrack-1))) {
|
|
continue;
|
|
}
|
|
buffstream = new stringstream;
|
|
(*buffstream) << "**kern\n";
|
|
metaindex = 0;
|
|
for (j=0; j<mididata[i].getSize(); j++) {
|
|
while ((metaindex < metadata.getSize()) &&
|
|
(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 < 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<MetaInfo>& metadata, int metaindex) {
|
|
int ii;
|
|
int count = 0;
|
|
switch (metadata[metaindex].type) {
|
|
case 0x06: // marker
|
|
break; // ignore for now
|
|
|
|
for (ii=0; ii<metadata[metaindex].tsize; ii++) {
|
|
if (std::isprint(metadata[metaindex].text[ii])) {
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0) {
|
|
out << "! ";
|
|
for (ii=0; ii<metadata[metaindex].tsize; ii++) {
|
|
if (std::isprint(metadata[metaindex].text[ii])) {
|
|
out << metadata[metaindex].text[ii];
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
break;
|
|
|
|
case 0x07: // cue point
|
|
break; // ignore for now
|
|
|
|
for (ii=0; ii<metadata[metaindex].tsize; ii++) {
|
|
if (std::isprint(metadata[metaindex].text[ii])) {
|
|
count++;
|
|
}
|
|
}
|
|
if (count > 0) {
|
|
out << "! ";
|
|
for (ii=0; ii<metadata[metaindex].tsize; ii++) {
|
|
if (std::isprint(metadata[metaindex].text[ii])) {
|
|
out << metadata[metaindex].text[ii];
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
break;
|
|
|
|
case 0x51: // tempo marking
|
|
out << "*MM" << metadata[metaindex].tempo << "\n";
|
|
break;
|
|
case 0x58: // time signature
|
|
out << "*M" << metadata[metaindex].numerator << "/"
|
|
<< metadata[metaindex].denominator << "\n";
|
|
break;
|
|
case 0x59: // key signature
|
|
out << Convert::keyNumberToKern(metadata[metaindex].keysig) << "\n";
|
|
break;
|
|
// default:
|
|
// out << "!meta:" << hex << metadata[metaindex].type << dec << endl;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printRestCorrection --
|
|
//
|
|
|
|
void printRestCorrection (ostream& out, int restcorr, int tqp) {
|
|
double totaldur = (double)restcorr/tqp;
|
|
while (totaldur >= 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<hfile.getNumLines(); i++) {
|
|
if (hfile[i].isData()) {
|
|
bpos = (hfile[i].getAbsBeat() - pickupbeat) / timesigtop*4.0 / timesigbottom;
|
|
startmeasure = (int)bpos;
|
|
endmeasure = (int)(bpos + Convert::kernToDuration(hfile[i][0]) /
|
|
timesigtop*4.0 / timesigbottom - 0.001);
|
|
if (fabs(bpos - (int)bpos) < 0.001) {
|
|
out << "=";
|
|
if (measurenumQ) {
|
|
out << measurenum;
|
|
}
|
|
if ((measurenum == 1) && (pickupbeat = 0.0)) {
|
|
out << "-"; // non-printed barline
|
|
}
|
|
measurenum++;
|
|
out << "\n";
|
|
}
|
|
if (startmeasure == endmeasure) {
|
|
out << hfile[i] << "\n";
|
|
} else {
|
|
firstdur = ((double)endmeasure * timesigtop /
|
|
4.0 * timesigbottom) - hfile[i].getAbsBeat();
|
|
splitDataWithMeasure(out, hfile, i, measurenum, firstdur);
|
|
}
|
|
} else {
|
|
out << hfile[i] << "\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// splitDataWithMeasure --
|
|
//
|
|
|
|
void splitDataWithMeasure(ostream& out, HumdrumFile& hfile, int index,
|
|
int& measurenum, double firstdur) {
|
|
double seconddur = Convert::kernToDuration(hfile[index][0]) - firstdur;
|
|
int i;
|
|
int tokencount = hfile[index].getTokenCount(0);
|
|
char buffer[1024] = {0};
|
|
char tokenbuffer[1024] = {0};
|
|
int base40 = 0;
|
|
|
|
// print first part
|
|
for (i=0; i<tokencount; i++) {
|
|
hfile[index].getToken(tokenbuffer, 0, i);
|
|
base40 = Convert::kernToBase40(tokenbuffer);
|
|
if (base40 >= 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<tokencount-1) {
|
|
out << ' ';
|
|
}
|
|
}
|
|
out << "\n";
|
|
|
|
// print barline
|
|
out << "=";
|
|
if (measurenumQ) {
|
|
out << measurenum;
|
|
}
|
|
if ((measurenum == 1) && (pickupbeat = 0.0)) {
|
|
out << "-"; // non-printed barline
|
|
}
|
|
out << "\n";
|
|
measurenum++;
|
|
|
|
// print second part
|
|
for (i=0; i<tokencount; i++) {
|
|
hfile[index].getToken(tokenbuffer, 0, i);
|
|
base40 = Convert::kernToBase40(tokenbuffer);
|
|
out << Convert::durationToKernRhythm(buffer, seconddur);
|
|
if (base40 >= 0) { // not a rest
|
|
out << Convert::base40ToKern(buffer, base40);
|
|
out << ']';
|
|
} else {
|
|
out << "r";
|
|
}
|
|
if (i<tokencount-1) {
|
|
out << ' ';
|
|
}
|
|
}
|
|
out << "\n";
|
|
|
|
}
|
|
|
|
|
|
/* Basic Documentation:
|
|
|
|
Here are the options available with the mid2hum program:
|
|
|
|
-s == print the voices serially. This needs to be done when
|
|
There are rhythm errors which prevent the file from being
|
|
printed in score format. You will need to correct
|
|
the rhythm in the individual parts, and then use
|
|
the Humdrum assemble command to combine the files together.
|
|
-r == output the Humdrum spines in reverse ordering. The default
|
|
method is from the last track to the first track in the
|
|
MIDI file which usually corresponds to the lowest part
|
|
to highest part respectively which is the standard
|
|
ordering of parts in a Humdrum file.
|
|
-M == don't print measure numbers. This is useful if measure
|
|
numbers need to be edited separately, or there was a
|
|
measure counting problem in the program.
|
|
-p 0.0 == specify a pickup-duration with which the music starts.
|
|
The duration specified after the -p option is the number
|
|
of quarter notes in the pickup beat(s). 1.0 means one
|
|
quarter note, 0.5 is one half note, 1.5 is a dotted
|
|
quarter note, etc.
|
|
-t -1 == extract and convert only specified track. Track 1 is
|
|
extracted with -t 1, Track 2 with -t 2. Some tracks
|
|
do not contain data, and will have an empty conversion.
|
|
This is typical of a Type 1 MIDI file's first track which
|
|
is usually used for control data.
|
|
-q 0.25 == often score-based MIDI files contain durations which are
|
|
not precisely legato, and a small rest is placed after
|
|
the note. This option quantizes the ending position of
|
|
a note to the nearest rhythmic value (by default to the
|
|
nearest sixteenth note.)
|
|
--version=b == print when the program was compiled, and what version
|
|
is it.
|
|
--options == print a list of all possible command-line options for
|
|
the program.
|
|
|
|
*/
|
|
|
|
|
|
|