// // Programmer: Craig Stuart Sapp // Creation Date: Sun Feb 7 11:37:52 PST 2021 // Last Modified: Sun Feb 7 14:12:31 PST 2021 // Filename: tools/deltatimes.cpp // URL: https://github.com/craigsapp/midifile/blob/master/tools/deltatimes.cpp // Syntax: C++11 // vim: ts=3 // // Description: Generate list of delta times in a MIDI file, either as MIDI ticks, // quarter notes, or at real-time values in seconds or milliseconds // (why applying tempo meta messages to the tick values). Useful // for determining if the MIDI file contains a score or a performance. // // Options: // -q Output delta times as quarter note units instead of MIDI ticks. // -s Output real times in seconds. // -m Output real times in milliseconds. // -a Output MIDI tick, quarter note, second and millisecond timings. // -c Count timings by value (output a delta-time histogram). // -r Reverse: place the count after the timing values in the histogram. // -n # Normalize by the maximum count in the histogram scaled to number #. // -N # Normalize by the sum of the histogram counts scaled to number #. // -C Sort histogram by counts (largest to smallest). // --tpq Print ticks-per-quarter note from MIDI header(s) only. // --notes Calculate for MIDI notes only. // --on Calculate for MIDI notes-ons only. // #include "MidiFile.h" #include "Options.h" #include #include #include #include #include #include using namespace std; using namespace smf; #define TIME_TICKS 0 #define TIME_SECONDS 1 #define TIME_MILLISECONDS 2 #define TIME_QUARTERS 3 void processFile(MidiFile& midifile, map& histogram, Options& options); void printHistogram(map& histogram, Options& options); void extractDeltaTimesForAllEvents(MidiFile& midifile, map& histogram, Options& options); void extractDeltaTimesForAllNotes(MidiFile& midifile, map& histogram, Options& options); void extractDeltaTimesForAllNotesOns(MidiFile& midifile, map& histogram, Options& options); /////////////////////////////////////////////////////////////////////////// int main(int argc, char** argv) { Options options; options.define("a|all=b", "output all types of time values for delta times"); options.define("s|sec|second|seconds=b", "display times in seconds"); options.define("m|ms|millisecond|milliseconds=b", "display times in milliseconds"); options.define("q|quarter|quarters|quarter-notes|quarter-note=b", "display total time in quarter notes"); options.define("c|count|counts=b", "display times in a histogram"); options.define("n|normalize-by-max=d:100.0", "Normalize to max value in histogram"); options.define("N|normalize-by-total=d:100.0", "Normalize to total count in histogram"); options.define("r|reverse=b", "reverse histogram label/count order"); options.define("C|sort-by-count=b", "sort histogram by counts rather than times"); options.define("tpq=b", "print only the ticks-per-quarter note value from the MIDI header"); options.define("notes|note=b", "delta times only between note on/offs"); options.define("on|note-on|note-ons=b", "delta times only between note ons"); options.process(argc, argv); MidiFile midifile; map histogram; if (options.getArgCount() == 0) { midifile.read(cin); processFile(midifile, histogram, options); } else { int count = options.getArgCount(); for (int i=0; i& histogram, Options& options) { if (options.getBoolean("tpq")) { int tpq = midifile.getTicksPerQuarterNote(); cerr << tpq << endl; return; } if (options.getBoolean("notes")) { extractDeltaTimesForAllNotes(midifile, histogram, options); } else if (options.getBoolean("on")) { extractDeltaTimesForAllNotesOns(midifile, histogram, options); } else { extractDeltaTimesForAllEvents(midifile, histogram, options); } } ////////////////////////////// // // extractDeltaTimesForAllEvents -- calculate delta timings between all events. // void extractDeltaTimesForAllEvents(MidiFile& midifile, map& histogram, Options& options) { int tpq = midifile.getTicksPerQuarterNote(); int timeFormat = TIME_TICKS; bool realtimeQ = false; bool allQ = options.getBoolean("all"); if (allQ) { realtimeQ = true; } if (options.getBoolean("seconds")) { timeFormat = TIME_SECONDS; realtimeQ = true; } else if (options.getBoolean("milliseconds")) { timeFormat = TIME_MILLISECONDS; realtimeQ = true; } else if (options.getBoolean("quarters")) { timeFormat = TIME_QUARTERS; } bool histogramQ = options.getBoolean("count"); if (realtimeQ) { midifile.doTimeAnalysis(); } midifile.makeDeltaTicks(); for (int i=0; i 0) { oldtime = midifile[i][j-1].seconds; } else { oldtime = 0.0; } value = newtime - oldtime; key << value << "\t"; // seconds key << value * 1000.0; // milliseconds } else if (realtimeQ) { newtime = midifile[i][j].seconds; if (j == 0) { oldtime = 0; } else { oldtime = midifile[i][j-1].seconds; } value = newtime - oldtime; if (timeFormat == TIME_MILLISECONDS) { value *= 1000.0; } key.str().clear(); key << value; } else { value = midifile[i][j].tick; if (timeFormat == TIME_QUARTERS) { value /= tpq; } key.str().clear(); key << value; } if (histogramQ) { histogram[key.str()]++; } else { cout << key.str() << endl; } } } } ////////////////////////////// // // extractDeltaTimesForAllNotes -- calculate delta timings between all note MIDI // messages (ignoring other MIDI messages). // void extractDeltaTimesForAllNotes(MidiFile& midifile, map& histogram, Options& options) { int tpq = midifile.getTicksPerQuarterNote(); int timeFormat = TIME_TICKS; bool realtimeQ = false; bool allQ = options.getBoolean("all"); int lastindex = -1; if (allQ) { realtimeQ = true; } if (options.getBoolean("seconds")) { timeFormat = TIME_SECONDS; realtimeQ = true; } else if (options.getBoolean("milliseconds")) { timeFormat = TIME_MILLISECONDS; realtimeQ = true; } else if (options.getBoolean("quarters")) { timeFormat = TIME_QUARTERS; } bool histogramQ = options.getBoolean("count"); if (realtimeQ) { midifile.doTimeAnalysis(); } for (int i=0; i& histogram, Options& options) { int tpq = midifile.getTicksPerQuarterNote(); int timeFormat = TIME_TICKS; bool realtimeQ = false; bool allQ = options.getBoolean("all"); int lastindex = -1; if (allQ) { realtimeQ = true; } if (options.getBoolean("seconds")) { timeFormat = TIME_SECONDS; realtimeQ = true; } else if (options.getBoolean("milliseconds")) { timeFormat = TIME_MILLISECONDS; realtimeQ = true; } else if (options.getBoolean("quarters")) { timeFormat = TIME_QUARTERS; } bool histogramQ = options.getBoolean("count"); if (realtimeQ) { midifile.doTimeAnalysis(); } for (int i=0; i& histogram, Options& options) { bool reverseQ = options.getBoolean("reverse"); double scale = 1.0; if (options.getBoolean("normalize-by-max")) { int maximum = 0; for (auto it : histogram) { if (it.second > maximum) { maximum = it.second; } } if (maximum > 0) { scale = options.getDouble("normalize-by-max") / maximum; } } else if (options.getBoolean("normalize-by-total")) { int sum = 0; for (auto it : histogram) { sum += it.second; } if (sum > 0) { scale = options.getDouble("normalize-by-total") / sum; } } if (options.getBoolean("sort-by-count")) { // Sort by count, from lowest to highest: typedef function, pair)> Comparator; Comparator sortFunction = [](pair a, pair b) { if (a.first == b.first) { return stod(a.second) < stod(b.second); } else { return a.first > b.first; } }; map, int, Comparator> sortedkeys(sortFunction);; for (auto it : histogram) { sortedkeys[make_pair(it.second, it.first)] = it.second; } for (auto it : sortedkeys) { string label = it.first.second; double count = it.second * scale; if (reverseQ) { cout << label << '\t' << count << endl; } else { cout << count << '\t' << label << endl; } } } else { // Sort by delta time, from lowest to highest: map> sortedkeys; for (auto it : histogram) { sortedkeys[stod(it.first)] = it.first; } for (auto it : sortedkeys) { string label = it.second; double count = histogram[it.second] * scale; if (reverseQ) { cout << label << '\t' << count << endl; } else { cout << count << '\t' << label << endl; } } } }