478 lines
12 KiB
C++
478 lines
12 KiB
C++
//
|
|
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
|
// Creation Date: Wed Jan 9 14:29:54 PST 2002
|
|
// Last Modified: Thu Apr 3 10:23:41 PST 2008 (added Humdrum output)
|
|
// Filename: tools/henonfile.cpp
|
|
// URL: https://github.com/craigsapp/midifile/blob/master/tools/henonfile.cpp
|
|
// Syntax: C++11
|
|
// vim: ts=3
|
|
//
|
|
// Description: Creates a fractal melodic line based on the
|
|
// Henon Map. Output can be either a MIDI file, Guido
|
|
// Music Notation (GMN), Humdrum, or plain text.
|
|
//
|
|
|
|
#include "MidiFile.h"
|
|
#include "Convert.h"
|
|
#include "Options.h"
|
|
#include "CircularBuffer.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
using namespace std;
|
|
using namespace smf;
|
|
|
|
|
|
void checkOptions (Options& opts, int argc, char** argv);
|
|
void example (void);
|
|
void usage (const char* command);
|
|
void createHenon (double alpha, double beta, double x0,
|
|
double y0, int maxcount, MidiFile& midifile);
|
|
int checkTermination (int key);
|
|
void storeInMidiFile (MidiFile& midifile, int key);
|
|
void printGuidoNotation (void);
|
|
void printHumdrumNotation(void);
|
|
char* convertMidiToGuido (char* buffer, Array<char> notelist, int index);
|
|
void printLeftHandGuido (void);
|
|
void printRightHandGuido (void);
|
|
|
|
// User interface variables:
|
|
Options options;
|
|
int maxcount = 10000; // used with the -n option
|
|
double alpha = -1.56693; // used with the -a option
|
|
double beta = -0.011811; // used with the -b option
|
|
double x0 = 0.0; // x-axis starting point
|
|
double y0e = 0.0; // y-axis starting point
|
|
int textQ = 0; // used with the --text option
|
|
int guidoQ = 0; // used with the -g option
|
|
int humdrumQ = 0; // used with the -u option
|
|
int repeatQ = 0; // used with the -r option
|
|
const char* filename = "test.mid"; // filename to write MIDI file
|
|
int tpq = 96; // ticks per quarter note in MIDI file
|
|
int divisions = 4; // number of notes per quarter note
|
|
int instrument = 0; // used with the -i option
|
|
double tempo = 120.0; // used with the -t option
|
|
Array<char> notelist; // for printing in Guido Music Notation
|
|
int minNote = 30; // minimum note to play
|
|
int maxNote = 100; // maximum note to play
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
int main(int argc, char** argv) {
|
|
checkOptions(options, argc, argv); // process the command-line options
|
|
|
|
MidiFile midifile;
|
|
midifile.setTicksPerQuarterNote(tpq);
|
|
midifile.allocateEvents(0, 2 * maxcount + 500); // pre allocate space for
|
|
// max expected MIDI events
|
|
notelist.setSize(maxcount+10);
|
|
notelist.setSize(0);
|
|
notelist.allowGrowth();
|
|
|
|
midifile.absoluteTime();
|
|
|
|
Array<uchar> mididata(2);
|
|
mididata[0] = 0xc0; // patch change on MIDI channel 1
|
|
mididata[1] = (uchar) instrument; // user input instrument
|
|
midifile.addEvent(0, 0, mididata);
|
|
|
|
// write the tempo to the midifile
|
|
mididata.setSize(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);
|
|
|
|
createHenon(alpha, beta, x0, y0e, maxcount, midifile);
|
|
if (guidoQ) {
|
|
printGuidoNotation();
|
|
} else if (humdrumQ) {
|
|
printHumdrumNotation();
|
|
} else {
|
|
midifile.write(filename);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// createHenon --
|
|
//
|
|
|
|
void createHenon(double alpha, double beta, double x0, double y0e,
|
|
int maxcount, MidiFile& midifile) {
|
|
|
|
double x = x0;
|
|
double y = y0e;
|
|
int termination = 0;
|
|
|
|
for (int i=0; i<maxcount; i++) {
|
|
double newx = 1 + alpha * x * x + beta * y;
|
|
double newy = x;
|
|
x = newx;
|
|
y = newy;
|
|
|
|
int key = (int)((x + 1.0)/2.0 * 127.0 + 0.5);
|
|
if (key < minNote) {
|
|
key = 0;
|
|
}
|
|
if (key > maxNote) {
|
|
key = 0;
|
|
}
|
|
if (repeatQ) {
|
|
termination = 0;
|
|
} else {
|
|
termination = checkTermination(key);
|
|
}
|
|
if (textQ) {
|
|
cout << key << "\n";
|
|
if (termination != 0) {
|
|
cout << "REPEAT" << termination << endl;
|
|
exit(0);
|
|
}
|
|
} else {
|
|
storeInMidiFile(midifile, key);
|
|
if (termination != 0) {
|
|
midifile.write(filename);
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// storeInMidiFile --
|
|
//
|
|
|
|
void storeInMidiFile(MidiFile& midifile, int key) {
|
|
static int timer = tpq; // start after one beat (for patch change)
|
|
char note = (char)key;
|
|
|
|
// don't store extreme notes -- this gives interesting rhythms sometimes.
|
|
if (key < minNote || key > maxNote) {
|
|
note = 0;
|
|
notelist.append(note);
|
|
timer += tpq/divisions;
|
|
return;
|
|
}
|
|
notelist.append(note); // store note for displaying Guido Notation
|
|
Array<uchar> midinote(3);
|
|
midinote[0] = 0x90;
|
|
midinote[1] = key;
|
|
midinote[2] = 64;
|
|
midifile.addEvent(0, timer, midinote);
|
|
midinote[0] = 0x80;
|
|
timer += tpq/divisions;
|
|
midifile.addEvent(0, timer, midinote);
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// checkTermination --
|
|
//
|
|
|
|
int checkTermination(int key) {
|
|
static CircularBuffer<int> memory;
|
|
static int init = 0;
|
|
if (init == 0) {
|
|
init = 1;
|
|
memory.setSize(1000);
|
|
memory.reset();
|
|
}
|
|
memory.insert(key);
|
|
if (memory.getCount() < 40 + 10) {
|
|
return 0;
|
|
}
|
|
|
|
for (int j=1; j<20; j++) {
|
|
int cycleQ = 1;
|
|
for (int i=0; i<40; i++) {
|
|
if (memory[i] != memory[i+j]) {
|
|
cycleQ = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (cycleQ == 1) {
|
|
return j;
|
|
}
|
|
}
|
|
|
|
// no 1-9 period cycles detected
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// checkOptions --
|
|
//
|
|
|
|
void checkOptions(Options& opts, int argc, char* argv[]) {
|
|
opts.define("n|max-number=i:10000", "Maximum number of notes to generate");
|
|
opts.define("a|alpha=d:-1.56693", "alpha factor");
|
|
opts.define("b|beta=d:-0.011811", "beta factor");
|
|
opts.define("x|x0=d:0.0", "initial x value");
|
|
opts.define("y|y0=d:0.0", "initial y value");
|
|
opts.define("max|max-note=i:100", "maximum note to play; higher is rest");
|
|
opts.define("min|min-note=i:30", "minimum note to play; lower is rest");
|
|
opts.define("text=b", "display output as text only");
|
|
opts.define("g|guido=b", "Guido Music Notation output");
|
|
opts.define("u|humdrum=b", "Humdrum data file output");
|
|
opts.define("i|instrument=i:0", "General MIDI instrument number");
|
|
opts.define("t|tempo=d:120", "Tempo");
|
|
opts.define("d|divisions=i:4", "Number of notes per quarter note");
|
|
opts.define("r|allow-repeats=b", "Do not stop at cyclical patterns");
|
|
|
|
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, Jan 2002" << endl;
|
|
exit(0);
|
|
} else if (opts.getBoolean("version")) {
|
|
cout << argv[0] << ", version: 3 Apr 2008" << 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);
|
|
}
|
|
|
|
maxcount = opts.getInteger("max-number");
|
|
alpha = opts.getDouble("alpha");
|
|
beta = opts.getDouble("beta");
|
|
x0 = opts.getDouble("x0");
|
|
y0e = opts.getDouble("y0");
|
|
textQ = opts.getBoolean("text");
|
|
guidoQ = opts.getBoolean("guido");
|
|
humdrumQ = opts.getBoolean("humdrum");
|
|
if (textQ == 1) {
|
|
guidoQ = 0;
|
|
humdrumQ = 0;
|
|
}
|
|
repeatQ = opts.getBoolean("allow-repeats");
|
|
tempo = opts.getDouble("tempo");
|
|
divisions = opts.getInteger("divisions");
|
|
instrument = opts.getInteger("instrument");
|
|
|
|
maxNote = opts.getInteger("max-note");
|
|
minNote = opts.getInteger("min-note");
|
|
if (minNote < 0) minNote = 0;
|
|
if (minNote > 126) minNote = 126;
|
|
if (maxNote < 0) maxNote = 0;
|
|
if (maxNote > 126) maxNote = 126;
|
|
if (minNote > maxNote) {
|
|
int temp = minNote;
|
|
minNote = maxNote;
|
|
maxNote = temp;
|
|
}
|
|
|
|
if (instrument < 0) {
|
|
instrument = 0;
|
|
} else if (instrument > 127) {
|
|
instrument = 127;
|
|
}
|
|
|
|
if (tempo < 4) {
|
|
tempo = 4;
|
|
} else if (tempo > 1000) {
|
|
tempo = 1000;
|
|
}
|
|
|
|
if (humdrumQ == 0 && guidoQ == 0 && textQ == 0 && opts.getArgCount() != 1) {
|
|
usage(opts.getCommand());
|
|
exit(1);
|
|
}
|
|
if (humdrumQ == 0 && textQ == 0 && guidoQ == 0) {
|
|
filename = opts.getArg(1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// example --
|
|
//
|
|
|
|
void example(void) {
|
|
// to add
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// usage --
|
|
//
|
|
|
|
void usage(const char* command) {
|
|
cout << "Usage: " << command << " outputfile" << endl;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printGuidoNotation --
|
|
//
|
|
|
|
void printGuidoNotation(void) {
|
|
cout << "{ ";
|
|
printRightHandGuido();
|
|
cout << ", ";
|
|
printLeftHandGuido();
|
|
cout << " }\n";
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printHumdrumNotation --
|
|
//
|
|
|
|
void printHumdrumNotation(void) {
|
|
char buffer[1024] = {0};
|
|
cout << "!!!alpha:\t" << alpha << "\n";
|
|
cout << "!!!beta:\t" << beta << "\n";
|
|
cout << "!!!start:\t(" << x0 << ", " << y0e << ")\n";
|
|
cout << "**kern\n";
|
|
int i;
|
|
for (i=0; i<notelist.getSize(); i++) {
|
|
cout << "16";
|
|
if (notelist[i] <= 0) {
|
|
cout << "r";
|
|
} else {
|
|
cout << Convert::base12ToKern(buffer, notelist[i]);
|
|
}
|
|
cout << "\n";
|
|
if ((i+1) % 32 == 0) {
|
|
cout << "=" << (i / 32) + 2 << "\n";
|
|
}
|
|
|
|
}
|
|
cout << "*-" << endl;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printRightHandGuido --
|
|
//
|
|
|
|
void printRightHandGuido(void) {
|
|
char buffer[128] = {0};
|
|
cout << "[";
|
|
int i;
|
|
cout << "\\meter<\"2/4\">\n";
|
|
for (i=0; i<notelist.getSize(); i++) {
|
|
if (notelist[i] >= 60) {
|
|
cout << convertMidiToGuido(buffer, notelist, i) << " ";
|
|
} else {
|
|
cout << "_/16" << " ";
|
|
}
|
|
if ((i+1)%16 == 0) {
|
|
cout << "\n\t";
|
|
}
|
|
}
|
|
cout << "]\n";
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// printLeftHandGuido --
|
|
//
|
|
|
|
void printLeftHandGuido(void) {
|
|
char buffer[128] = {0};
|
|
cout << "[";
|
|
int i;
|
|
cout << "\\meter<\"2/4\">\n";
|
|
for (i=0; i<notelist.getSize(); i++) {
|
|
if (notelist[i] < 60) {
|
|
cout << convertMidiToGuido(buffer, notelist, i) << " ";
|
|
} else {
|
|
cout << "_/16" << " ";
|
|
}
|
|
if ((i+1)%16 == 0) {
|
|
cout << "\n\t";
|
|
}
|
|
}
|
|
cout << "]\n";
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
//
|
|
// convertMidiToGuido -- convert from MIDI note number to Guido Music
|
|
// Notation note name.
|
|
//
|
|
|
|
char* convertMidiToGuido(char* buffer, Array<char> notelist, int index) {
|
|
if (notelist[index] == 0) {
|
|
strcpy(buffer, "_/16");
|
|
return buffer;
|
|
}
|
|
int octave = (int)notelist[index] / 12 - 4;
|
|
if (octave > 2) octave--;
|
|
|
|
char octbuf[32] = {0};
|
|
sprintf(octbuf, "%d", octave);
|
|
|
|
int pc = (int)notelist[index] % 12;
|
|
switch (pc) {
|
|
case 0: strcpy(buffer, "c"); break;
|
|
case 1: strcpy(buffer, "c#"); break;
|
|
case 2: strcpy(buffer, "d"); break;
|
|
case 3: strcpy(buffer, "e&"); break;
|
|
case 4: strcpy(buffer, "e"); break;
|
|
case 5: strcpy(buffer, "f"); break;
|
|
case 6: strcpy(buffer, "f#"); break;
|
|
case 7: strcpy(buffer, "g"); break;
|
|
case 8: strcpy(buffer, "a&"); break;
|
|
case 9: strcpy(buffer, "a"); break;
|
|
case 10: strcpy(buffer, "b&"); break;
|
|
case 11: strcpy(buffer, "b"); break;
|
|
}
|
|
strcat(buffer, octbuf);
|
|
strcat(buffer, "/16");
|
|
return buffer;
|
|
}
|
|
|
|
|
|
|