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

299 lines
6.6 KiB
C++

//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Jan 22 16:46:19 PST 2002
// Last Modified: Mon Feb 9 21:26:32 PST 2015 Updated for C++11.
// Filename: tools/text2midi.cpp
// URL: https://github.com/craigsapp/midifile/blob/master/tools/text2midi.cpp
// Syntax: C++11
// vim: ts=3
//
// Description: Converts a text based notelist into a MIDI file.
//
#include "MidiFile.h"
#include "Options.h"
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
using namespace smf;
void convertTextToMidiFile (istream& textfile, MidiFile& midifile);
void adjustbuffer (char* buffer);
void readvalues (char* buffer, int& eventtype, double& start,
double& dur, int& note, int& vel);
void checkOptions (Options& opts, int argc, char** argv);
void example (void);
void usage (const char* command);
// User interface variables:
Options options;
int tpq = 480; // ticks per quarter note
int debugQ = 0; // use with --debug option
int maxcount = 100000; // maxiumum number of notes expected
double tempo = 120.0; // time units will be in seconds
int channel = 0; // default channel
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[]) {
checkOptions(options, argc, argv);
fstream textfile(options.getArg(1).c_str(), ios::in);
if (!textfile.is_open()) {
cout << "Error: cannot read input text file." << endl;
usage(options.getCommand().c_str());
exit(1);
}
MidiFile midifile;
convertTextToMidiFile(textfile, midifile);
midifile.sortTracks();
midifile.write(options.getArg(2));
return 0;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////
//
// convertTextToMidiFile --
//
void convertTextToMidiFile(istream& textfile, MidiFile& midifile) {
vector<uchar> mididata;
midifile.setTicksPerQuarterNote(tpq);
midifile.absoluteTicks();
midifile.allocateEvents(0, 2 * maxcount + 500); // pre allocate space for
// max expected MIDI events
// write the tempo to the midifile
mididata.resize(6);
mididata[0] = 0xff; // meta message
mididata[1] = 0x51; // tempo change
mididata[2] = 0x03; // three bytes to follow
int microseconds = (int)(60.0 / tempo * 1000000.0 + 0.5);
mididata[3] = (microseconds >> 16) & 0xff;
mididata[4] = (microseconds >> 8) & 0xff;
mididata[5] = (microseconds >> 0) & 0xff;
midifile.addEvent(0, 0, mididata);
char buffer[1024] = {0};
int line = 1;
double start = 0.0;
double dur = 0.0;
int ontick = 0;
int offtick = 0;
int note = 0;
int vel = 0;
int eventtype = 0;
while (!textfile.eof()) {
textfile.getline(buffer, 1000, '\n');
if (textfile.eof()) {
break;
}
adjustbuffer(buffer);
if (debugQ) {
cout << "line " << line << ":\t" << buffer << endl;
}
readvalues(buffer, eventtype, start, dur, note, vel);
if (eventtype != 1) {
continue;
}
// have a good note, so store it in the MIDI file
ontick = (int)(start * tpq * 2.0 + 0.5);
offtick = (int)((start + dur) * tpq * 2.0 + 0.5);
if (offtick <= ontick) {
offtick = ontick + 1;
}
if (debugQ) {
cout << "Note ontick=" << ontick << "\tofftick=" << offtick
<< "\tnote=" << note << "\tvel=" << vel << endl;
}
mididata.resize(3);
mididata[0] = 0x90 | channel; // note on command
mididata[1] = (uchar)(note & 0x7f);
mididata[2] = (uchar)(vel & 0x7f);
midifile.addEvent(0, ontick, mididata);
mididata[0] = 0x80 | channel; // note off command
midifile.addEvent(0, offtick, mididata);
line++;
}
}
//////////////////////////////
//
// readvalues -- read parameter values from the input dataline.
// returns 0 if no parameters were readable.
//
void readvalues(char* buffer, int& eventtype, double& start, double& dur,
int& note, int& vel) {
char *ptr = strtok(buffer, " \t\n");
if (ptr == NULL) {
eventtype = 0;
return;
}
if (strcmp(ptr, "note") != 0) {
eventtype = 0;
return;
} else {
eventtype = 1;
}
// read the starttime
ptr = strtok(NULL, " \t\n");
if (ptr == NULL) {
eventtype = 0;
return;
}
start = atof(ptr);
// read the duration
ptr = strtok(NULL, " \t\n");
if (ptr == NULL) {
eventtype = 0;
return;
}
dur = atof(ptr);
// read the note number
ptr = strtok(NULL, " \t\n");
if (ptr == NULL) {
eventtype = 0;
return;
}
note = strtol(ptr, NULL, 10);
if (note < 0 || note > 127) {
eventtype = 0;
return;
}
// read the starttime
ptr = strtok(NULL, " \t\n");
if (ptr == NULL) {
eventtype = 0;
return;
}
vel = strtol(ptr, NULL, 10);
if (vel < 0 || vel > 127) {
eventtype = 0;
return;
}
eventtype = 1;
}
//////////////////////////////
//
// adjustbuffer -- remove comments and make lower characters
//
void adjustbuffer(char* buffer) {
int i = 0;
while (buffer[i] != '\0') {
buffer[i] = tolower(buffer[i]);
if (buffer[i] == ';') {
buffer[i] = '\0';
return;
}
i++;
}
}
//////////////////////////////
//
// checkOptions --
//
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("c|channel=i:0","MIDI Channel to play notes on (offset from 0)");
opts.define("debug=b", "debug mode to find errors in input file");
opts.define("max=i:100000", "maximum number of notes expected in input");
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: 22 Jan 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);
}
debugQ = opts.getBoolean("debug");
maxcount = opts.getInteger("max");
channel = opts.getInteger("channel");
if (channel < 0) {
channel = 0;
} if (channel > 15) {
channel = 15;
}
if (opts.getArgCount() != 2) {
usage(opts.getCommand().c_str());
exit(1);
}
}
//////////////////////////////
//
// example --
//
void example(void) {
}
//////////////////////////////
//
// usage --
//
void usage(const char* command) {
cout << "Usage: " << command << " textfile midifile" << endl;
}