2024-07-15 11:46:59 +08:00

229 lines
6.5 KiB
C++

//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Jun 6 20:41:53 PDT 2002
// Last Modified: Mon Feb 9 21:01:00 PST 2015 Updated for C++11.
// Filename: tools/midi2skini.cpp
// URL: https://github.com/craigsapp/midifile/blob/master/tools/midi2skini.cpp
// Syntax: C++11
// vim: ts=3
//
// Description: Converts a Standard MIDI file into the SKINI data format.
//
#include "MidiFile.h"
#include "Options.h"
#include <iomanip>
#include <iostream>
using namespace std;
using namespace smf;
void checkOptions (Options& opts, int argc, char** argv);
void example (void);
void printMidiAsSkini (MidiFile& midifile);
void processEvent (MidiEvent& event, double& tempo,
double& curtime);
void usage (const char* command);
// User interface variables:
Options options;
int track = -1; // track to extract from (starting from 0)
int debugQ = 0; // use with --debug option
int maxcount = 100000; // maxiumum number of notes expected
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
checkOptions(options, argc, argv);
MidiFile midifile;
midifile.read(options.getArg(1));
midifile.absoluteTicks();
midifile.joinTracks();
printMidiAsSkini(midifile);
return 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// printMidiAsSkini -- go thorough all events in the MIDI file
// and print the data. Input MIDI file is assumed to be in
// type 0 format (single track).
//
void printMidiAsSkini(MidiFile& midifile) {
int tpq = midifile.getTicksPerQuarterNote();
int ticks = 0; // absolute ticks of current event
double tempo = 120.0; // time units will be in seconds
double curtime = 0.0; // current time in seconds
int i;
for (i=0; i<midifile.getNumEvents(0); i++) {
int oldticks = ticks;
MidiEvent event = midifile.getEvent(0, i);
ticks = event.tick;
if (i>0) {
curtime += (ticks - oldticks) * 60.0 / tempo / tpq;
}
processEvent(event, tempo, curtime);
}
}
//////////////////////////////
//
// processEvent -- check for tempo markings, and print MIDI event.
//
void processEvent(MidiEvent& event, double& tempo, double& curtime) {
if (((event[0] & 0xf0) == 0x80) || (((event[0] & 0xf0) == 0x90) &&
(event[2] == 0)) ) {
// note-off MIDI message
if (track >= 0 && track != event.track) { return; }
cout << "NoteOff\t\t=";
cout.width(9);
cout.setf(ios::left);
cout << curtime << "\t" << event.track << "\t"
<< (int)event[1] << "\t" << (int)event[2] << endl;
} else if ((event[0] & 0xf0) == 0x90) {
// note-on MIDI message
if (track >= 0 && track != event.track) { return; }
cout << "NoteOn\t\t=";
cout.width(9);
cout.setf(ios::left);
cout << curtime << "\t" << event.track << "\t"
<< (int)event[1] << "\t" << (int)event[2] << endl;
} else if ((event[0] & 0xf0) == 0xb0) {
// continuous controller MIDI message
if (track >= 0 && track != event.track) { return; }
if (event[1] == 7) {
cout << "Volume\t\t=";
cout.width(9);
cout.setf(ios::left);
cout << curtime << "\t" << event.track << "\t"
<< "\t" << (int)event[2] << endl;
} else {
cout << "ControlChange\t=";
cout.width(9);
cout.setf(ios::left);
cout << curtime << "\t" << event.track << "\t"
<< (int)event[1] << "\t" << (int)event[2] << endl;
}
} else if ((event.size() > 3) && (event[0] == 0xff) &&
(event[1] == 0x51)) {
// Tempo meta event
int microseconds = (unsigned int)event[3];
microseconds = microseconds << 8;
microseconds |= (unsigned int)event[4];
microseconds = microseconds << 8;
microseconds |= (unsigned int)event[5];
tempo = 1000000.0 / microseconds * 60.0;
cout << "// time:=" << curtime << " tempo: " << tempo << endl;
} else if ((event.size() > 3) && (event[0] == 0xff) &&
(event[1] == 0x03)) {
// Track name MIDI meta event
if (track >= 0 && track != event.track) { return; }
cout << "//";
cout << " time:=" << curtime;
cout << " track:" << event.track;
cout << " text: ";
for (int i=3; i<(int)event.size(); i++) {
cout << (char)event[i];
}
cout << endl;
} else {
// a MIDI event with an unknown mapping into SKINI, print as comment
if (track >= 0 && track != event.track) { return; }
cout << "//"
<< " time:=" << curtime
<< " track:" << event.track
<< " midi-data: ";
for (int i=0; i<(int)event.size(); i++) {
cout << (int)event[i] << " ";
}
cout << endl;
}
}
//////////////////////////////
//
// checkOptions -- process command-line options.
//
void checkOptions(Options& opts, int argc, char* argv[]) {
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.define("t|track=i:-1", "which track to extract");
opts.define("max=i:100000", "maximum number of notes expected in input");
opts.define("debug=b", "debug mode to find errors in input file");
opts.process(argc, argv);
// handle basic options:
if (opts.getBoolean("author")) {
cout << "Written by Craig Stuart Sapp, "
<< "craig@ccrma.stanford.edu, 6 Jun 2002" << endl;
exit(0);
} else if (opts.getBoolean("version")) {
cout << argv[0] << ", version: 10 Jun 2002" << 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);
}
track = opts.getInteger("track");
debugQ = opts.getBoolean("debug");
maxcount = opts.getInteger("max");
if (opts.getArgCount() != 1) {
usage(opts.getCommand().c_str());
exit(1);
}
}
//////////////////////////////
//
// example -- give example calls to the program.
//
void example(void) {
cout <<
"midi2skini file.mid == convert file.mid to the SKINI format\n"
"midi2skini file.mid >file.skini == save output SKINI data to file.skini\n"
"midi2skini -t1 file.mid == extract only track 1 from the tdata\n"
;
}
//////////////////////////////
//
// usage --
//
void usage(const char* command) {
cout << "Usage: " << command << " [-t #|-m #] midifile\n";
cout << " -t # == extract only specified track (offset from 0).\n";
cout << " -m # == number of events to allocate for MIDI \n";
cout << endl;
}