322 lines
7.2 KiB
C++
322 lines
7.2 KiB
C++
//
|
|
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
|
// Creation Date: Wed Apr 3 23:43:56 PDT 2013
|
|
// Last Modified: Mon Feb 9 21:26:32 PST 2015 Updated for C++11.
|
|
// Filename: tools/midi2binasc.cpp
|
|
// URL: https://github.com/craigsapp/midifile/blob/master/tools/midi2binasc.cpp
|
|
// Syntax: C++11
|
|
// vim: ts=3
|
|
//
|
|
// Description: Converts a MIDI file into an ASCII format which can be
|
|
// converted back into a MIDI file with the binasc program:
|
|
// https://github.com/craigsapp/binasc
|
|
//
|
|
|
|
#include "MidiFile.h"
|
|
#include "Options.h"
|
|
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
using namespace std;
|
|
using namespace smf;
|
|
|
|
|
|
void convertMidiFile (MidiFile& midifile);
|
|
void printMidiHeader (MidiFile& midifile);
|
|
void checkOptions (Options& opts, int argc, char* argv[]);
|
|
void printTrack (MidiFile& midifile, int track);
|
|
int getVlvSize (int value);
|
|
int getTrackByteCount (MidiFile& midifile, int track);
|
|
void printDecByte (int value);
|
|
void printMidiEvent (MidiEvent& event);
|
|
void printHexByte (int value);
|
|
void usage (const char* command);
|
|
void example (void);
|
|
|
|
// User interface variables:
|
|
Options options;
|
|
int debugQ = 0; // use with --debug option
|
|
int type0Q = 0; // force MIDI file to type 0 (single track)
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int main(int argc, char* argv[]) {
|
|
checkOptions(options, argc, argv);
|
|
MidiFile midifile(options.getArg(1));
|
|
convertMidiFile(midifile);
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// convertMidiFile --
|
|
//
|
|
|
|
void convertMidiFile(MidiFile& midifile) {
|
|
if (type0Q) {
|
|
midifile.joinTracks();
|
|
}
|
|
midifile.deltaTicks();
|
|
|
|
printMidiHeader(midifile);
|
|
int trackcount = midifile.getTrackCount();
|
|
for (int i=0; i<trackcount; i++) {
|
|
printTrack(midifile, i);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printTrack --
|
|
//
|
|
|
|
void printTrack(MidiFile& midifile, int track) {
|
|
cout << endl;
|
|
|
|
// first print track header
|
|
cout << "+M +T +r +k\t\t\t; Track chunk marker" << endl;
|
|
// print number of bytes in track
|
|
int trackbytes = getTrackByteCount(midifile, track);
|
|
cout << "4'" << trackbytes << "\t\t\t\t; number of bytes to follow in track"
|
|
<< endl;
|
|
|
|
cout << endl;
|
|
|
|
// print the list of events in the track
|
|
MidiEvent event;
|
|
int eventcount = midifile.getEventCount(track);
|
|
for (int i=0; i<eventcount; i++) {
|
|
event = midifile.getEvent(track, i);
|
|
printMidiEvent(event);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printMidiEvent -- print a time/MIDI message pair.
|
|
//
|
|
|
|
void printMidiEvent(MidiEvent& event) {
|
|
// print the time:
|
|
cout << "v" << event.tick << "\t";
|
|
|
|
// print the command byte in hex format (two digits):
|
|
int commandbyte = event[0];
|
|
printHexByte(commandbyte);
|
|
int i;
|
|
|
|
switch (commandbyte & 0xf0) {
|
|
case 0x90:
|
|
case 0x80:
|
|
for (i=1; i<(int)event.size(); i++) {
|
|
cout << " ";
|
|
printDecByte(event[i]);
|
|
}
|
|
break;
|
|
default:
|
|
for (i=1; i<(int)event.size(); i++) {
|
|
cout << " ";
|
|
printHexByte(event[i]);
|
|
}
|
|
}
|
|
|
|
cout << endl;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printDecByte --
|
|
//
|
|
|
|
void printDecByte(int value) {
|
|
cout << "'" << value;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printHexByte --
|
|
//
|
|
|
|
void printHexByte(int value) {
|
|
if (value < 16) {
|
|
cout << "0";
|
|
} else if (value > 255) {
|
|
cerr << "ERROR: value is too large: " << value << endl;
|
|
exit(1);
|
|
}
|
|
cout << hex << value << dec;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// getTrackByteCount -- number of by the track after the track header.
|
|
// Might need to keep track of End-of-track meta message.
|
|
//
|
|
|
|
int getTrackByteCount(MidiFile& midifile, int track) {
|
|
int sum = 0;
|
|
int i;
|
|
int eventcount = midifile.getEventCount(track);
|
|
MidiEvent event;
|
|
|
|
for (i=0; i<eventcount; i++) {
|
|
event = midifile.getEvent(track, i);
|
|
sum += getVlvSize(event.tick);
|
|
sum += (int)event.size();
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// getVlvSize -- return the number of bytes in the VLV format for the
|
|
// integer.
|
|
//
|
|
|
|
int getVlvSize(int value) {
|
|
if (value < 0x80) {
|
|
return 1;
|
|
} if (value < 0x4000) {
|
|
return 2;
|
|
} else if (value < 0x200000) {
|
|
return 3;
|
|
} else if (value < 0x10000000) {
|
|
return 4;
|
|
} else {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printMidiHeader --
|
|
//
|
|
// Example header:
|
|
//
|
|
// +M +T +h +d ; MIDI file header chunk marker
|
|
// 4'6 ; bytes in header to follow
|
|
// 2'0 ; format: Type-0 (single track)
|
|
// 2'1 ; track count: 1 track
|
|
// '-25 '40 ; divisions: SMPTE: 25 frames/sec 40 subframes/frame
|
|
// ; in other words: 25 * 40 = 1000 ticks per second.
|
|
//
|
|
|
|
void printMidiHeader(MidiFile& midifile) {
|
|
// print MIDI file header marker
|
|
cout << "+M +T +h +d\t\t\t; MIDI file header chunk marker" << endl;
|
|
// print the number of bytes in the MIDI file to follow (always 6):
|
|
cout << "4'6\t\t\t\t; bytes in header to follow" << endl;
|
|
// print the format (0 = single track, 1 = multitrack)
|
|
// The MidiFile class does not exactly keep track of this value.
|
|
// It will presume that a single track file is a type-0 MIDI file
|
|
// (type-1 MIDI files can theoretically have a single track, but not
|
|
// usually).
|
|
if (midifile.getTrackCount() == 0) {
|
|
cout << "2'0\t\t\t\t; format: Type-0 (single track)";
|
|
} else {
|
|
cout << "2'1\t\t\t\t; format: Type-1 (multi-track)";
|
|
}
|
|
cout << endl;
|
|
// print track count
|
|
cout << "2'" << midifile.getTrackCount();
|
|
cout << "\t\t\t\t; track count: ";
|
|
cout << midifile.getTrackCount() << " track";
|
|
if (midifile.getTrackCount() != 1) {
|
|
cout << "s";
|
|
}
|
|
cout << endl;
|
|
|
|
// print the ticks per quarter note. The ticks per quarter note
|
|
// can be SMPTE or regular. Assuming regular at the moment.
|
|
int ticks = midifile.getTicksPerQuarterNote();
|
|
cout << "2'" << ticks << "\t\t\t\t; ticks per quarter note" << endl;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// checkOptions --
|
|
//
|
|
|
|
void checkOptions(Options& opts, int argc, char* argv[]) {
|
|
opts.define("debug=b", "debug mode to find errors in input file");
|
|
|
|
opts.define("author=b", "author of program");
|
|
opts.define("version=b", "compilation info");
|
|
opts.define("example=b", "example usages");
|
|
opts.define("h|help=b", "short description");
|
|
|
|
opts.process(argc, argv);
|
|
|
|
// handle basic options:
|
|
if (opts.getBoolean("author")) {
|
|
cout << "Written by Craig Stuart Sapp, "
|
|
<< "craig@ccrma.stanford.edu, 22 Jan 2002" << endl;
|
|
exit(0);
|
|
} else if (opts.getBoolean("version")) {
|
|
cout << argv[0] << ", version: 12 Nov 2003" << 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);
|
|
}
|
|
|
|
debugQ = opts.getBoolean("debug");
|
|
|
|
if (opts.getArgCount() != 1) {
|
|
usage(opts.getCommand().c_str());
|
|
exit(1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// example --
|
|
//
|
|
|
|
void example(void) {
|
|
// add example usage here
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// usage --
|
|
//
|
|
|
|
void usage(const char* command) {
|
|
cout << "Usage: " << command << " midifile" << endl;
|
|
}
|
|
|
|
|
|
|