354 lines
7.9 KiB
C++
354 lines
7.9 KiB
C++
//
|
|
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
|
// Creation Date: Mon Apr 6 13:54:09 PDT 2009
|
|
// Last Modified: Mon Apr 6 13:54:12 PDT 2009
|
|
// Filename: tools/peep2midi.cpp
|
|
// URL: https://github.com/craigsapp/midifile/blob/master/tools/peep2midi.cpp
|
|
// Syntax: C++11
|
|
// vim: ts=3
|
|
//
|
|
// Description: Convert Performance Expression Extraction Program
|
|
// output data into MIDI data.
|
|
//
|
|
|
|
#include "MidiFile.h"
|
|
#include "humdrum.h"
|
|
#include "Options.h"
|
|
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
using namespace std;
|
|
using namespace smf;
|
|
|
|
|
|
void checkOptions (Options& opts, int argc, char** argv);
|
|
void createMidiFile (MidiFile& midifile, HumdrumFile& infile);
|
|
int getMIDIKeyNum (const char* string);
|
|
int getTrackNumber (const char* string);
|
|
void example (void);
|
|
void usage (const char* command);
|
|
|
|
|
|
// User interface variables:
|
|
Options options;
|
|
int debugQ = 0; // use with --debug option
|
|
int maxcount = 100000;
|
|
int mindyn = 30; // use with -r option
|
|
int maxdyn = 120; // use with -r option
|
|
double duration = 0.1; // use with -d option
|
|
const char* filename = ""; // use with -o option
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int main(int argc, char* argv[]) {
|
|
checkOptions(options, argc, argv);
|
|
HumdrumFile infile;
|
|
infile.read(options.getArg(1));
|
|
MidiFile midifile;
|
|
createMidiFile(midifile, infile);
|
|
|
|
if (strcmp(filename, "") == 0) {
|
|
cout << midifile;
|
|
} else {
|
|
midifile.write(filename);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// createMidiFile --
|
|
// input humdrum file data should be in simple columns in this order:
|
|
//
|
|
// **data **data **data **data **data
|
|
// 1.667 18.8 F3 -2 1
|
|
// 1.666 19 C4 -1 1
|
|
// 1.667 10.7 a4 -2 1
|
|
// 1.667 19.2 c5 -2 1
|
|
// 2.009 24 F3 +10 1
|
|
// 2.01 24.2 C4 +10 1
|
|
//
|
|
// Column one is the time in seconds at which the note is started
|
|
// Column two is a dynamic value (the range of which will be normalized)
|
|
// Column three contains the pitch information.
|
|
//
|
|
|
|
void createMidiFile(MidiFile& midifile, HumdrumFile& infile) {
|
|
|
|
Array<int> millitimes;
|
|
Array<double> velocities;
|
|
Array<int> keynum;
|
|
Array<int> track;
|
|
|
|
millitimes.setSize(infile.getNumLines());
|
|
velocities.setSize(infile.getNumLines());
|
|
keynum.setSize(infile.getNumLines());
|
|
track.setSize(infile.getNumLines());
|
|
|
|
millitimes.setSize(0);
|
|
velocities.setSize(0);
|
|
keynum.setSize(0);
|
|
track.setSize(0);
|
|
|
|
int intval;
|
|
double floatval;
|
|
double dmax = -100000;
|
|
double dmin = +100000;
|
|
|
|
int i;
|
|
for (i=0; i<infile.getNumLines(); i++) {
|
|
if (!infile[i].isData()) {
|
|
continue;
|
|
}
|
|
sscanf(infile[i][0], "%lf", &floatval);
|
|
intval = int(floatval * 1000.0 + 0.5);
|
|
millitimes.append(intval);
|
|
|
|
sscanf(infile[i][1], "%lf", &floatval);
|
|
velocities.append(floatval);
|
|
if (floatval < dmin) { dmin = floatval; }
|
|
if (floatval > dmax) { dmax = floatval; }
|
|
|
|
intval = getMIDIKeyNum(infile[i][2]);
|
|
keynum.append(intval);
|
|
|
|
intval = getTrackNumber(infile[i][2]);
|
|
track.append(intval);
|
|
}
|
|
millitimes.allowGrowth(0);
|
|
velocities.allowGrowth(0);
|
|
keynum.allowGrowth(0);
|
|
track.allowGrowth(0);
|
|
|
|
// normalize the dynamics data into the range from 0 to 1
|
|
double diff = dmax - dmin;
|
|
for (i=0; i<velocities.getSize(); i++) {
|
|
if (diff > 0.0) {
|
|
velocities[i] = (velocities[i] - dmin) / diff;
|
|
} else {
|
|
velocities[i] = 0.5;
|
|
}
|
|
}
|
|
|
|
|
|
// now ready to write the data to the MIDI file:
|
|
|
|
midifile.setMillisecondDelta(); // SMPTE 25 frames & 40 subframes
|
|
midifile.absoluteTime(); // Time values inserted are absolute
|
|
midifile.addTrack(2); // Right and Left hands
|
|
|
|
Array<uchar> event;
|
|
event.setSize(3);
|
|
|
|
int intdur = int(duration * 1000.0 + 0.5);
|
|
int lasttime = 0;
|
|
int dyndiff = maxdyn - mindyn;
|
|
int vel;
|
|
for (i=0; i<millitimes.getSize(); i++) {
|
|
if ((keynum[i] <= 10) || (keynum[i] > 127)) {
|
|
continue;
|
|
}
|
|
vel = int(velocities[i] * dyndiff + mindyn + 0.5);
|
|
if (vel < 1) { vel = 1; }
|
|
if (vel > 127) { vel = 127; }
|
|
|
|
event[0] = 0x90; // note-on
|
|
event[1] = keynum[i];
|
|
event[2] = vel;
|
|
midifile.addEvent(track[i], millitimes[i], event);
|
|
event[2] = 0;
|
|
lasttime = millitimes[i] + intdur;
|
|
midifile.addEvent(track[i], lasttime, event);
|
|
}
|
|
|
|
// write the end of track marker
|
|
event[0] = 0xff;
|
|
event[1] = 0x2f;
|
|
event[2] = 0;
|
|
for (i=0; i<midifile.getTrackCount(); i++) {
|
|
|
|
if (i>0) {
|
|
// have to lengthen the last note in track due to bugs
|
|
// in various MIDI playback programs which clip
|
|
// the last chord of a file
|
|
midifile.getEvent(i, midifile.getNumEvents(i)-1).time += 1500;
|
|
}
|
|
midifile.addEvent(i, lasttime+2000, event);
|
|
}
|
|
|
|
// add comments from header
|
|
for (i=0; i<infile.getNumLines() && i<lasttime; i++) {
|
|
if (infile[i].isBibliographic() || infile[i].isGlobalComment()) {
|
|
// 0x01 is a text event
|
|
midifile.addMetaEvent(0, i, 0x01, infile[i].getLine());
|
|
}
|
|
}
|
|
|
|
// sort the ontimes and offtimes so they are in correct time order:
|
|
midifile.sortTracks();
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// getTrackNumber -- lowercase pitch name = right hand; uppercase = left hand
|
|
//
|
|
|
|
int getTrackNumber(const char* string) {
|
|
if (islower(string[0])) {
|
|
return 1;
|
|
} else {
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// getMIDIKeyNum --
|
|
//
|
|
|
|
int getMIDIKeyNum(const char* string) {
|
|
int accid = 0;
|
|
int octave = -1;
|
|
int len = strlen(string);
|
|
int diatonic = -1;
|
|
|
|
switch (tolower(string[0])) {
|
|
case 'c': diatonic = 0; break;
|
|
case 'd': diatonic = 2; break;
|
|
case 'e': diatonic = 4; break;
|
|
case 'f': diatonic = 5; break;
|
|
case 'g': diatonic = 7; break;
|
|
case 'a': diatonic = 9; break;
|
|
case 'b': diatonic = 11; break;
|
|
}
|
|
|
|
if (diatonic < 0) {
|
|
return -1;
|
|
}
|
|
|
|
int i;
|
|
for (i=0; i<len; i++) {
|
|
if (string[i] == '#') {
|
|
accid++;
|
|
} else if (string[i] == '-') {
|
|
accid--;
|
|
}
|
|
if (isdigit(string[i]) && (octave < 0)) {
|
|
sscanf(&(string[i]), "%d", &octave);
|
|
}
|
|
}
|
|
|
|
if (octave < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return diatonic + octave * 12 + accid;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// checkOptions --
|
|
//
|
|
|
|
void checkOptions(Options& opts, int argc, char* argv[]) {
|
|
opts.define("o|output=s", "output midi file name");
|
|
opts.define("r|range=s", "dynamics range");
|
|
|
|
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: 22 Jan 2002" << endl;
|
|
cout << "compiled: " << __DATE__ << endl;
|
|
exit(0);
|
|
} else if (opts.getBoolean("help")) {
|
|
usage(opts.getCommand());
|
|
exit(0);
|
|
} else if (opts.getBoolean("example")) {
|
|
example();
|
|
exit(0);
|
|
}
|
|
|
|
if (opts.getArgCount() != 1) {
|
|
usage(opts.getCommand());
|
|
exit(1);
|
|
}
|
|
|
|
filename = opts.getString("output");
|
|
if (opts.getBoolean("range")) {
|
|
int count = sscanf(opts.getString("range"), "%d-%d", &mindyn, &maxdyn);
|
|
if (count != 2) {
|
|
count = sscanf(opts.getString("range"), "%d:%d", &mindyn, &maxdyn);
|
|
}
|
|
if (count != 2) {
|
|
count = sscanf(opts.getString("range"), "%d,%d", &mindyn, &maxdyn);
|
|
}
|
|
if (count != 2) {
|
|
count = sscanf(opts.getString("range"), "%d, %d", &mindyn, &maxdyn);
|
|
}
|
|
if (count != 2) {
|
|
/* count = */ sscanf(opts.getString("range"), "%d %d", &mindyn, &maxdyn);
|
|
}
|
|
}
|
|
|
|
if (mindyn > maxdyn) {
|
|
int temp = mindyn;
|
|
mindyn = maxdyn;
|
|
maxdyn = temp;
|
|
}
|
|
|
|
if (mindyn == maxdyn) {
|
|
mindyn = 20;
|
|
maxdyn = 120;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// example --
|
|
//
|
|
|
|
void example(void) {
|
|
// add examples here
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// usage --
|
|
//
|
|
|
|
void usage(const char* command) {
|
|
cout << "Usage: " << command << " midifile" << endl;
|
|
}
|
|
|
|
|
|
|