// // Programmer: Craig Stuart Sapp // Creation Date: Fri Feb 19 22:25:22 PST 2016 // Last Modified: Sat Feb 27 18:16:39 PST 2016 // Filename: tools/mid2svg.cpp // URL: https://github.com/craigsapp/midifile/blob/master/tools/mid2svg.cpp // Syntax: C++11 // vim: ts=3 // // Description: Convert a MIDI file into an SVG piano roll. // #include "MidiFile.h" #include "Options.h" #include #include #include #include #include #include using namespace std; using namespace smf; void checkOptions (Options& opts, int argc, char* argv[]); void usage (const char* command); void example (void); void convertMidiFileToSvg (stringstream& output, MidiFile& midifile, Options& options); int hasNotes (MidiEventList& eventlist); void getMinMaxPitch (const MidiFile& midifile, int& minpitch, int &maxpitch); void getMinMaxTrackPitch (const MidiEventList& evl, int& minpitch, int &maxpitch); double getMaxTime (const MidiFile& midifile); vector getTrackHues (MidiFile& midifile); void drawNote (ostream& out, MidiFile& midifile, int i, int j, int dataQ, int minpitch, int maxpitch); void drawLines (ostream& out, MidiFile& midifile, vector& hues, Options& options); void printLineToNextNote (ostream& out, MidiFile& midifile, int track, int index, Options& options); void drawStaves (ostream& out, double staffwidth, const string& staffcolor, double totalduration); int base12ToBase7 (int pitch); void printDoubleClass (ostream& out, double value); void makeMappings (vector& mapping, const string& mapstring); void drawNoteShape (ostream& out, string& shape, double x, double y, double width, double height); void drawRectangle (ostream& out, double x, double y, double width, double height); void drawDiamond (ostream& out, double x, double y, double width, double height); void drawEyelid (ostream& out, double x, double y, double width, double height); void drawPlus (ostream& out, double x, double y, double width, double height); void drawOval (ostream& out, double x, double y, double width, double height); void drawAntiOval (ostream& out, double x, double y, double width, double height); void drawRound1 (ostream& out, double x, double y, double width, double height); void drawRound2 (ostream& out, double x, double y, double width, double height); void drawRound3 (ostream& out, double x, double y, double width, double height); void drawRound4 (ostream& out, double x, double y, double width, double height); void drawAntiRound (ostream& out, double x, double y, double width, double height); void drawAntiRound1 (ostream& out, double x, double y, double width, double height); void drawAntiRound2 (ostream& out, double x, double y, double width, double height); void drawAntiRound3 (ostream& out, double x, double y, double width, double height); void drawAntiRound4 (ostream& out, double x, double y, double width, double height); void drawCurvedInner (ostream& out, double x, double y, double width, double height); void drawCurvedInner1 (ostream& out, double x, double y, double width, double height); void drawCurvedInner2 (ostream& out, double x, double y, double width, double height); void drawCurvedInner3 (ostream& out, double x, double y, double width, double height); void drawCurvedInner4 (ostream& out, double x, double y, double width, double height); void drawCurvedOuter (ostream& out, double x, double y, double width, double height); void drawCurvedOuter1 (ostream& out, double x, double y, double width, double height); void drawCurvedOuter2 (ostream& out, double x, double y, double width, double height); void drawCurvedOuter3 (ostream& out, double x, double y, double width, double height); void drawCurvedOuter4 (ostream& out, double x, double y, double width, double height); void drawTriangleUp (ostream& out, double x, double y, double width, double height); void drawTriangleDown (ostream& out, double x, double y, double width, double height); void drawTriangleLeft (ostream& out, double x, double y, double width, double height); void drawTriangleRight (ostream& out, double x, double y, double width, double height); void drawTriangleRoundLeft (ostream& out, double x, double y, double width, double height); void drawTriangleRoundRight(ostream& out, double x, double y, double width, double height); void drawHexThick (ostream& out, double x, double y, double width, double height); void drawHexThin (ostream& out, double x, double y, double width, double height); string getTrackShape (int track); void drawClefs (ostream& out); void drawBrace (ostream& out); // User interface variables: Options options; int dataQ = 0; // used with -d option int roundedQ = 0; // used with -r option int darkQ = 0; // used with --dark option int bwQ = 0; // used with --bw option double Scale = 1.0; // used with -s option double Border = 2.0; // used with -b option double Opacity = 0.75; // used with -o option double drumQ = 0; // used with --drum option int lineQ = 0; // used with -l option int curveQ = 0; // used with --cl option int radiusQ = 0; // used with --rl option double radius = 1.0; // used with --rl option int staffQ = 0; // used with --staff option int clefQ = 0; // used with --clef option int braceQ = 0; // used with --clef option int diatonicQ = 0; // used with --diatonic option int grandQ = 0; // used with --gs option int finalQ = 0; // used with -f option int doubleQ = 0; // used with --double option int transparentQ = 1; // used with -T option bool velocitybQ = false; // used with -v option double ClefFactor = 6; double StaffThickness = 2.0; // used with --staff-width double LineThickness = 2.0; // used with --line-width string StaffColor = "#555555"; // used with -staff-color string ClefColor = "#555555"; // used with -clef-color double MaxRest = 4.0; // used with --max-rest option double EndSpace = 0.0; // used with -e option int percmapQ = 0; // used with --perc option double AspectRatio = 2.5; // used with -a option vector PercussionMap; // used with --perc option vector Shapes; ////////////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { checkOptions(options, argc, argv); MidiFile midifile; if (options.getArgCount() == 0) { midifile.read(cin); } else { midifile.read(options.getArg(1)); } stringstream notes; int minpitch = -1; int maxpitch = -1; getMinMaxPitch(midifile, minpitch, maxpitch); convertMidiFileToSvg(notes, midifile, options); double minx = 0; double miny = minpitch; double width = getMaxTime(midifile); double height = maxpitch - minpitch + 1; double clefwidth = 0; if (clefQ) { clefwidth = 180 / 12; } cout << "\n"; cout << "\n"; cout << "\n"; // Graphics setup: // This filter is used to show overlap between notes: if (transparentQ && !bwQ) { cout << "\n"; cout << "\t\n"; cout << "\t\t\n"; cout << "\t\n"; cout << "\n"; } if (darkQ && !bwQ) { cout << "\n"; } cout << "\n"; // Print the piano roll note boxes: cout << notes.str(); // Graphics setup end closing: cout << "\n"; cout << "\n"; cout << "\n"; return 0; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////// // // convetMidiFileToSvg -- // void convertMidiFileToSvg(stringstream& output, MidiFile& midifile, Options& options) { midifile.linkNotePairs(); // first link note-ons to note-offs midifile.doTimeAnalysis(); // then create ticks to seconds mapping stringstream notes; if (staffQ) { drawStaves(notes, StaffThickness, StaffColor, midifile.getFileDurationInSeconds()); if (clefQ) { drawClefs(notes); } if (braceQ) { drawBrace(notes); } } string strokecolor = options.getString("stroke-color"); double strokewidth = options.getDouble("stroke-width") * Scale; notes << "\t\n"; vector trackhues = getTrackHues(midifile); if (lineQ) { drawLines(notes, midifile, trackhues, options); } int minpitch = 0; int maxpitch = 0; // Draw background for increasing contrast of notes and background // (needed due to constant opacity filter): int track = 0; if (!bwQ) { for (int i=midifile.size()-1; i>=0; i--) { if (!hasNotes(midifile[i])) { continue; } getMinMaxTrackPitch(midifile[i], minpitch, maxpitch); track = i; notes << "\t\t= 0.0) { if (bwQ) { notes << " style=\"" << "fill:none;" << "\""; } else if (darkQ) { notes << " style=\"" << "fill:white;" << "\""; } else { notes << " style=\"" << "fill:black;" << "\""; } } notes << " >\n"; for (int j=0; j\n"; } } // draw the actual notes: for (int i=midifile.size()-1; i>=0; i--) { if (!hasNotes(midifile[i])) { continue; } track = i; getMinMaxTrackPitch(midifile[i], minpitch, maxpitch); notes << "\t\t= 0.0) { if (bwQ) { notes << " style=\"" << " fill:white;" << "\""; } else { notes << " style=\"" << "opacity:" << Opacity << ";" << " fill:hsl(" << trackhues[i] << ", 100%, 75%);" << "\""; } } notes << " >\n"; for (int j=0; j\n"; } notes << "\t\n"; output << notes.str(); } ////////////////////////////// // // drawBrace -- Draw curley brace // void drawBrace(ostream& out) { double unscale = 2.5 / AspectRatio; string fill = ClefColor; string stroke = ClefColor; if (bwQ) { fill = "transparent"; } double strokewidth = StaffThickness * ClefFactor; double xpos = 30.5; double ypos = -324; out << "\n"; out << " \n" " \n" " \n"; out << "\n"; } ////////////////////////////// // // drawClefs -- // void drawClefs(ostream& out) { string fill = ClefColor; string stroke = ClefColor; double unscale = 2.5 / AspectRatio; double strokewidth = StaffThickness * ClefFactor; if (bwQ) { fill = "transparent"; } out << "\n"; out << " \n" " \n" " \n"; out << " \n" " \n" " \n" " \n" " \n" " \n" " \n"; out << "\n"; } ////////////////////////////// // // drawStaves -- // void drawStaves(ostream& out, double staffwidth, const string& staffcolor, double totalduration) { vector vpos; double unscale = 2.5 / AspectRatio; if (diatonicQ) { vpos.insert(vpos.end(), {37.5, 39.5, 41.5, 43.5, 45.5}); // treble clef vpos.insert(vpos.end(), {25.5, 27.5, 29.5, 31.5, 33.5}); // bass clef } else { vpos.insert(vpos.end(), {64.5, 67.5, 71.5, 74.5, 77.5}); // treble clef vpos.insert(vpos.end(), {43.5, 47.5, 50.5, 53.5, 57.5}); // bass clef } out << "\t\n"; double start = 0.0; if (clefQ) { start = -4.65 * unscale; } double endx = totalduration + EndSpace; for (int i=0; i<(int)vpos.size(); i++) { out << "\t\t\n"; } double maxy = *max_element(vpos.begin(), vpos.end()); double miny = *min_element(vpos.begin(), vpos.end()); double thickness = 0.5 * unscale; if (finalQ) { out << "\t\t\n"; out << "\t\t\n"; } else if (doubleQ) { out << "\t\t\n"; out << "\t\t\n"; } if (braceQ) { // staffwidth = 5 * staffwidth; out << "\t\t\n"; } out << "\t\n"; } ////////////////////////////// // // drawLines -- Draw lines to connect notes. // void drawLines(ostream& out, MidiFile& midifile, vector& hues, Options& options) { int dashing = options.getBoolean("dash"); int track = 0; for (int i=midifile.size()-1; i>=0; i--) { if (!hasNotes(midifile[i])) { continue; } track = i; string color = "hsl(" + to_string(hues[i]) + ", 100%, 75%)"; if (bwQ) { color = StaffColor; } out << "\t\t\n"; for (int j=0; j\n"; } } ////////////////////////////// // // printLineToNextNote -- // void printLineToNextNote(ostream& out, MidiFile& midifile, int track, int index, Options& options) { int p1 = midifile[track][index].getP1(); if (midifile[track][index].getChannel() == 9) { p1 = PercussionMap[p1]; } if (diatonicQ) { p1 = base12ToBase7(p1); } double endtime = midifile[track][index].seconds + midifile[track][index].getDurationInSeconds(); stringstream path; int nextindex = -1; for (int i=index+1; i= endtime) { nextindex = i; break; } } if (nextindex < 0) { return; } int p2 = midifile[track][nextindex].getP1(); if (midifile[track][nextindex].getChannel() == 9) { p2 = PercussionMap[p2]; } if (diatonicQ) { p2 = base12ToBase7(p2); } double nextstarttime = midifile[track][nextindex].seconds; double difference = nextstarttime - endtime; if (difference > MaxRest) { // don't connect notes after long rests. return; } double Ax = endtime; double Ay = p1 + 0.5; double Bx = nextstarttime; double By = p2 + 0.5; double Cx = nextstarttime; double Cy = p1 + 0.5; double tradius = radius; if (tradius > fabs(Cx - Ax)) { tradius = fabs(Cx - Ax); } if (tradius > fabs(By - Cy)) { tradius = fabs(By - Cy); } double Rx = tradius / AspectRatio; double Ry = tradius; double Dx = Cx; double Dy = Cy + Ry; double Dxn = Cx; double Dyn = Cy - Ry; double Ex = Cx - Rx; double Ey = Cy; if (radiusQ && (radius > 0)) { if (Ay < By) { path << " M" << Ax << " " << Ay; path << " L" << Ex << " " << Ey; path << " A" << Rx << " " << Ry << " 0 0 1 "; path << Dx << " " << Dy; path << " L" << Bx << " " << By; } else { path << " M" << Ax << " " << Ay; path << " L" << Ex << " " << Ey; path << " A" << Rx << " " << Ry << " 0 0 0 "; path << Dxn << " " << Dyn; path << " L" << Bx << " " << By; } } else if (curveQ) { if (difference > 0.0) { path << " M" << Ax << "," << Ay; path << " Q" << Cx << "," << Cy; path << " " << Bx << " " << By; } else { // vertical line: path << " M" << Cx << " " << Cy; path << " L" << Bx << " " << By; } } else { if (difference > 0.0) { // there is a rest so extent the line horizontally path << " M" << Ax << " " << Ay; path << " L" << Cx << " " << Cy; path << " L" << Bx << " " << By; } else { // vertical line: path << " M" << Cx << " " << Cy; path << " L" << Bx << " " << By; } } out << "\t\t\t\n"; } ////////////////////////////// // // drawNote -- // void drawNote(ostream& out, MidiFile& midifile, int i, int j, int dataQ, int minpitch, int maxpitch) { int tickstart, tickend, tickdur; double starttime, endtime, duration; int height = 1; tickstart = midifile[i][j].tick; starttime = midifile[i][j].seconds; if (midifile[i][j].isLinked()) { tickdur = midifile[i][j].getTickDuration(); tickend = tickstart + tickdur; duration = midifile[i][j].getDurationInSeconds(); endtime = starttime + duration; } else { tickdur = 0; tickend = tickstart; duration = 0.0; endtime = starttime; } int pitch = midifile[i][j].getP1(); if (midifile[i][j].getChannel() == 9) { pitch = PercussionMap[pitch]; } int pitch12 = pitch; if (diatonicQ) { pitch = base12ToBase7(pitch); } int velocity = midifile[i][j].getP2(); int channel = midifile[i][j].getChannel(); // 0-offset int track = i; // 0-offset if (dataQ) { out << "\t\t\t" << endl; } // note box: out << "\t\t\t= maxpitch) { out << " maxima"; } out << "\""; out << ">\n"; // string shape = "diamond"; // string shape = "rectangle"; // string shape = "eyelid"; // string shape = "hexthin"; // string shape = "hexthick"; // string shape = "plus"; // string shape = "round1"; // string shape = "round2"; // string shape = "round3"; // string shape = "round4"; // string shape = "oval"; // string shape = "antioval"; // string shape = "antiround"; // string shape = "antiround1"; // string shape = "antiround2"; // string shape = "antiround3"; // string shape = "antiround4"; // string shape = "triangleup"; // string shape = "triangledown"; // string shape = "triangleleft"; // string shape = "triangleright"; // string shape = "triangleroundleft"; // string shape = "triangleroundright"; // string shape = "curvedinner"; // string shape = "curvedinner1"; // string shape = "curvedinner2"; // string shape = "curvedinner3"; // string shape = "curvedinner4"; // string shape = "curvedouter"; // string shape = "curvedouter1"; // string shape = "curvedouter2"; // string shape = "curvedouter3"; // string shape = "curvedouter4"; string shape = getTrackShape(track); drawNoteShape(out, shape, starttime, pitch, duration, height); out << "\t\t\t\n"; } ////////////////////////////// // // getTrackShape -- // string getTrackShape(int track) { if (track < 0) { track = 0; } if (track > 0) { track = track - 1; } if (track < (int)Shapes.size()) { return Shapes[track]; } else { return "rectangle"; } } ////////////////////////////// // // drawNoteShape -- Draw the desired note shape. // void drawNoteShape(ostream& out, string& shape, double x, double y, double width, double height) { if (shape == "rectangle") { drawRectangle(out, x, y, width, height); } else if (shape == "diamond") { drawDiamond(out, x, y, width, height); } else if (shape == "eyelid") { drawEyelid(out, x, y, width, height); } else if (shape == "hexthin") { drawHexThin(out, x, y, width, height); } else if (shape == "hexthick") { drawHexThick(out, x, y, width, height); } else if (shape == "plus") { drawPlus(out, x, y, width, height); } else if (shape == "round1") { drawRound1(out, x, y, width, height); } else if (shape == "round2") { drawRound2(out, x, y, width, height); } else if (shape == "round3") { drawRound3(out, x, y, width, height); } else if (shape == "round4") { drawRound4(out, x, y, width, height); } else if (shape == "oval") { drawOval(out, x, y, width, height); } else if (shape == "antioval") { drawAntiOval(out, x, y, width, height); } else if (shape == "antiround") { drawAntiRound(out, x, y, width, height); } else if (shape == "antiround1") { drawAntiRound1(out, x, y, width, height); } else if (shape == "antiround2") { drawAntiRound2(out, x, y, width, height); } else if (shape == "antiround3") { drawAntiRound3(out, x, y, width, height); } else if (shape == "antiround4") { drawAntiRound4(out, x, y, width, height); } else if (shape == "curvedinner") { drawCurvedInner(out, x, y, width, height); } else if (shape == "curvedinner1") { drawCurvedInner1(out, x, y, width, height); } else if (shape == "curvedinner2") { drawCurvedInner2(out, x, y, width, height); } else if (shape == "curvedinner3") { drawCurvedInner3(out, x, y, width, height); } else if (shape == "curvedinner4") { drawCurvedInner4(out, x, y, width, height); } else if (shape == "curvedouter") { drawCurvedOuter(out, x, y, width, height); } else if (shape == "curvedouter1") { drawCurvedOuter1(out, x, y, width, height); } else if (shape == "curvedouter2") { drawCurvedOuter2(out, x, y, width, height); } else if (shape == "curvedouter3") { drawCurvedOuter3(out, x, y, width, height); } else if (shape == "curvedouter4") { drawCurvedOuter4(out, x, y, width, height); } else if (shape == "triangleup") { drawTriangleUp(out, x, y, width, height); } else if (shape == "triangledown") { drawTriangleDown(out, x, y, width, height); } else if (shape == "triangleleft") { drawTriangleLeft(out, x, y, width, height); } else if (shape == "triangleright") { drawTriangleRight(out, x, y, width, height); } else if (shape == "triangleroundleft") { drawTriangleRoundLeft(out, x, y, width, height); } else if (shape == "triangleroundright") { drawTriangleRoundRight(out, x, y, width, height); } else { drawRectangle(out, x, y, width, height); } } ////////////////////////////// // // drawDiamond -- // void drawDiamond(ostream& out, double x, double y, double width, double height) { out << "\t\t\t\t\n"; //if (roundedQ) { // out << "\trx=\"" << 1 << "\"" // << "\try=\"" << 1 << "\""; //} } ////////////////////////////// // // drawEyelid -- // void drawEyelid(ostream& out, double x, double y, double width, double height) { out << "\t\t\t\t\n"; } ////////////////////////////// // // drawHexThin -- // void drawHexThin(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawHexThick -- // void drawHexThick(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double maxbevel = h/2.0/AspectRatio; if (width*AspectRatio < height) { maxbevel = w/2.0/AspectRatio; } double& b = maxbevel; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawRectangle -- // void drawRectangle(ostream& out, double x, double y, double width, double height) { out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleUp -- // void drawTriangleUp(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleDown -- // void drawTriangleDown(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleLeft -- // void drawTriangleLeft(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleRight -- // void drawTriangleRight(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleRoundLeft -- // void drawTriangleRoundLeft(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double a = AspectRatio; double wa = w * a; double x2 = x+w; double r = h/2.0; double q = sqrt(wa*wa+r*r); double ww = (w*a*q-w*a*r)/q; double rr = (r*q-r*r)/q; double xa = x + ww/a; double ya = y + r + rr; double xb = xa; double yb = y + r - rr; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawTriangleRoundRight -- // void drawTriangleRoundRight(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawPlus -- // void drawPlus(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedInner -- // void drawCurvedInner(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedInner1 -- // void drawCurvedInner1(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedInner2 -- // void drawCurvedInner2(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedInner3 -- // void drawCurvedInner3(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedInner4 -- // void drawCurvedInner4(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedOuter -- // void drawCurvedOuter(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedOuter1 -- // void drawCurvedOuter1(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedOuter2 -- // void drawCurvedOuter2(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedOuter3 -- // void drawCurvedOuter3(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawCurvedOuter4 -- // void drawCurvedOuter4(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/4; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawOval -- // void drawOval(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiOval -- // void drawAntiOval(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = 2.0; if (d > w / 3) { d = w/3; } double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiRound -- // void drawAntiRound(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiRound1 -- // void drawAntiRound1(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiRound2 -- // void drawAntiRound2(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiRound3 -- // void drawAntiRound3(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawAntiRound4 -- // void drawAntiRound4(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawRound1 -- // void drawRound1(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawRound2 -- // void drawRound2(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawRound3 -- // void drawRound3(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // drawRound4 -- // void drawRound4(ostream& out, double x, double y, double width, double height) { double& h = height; double& w = width; double x2 = x+w; double y2 = y+h; double m = h/2.0; double d = w/2.0; double Rx = d; double Ry = h/2; out << "\t\t\t\t\n"; } ////////////////////////////// // // base12ToBase7 -- MIDI to diatonic pitch name. Middle C is 5th octave. // int base12ToBase7(int pitch) { int octave = pitch / 12; int chroma = pitch % 12; int output = 0; switch (chroma) { case 0: output = 0; break; // C case 1: output = 0; break; // C# case 2: output = 1; break; // D case 3: output = 2; break; // Eb case 4: output = 2; break; // E case 5: output = 3; break; // F case 6: output = 3; break; // F# case 7: output = 4; break; // G case 8: output = 4; break; // G# case 9: output = 5; break; // A case 10: output = 6; break; // Bb case 11: output = 6; break; // B } return output + 7 * octave; } ////////////////////////////// // // printDoubleClass -- print a double note with a "d" instead of // decimal point. // void printDoubleClass(ostream& out, double value) { value = int(value * 1000.0 + 0.5)/1000.0; char buffer[32] = {0}; snprintf(buffer, 32, "%.3lf", value); char* decimal = strchr(buffer, '.'); if (decimal != NULL) { decimal[0] = 'd'; } out << buffer; } ////////////////////////////// // // getTrackHues -- Assign track colors by maximally spaced hue. Maybe // shuffle or randomize if there are a lot of tracks. // vector getTrackHues(MidiFile& midifile) { vector output; output.resize(midifile.size()); fill(output.begin(), output.end(), -1); int tcount = 0; int i; for (i=0; i key)) { minpitch = key; } if ((maxpitch < 0) || (maxpitch < key)) { maxpitch = key; } } } } if (grandQ) { if (minpitch > 40) { minpitch = 40; } if (maxpitch < 80) { maxpitch = 80; } } if (diatonicQ) { minpitch = base12ToBase7(minpitch); maxpitch = base12ToBase7(maxpitch); } } ////////////////////////////// // // getMinMaxTrackPitch -- Determine the minimum and maximum pitch in a // particular Track. // void getMinMaxTrackPitch(const MidiEventList& evl, int& minpitch, int &maxpitch) { int key = 0; minpitch = -1; maxpitch = -1; for (int i=0; i key)) { minpitch = key; } if ((maxpitch < 0) || (maxpitch < key)) { maxpitch = key; } } } if (diatonicQ) { minpitch = base12ToBase7(minpitch); maxpitch = base12ToBase7(maxpitch); } } ////////////////////////////// // // getMaxTime -- return the ending time of the last note in any track. // double getMaxTime(const MidiFile& midifile) { double maxtime = 0.0; for (int i=0; i=0; j--) { if (midifile[i][j].isNoteOff()) { if (maxtime < midifile[i][j].seconds) { maxtime = midifile[i][j].seconds; } break; } } } return maxtime; } ////////////////////////////// // // checkOptions -- // void checkOptions(Options& opts, int argc, char* argv[]) { opts.define("a|aspect-ratio=d:2.5", "Aspect ratio for SVG image"); opts.define("w|stroke-width=d:0.1", "Stroke width for line around note boxes"); opts.define("stroke-color=s:black", "Stroke color for line around note boxes"); opts.define("staff=b", "Draw staff lines."); opts.define("gs|grand|grand-staff=b", "show at least all grand staff."); opts.define("sc|staff-color=s:#555555", "staff line color."); opts.define("cc|clef-color=s:#cdcdcd", "clef fill/stroke color."); opts.define("sw|st|staff-width|staff-thickness=d:0.5", "staff line width."); opts.define("lw|lt|line-width|line-thickness=d:0.5", "Width of note lines"); opts.define("dash|dashing=b", "Dash connecting lines"); opts.define("T|no-transparency=b", "Do not show notes with transparency"); opts.define("s|scale=d:1.0", "Scaling factor for SVG image"); opts.define("d|data=b", "Embed note data in SVG image"); opts.define("f|final|final-barline=b", "draw final barline"); opts.define("double|double-barline=b", "draw final double barline"); opts.define("bw|black-and-white=b", "Display as black and white (outlines only)"); opts.define("diatonic=b", "Vertical axis is base-7 pitch"); opts.define("drum=b", "Show drum track (channel 10)"); opts.define("pm|perc|percussion-map=s", "Map percussion notes to different pitch"); opts.define("r|round|rounded=b", "Round edges of note boxes"); opts.define("b|border=d:1.0", "Border around piano roll"); opts.define("dark=b", "Background is black"); opts.define("o|opacity=d:1.0", "Opacity for notes"); opts.define("l|line=b", "Draw lines between center of notes"); opts.define("cl|cline=b", "Draw curved lines between notes"); opts.define("rl|rline=d:0.25", "Draw lines with curved radius between notes"); opts.define("e|end-space=d:0.0", "extra horiz. space at end of piece"); opts.define("c|clef|clefs=b", "Draw clefs"); opts.define("v|velocity-brightness=b", "Show velocity as brightness on note"); opts.define("S|shapes=s:rectangle,rectangle", "shape of notes for each track"); opts.define("mr|rest|max-rest=d:4.0 seconds", "Maximum rest through which to draw lines"); 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, 20 February 2016" << endl; exit(0); } else if (opts.getBoolean("version")) { cout << argv[0] << ", version: 20 February 2016" << 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); } dataQ = opts.getBoolean("data"); drumQ = opts.getBoolean("drum"); darkQ = opts.getBoolean("dark"); lineQ = opts.getBoolean("line"); curveQ = opts.getBoolean("cline"); if (curveQ) { lineQ = 1; } radiusQ = opts.getBoolean("rline"); if (radiusQ) { lineQ = 1; curveQ = 1; radius = opts.getDouble("rline"); } clefQ = opts.getBoolean("clefs"); staffQ = opts.getBoolean("staff"); grandQ = opts.getBoolean("grand-staff"); bwQ = opts.getBoolean("black-and-white"); diatonicQ = opts.getBoolean("diatonic"); transparentQ = !opts.getBoolean("no-transparency"); roundedQ = opts.getBoolean("rounded"); Scale = opts.getDouble("scale"); Border = opts.getDouble("border"); AspectRatio = opts.getDouble("aspect-ratio"); Opacity = opts.getDouble("opacity"); MaxRest = opts.getDouble("max-rest"); if (clefQ) { staffQ = 1; braceQ = 1; } PercussionMap.resize(128); for (int i=0; i<(int)PercussionMap.size(); i++) { PercussionMap[i] = i; } percmapQ = opts.getBoolean("percussion-map"); if (percmapQ) { makeMappings(PercussionMap, opts.getString("percussion-map")); drumQ = 1; } finalQ = opts.getBoolean("final-barline"); doubleQ = opts.getBoolean("double-barline"); EndSpace = opts.getDouble("end-space"); if (finalQ || doubleQ) { EndSpace += 2.0; } StaffThickness = opts.getDouble("staff-width"); if (bwQ) { StaffThickness = StaffThickness * 0.25; } StaffColor = opts.getString("staff-color"); ClefColor = opts.getString("clef-color"); if (!opts.getBoolean("line-width")) { LineThickness = StaffThickness; } velocitybQ = opts.getBoolean("velocity-brightness"); char buffer[12345] = {0}; strcpy(buffer, opts.getString("shapes").c_str()); Shapes.resize(0); string current; const char* spacers = "\t :,;|\n"; char* ptr = strtok(buffer, spacers); while (ptr != NULL) { current = ptr; if (current == "r") { Shapes.push_back("rectangle"); } else if (current == "e") { Shapes.push_back("eyelid"); } else if (current == "d") { Shapes.push_back("diamond"); } else if (current == "h") { Shapes.push_back("hexthin"); } else if (current == "H") { Shapes.push_back("hexthick"); } else if (current == "p") { Shapes.push_back("plus"); } else if (current == "r1") { Shapes.push_back("round1"); } else if (current == "r2") { Shapes.push_back("round2"); } else if (current == "r3") { Shapes.push_back("round3"); } else if (current == "r4") { Shapes.push_back("round4"); } else if (current == "o") { Shapes.push_back("oval"); } else if (current == "O") { Shapes.push_back("antioval"); } else if (current == "R") { Shapes.push_back("antiround"); } else if (current == "R1") { Shapes.push_back("antiround1"); } else if (current == "R2") { Shapes.push_back("antiround2"); } else if (current == "R3") { Shapes.push_back("antiround3"); } else if (current == "R4") { Shapes.push_back("antiround4"); } else if (current == "c") { Shapes.push_back("curvedinner"); } else if (current == "c1") { Shapes.push_back("curvedinner1"); } else if (current == "c2") { Shapes.push_back("curvedinner2"); } else if (current == "c3") { Shapes.push_back("curvedinner3"); } else if (current == "c4") { Shapes.push_back("curvedinner4"); } else if (current == "C") { Shapes.push_back("curvedouter"); } else if (current == "C1") { Shapes.push_back("curvedouter1"); } else if (current == "C2") { Shapes.push_back("curvedouter2"); } else if (current == "C3") { Shapes.push_back("curvedouter3"); } else if (current == "C4") { Shapes.push_back("curvedouter4"); } else if (current == "tu") { Shapes.push_back("triangleup"); } else if (current == "td") { Shapes.push_back("triangledown"); } else if (current == "tl") { Shapes.push_back("triangleleft"); } else if (current == "tr") { Shapes.push_back("triangleright"); } else if (current == "trl") { Shapes.push_back("triangleroundleft"); } else if (current == "trr") { Shapes.push_back("triangleroundright"); } else { Shapes.push_back(current); } ptr = strtok(NULL, spacers); } } ////////////////////////////// // // makeMappings -- Manually move percussion notes to other locations in pitch range. // Maybe add separate coloring of percussion instruments. // Maybe need to add automatic note offs for persussion instruments (as they // do not always have note-offs. // // --perc "60>40, 61>51,62>44" // void makeMappings(vector& mapping, const string& mapstring) { string newmap = mapstring + ' '; int ltx = 0; int d = 1; int digit1 = 0; int digit2 = 0; int ii; for (ii=0; ii<(int)newmap.size(); ii++) { if (isdigit(newmap[ii])) { break; } } for (int i=ii; i<(int)newmap.size(); i++) { if (isdigit(newmap[i])) { if (d) { digit1 = digit1 * 10 + (newmap[i] - '0'); } else { digit2 = digit2 * 10 + (newmap[i] - '0'); } ltx = 0; } else { if (!ltx) { d = !d; } ltx = 1; if (d) { digit1 = digit1 > 127 ? 127 : digit1; digit1 = digit1 < 0 ? 0 : digit1; digit2 = digit2 > 127 ? 127 : digit2; digit2 = digit2 < 0 ? 0 : digit2; mapping[digit1] = digit2; digit1 = 0; digit2 = 0; } } } } ////////////////////////////// // // example -- // void example(void) { // add example usages here } ////////////////////////////// // // usage -- // void usage(const char* command) { cout << "Usage: " << command << " input.mid > output.svg" << endl; }