//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Apr 16 22:10:12 PDT 2002
// Last Modified: Fri Apr 19 13:03:58 PDT 2002
// Last Modified: Thu Oct 23 20:59:02 PDT 2003 (compiling to a file)
// Filename: ...sig/doc/examples/all/timeplay/timeplay.cpp
// Syntax: C++
//
// Description: Play **kern entries in a Humdrum file through MIDI
// by following a **time spine.
//
#include "synthImprov.h"
#include "humdrum.h"
#include "MidiFile.h"
#include <string.h>
/*----------------- beginning of improvization algorithms ---------------*/
SigTimer timer; // for playing the humdrum file
EventBuffer eventBuffer; // for storing note offs when playing notes
HumdrumFile data; // humdrum file to play
MidiFile midifile; // for compiling into a MIDI file
Array<uchar> mididata(3);
int linenum = 0; // for keeping track of current line in file
int velocity = 64; // default velocity to play MIDI notes
int echoTextQ = 1; // boolean for displaying input file
int fileNumber = 1; // current file number being played
int initial_time = 0; // starting time of t_time
int mine = 0; // used with the -m option
int shortenQ = 0; // used with the -s option
int shortenamount = 0; // used with the -s option
int debugQ = 0; // used with the --debug option
int midifileQ = 0; // used with the -o option
char midifilename[2048] = {0}; // used with the -o option
Array<double> timings;
// function declarations:
void checkOptions(void);
void inputNewFile(HumdrumFile& data, Array<double>& timings);
void playdata(HumdrumFile& data, int& linenum, SigTimer& timer,
Array<double>& timings);
void printInputLine(HumdrumRecord& record);
void processNotes(HumdrumFile& data, int line, Array<double>& timings);
double getDeltaTimeValue(HumdrumFile& data, int linenum,
Array<double>& timings);
void getTimings(HumdrumFile& data, Array<double>& timings);
int getendline(HumdrumFile& data, int startline, double duration);
/*--------------------- maintenance algorithms --------------------------*/
void description(void) {
printboxtop();
psl(" TIMEPLAY -- by Craig Stuart Sapp <craig@ccrma.stanford.edu> "
"-- 16 Apr 2002\n");
printboxbottom();
}
void initialization(void) {
checkOptions();
timer.setPeriod(500);
timer.reset();
eventIdler.setPeriod(0);
eventBuffer.setPollPeriod(10);
if (midifileQ) {
midifile.setTicksPerQuarterNote(1000);
midifile.allocateEvents(0, 100000);
midifile.absoluteTime();
cout << "Saving MIDI data to the file " << midifilename << endl;
}
initial_time = t_time;
}
void finishup(void) {
eventBuffer.off();
if (midifileQ) {
midifile.sortTracks();
cout << "Checking: midifilename is " << midifilename << endl;
midifile.write(midifilename);
}
}
/*-------------------- main loop algorithms -----------------------------*/
void mainloopalgorithms(void) {
eventBuffer.checkPoll();
if (timer.expired()) {
playdata(data, linenum, timer, timings);
if (linenum >= data.getNumLines()) {
if (midifileQ) {
finishup();
exit(0);
}
inputNewFile(data, timings);
}
}
}
/*-------------------- triggered algorithms -----------------------------*/
void keyboardchar(int key) {
switch (key) {
case ' ': // toggle display of file while playing
echoTextQ = !echoTextQ;
if (echoTextQ) {
cout << "!! FILE DISPLAY TURNED ON" << endl;
} else {
cout << "!! FILE DISPLAY TURNED OFF" << endl;
}
break;
case 's': // silence notes
eventBuffer.off();
break;
}
}
/*------------------ end improvization algorithms -----------------------*/
//////////////////////////////
//
// checkOptions -- process command-line options and setup the
// humdrum data to play.
//
void checkOptions(void) {
options.define("o|output=s:output.mid");
options.define("p|pause=d:1.0", "pause time in seconds between files");
options.define("q|quiet=b", "Turn off data echoing while playing");
options.define("v|velocity=i:64", "Default MIDI key velocity");
options.define("m|min=i:0", "minimum tick duration of notes");
options.define("s|shorten=i:0", "shortening tick value for note durations");
options.define("debug=b", "debug messages turned on");
options.process();
midifileQ = options.getBoolean("output");
strcpy(midifilename, options.getString("output"));
debugQ = options.getBoolean("debug");
velocity = options.getInteger("velocity");
if (options.getBoolean("quiet")) {
echoTextQ = 0;
}
if (options.getArgCount() < 1) {
data.read(cin);
} else {
inputNewFile(data, timings);
}
mine = options.getInteger("min");
if (mine < 0) {
mine = 0;
}
shortenQ = options.getBoolean("shorten");
shortenamount = options.getInteger("shorten");
}
//////////////////////////////
//
// inputNewFile -- load in a new Humdrum file.
//
void inputNewFile(HumdrumFile& data, Array& timings) {
data.clear();
linenum = 0;
int count = options.getArgCount();
if (fileNumber > count) {
finishup();
exit(0);
}
data.read(options.getArg(fileNumber));
data.analyzeRhythm("4");
getTimings(data, timings);
if (fileNumber > 1) {
millisleep((float)(1000 * options.getDouble("pause")));
}
fileNumber++;
}
//////////////////////////////
//
// getTimings -- get the absolute timing values from the humdrum file.
//
void getTimings(HumdrumFile& data, Array& timings) {
int i, j;
timings.setSize(data.getNumLines());
timings.allowGrowth(0);
timings.setAll(-1.0);
for (i=0; i<data.getNumLines(); i++) {
if (data[i].isData()) {
// find the **time spine and extract the timing info
// (in milliseconds only for now
for (j=0; j<data[i].getFieldCount(); j++) {
if (strcmp(data[i].getExInterp(j), "**time") == 0) {
timings[i] = atof(data[i][j]);
break;
}
}
} else if (strncmp(data[i][0], "**", 2) == 0) {
// check for **time spine
int found = 0;
for (j=0; j<data[i].getFieldCount(); j++) {
if (strcmp(data[i][j], "**time") == 0) {
found = 1;
break;
}
}
if (!found) {
cout << "Error: The input humdrum file does not "
<< "have a **time spine" << endl;
exit(1);
}
timings[i] = -1.0;
} else {
// if it is not data then the time is just equal to the next
// data element; store -1 in the array for now and fill in later
timings[i] = -1.0;
}
}
// now work backwards to generate the timing information for
// 0 duration lines.
double current = -1.0;
for (i=timings.getSize()-1; i>=0; i--) {
if (timings[i] == -1.0) {
timings[i] = current;
} else {
current = timings[i];
}
}
// print timings
if (debugQ) {
for (int i=0; i<timings.getSize(); i++) {
cout << timings[i] << "\t" << data[i] << endl;
}
}
}
//////////////////////////////
//
// playdata -- play the next line of the humdrum file, update the
// line number and the time for the next events to be read
// from the file.
//
void playdata(HumdrumFile& data, int& linenum, SigTimer& timer,
Array<double>& timings) {
double duration = 0.0; // duration of the current line;
while (linenum < data.getNumLines() && duration == 0.0) {
duration = getDeltaTimeValue(data, linenum, timings);
if (data[linenum].isData()) {
processNotes(data, linenum, timings);
}
if (echoTextQ) {
printInputLine(data[linenum]);
}
if (duration > 0.0) {
timer.setPeriod(duration);
timer.reset();
}
linenum++;
}
}
//////////////////////////////
//
// printInputLine -- print the current line of the file,
// omitting the duration field at the end of the line
//
void printInputLine(HumdrumRecord& record) {
cout << record.getLine() << endl;
}
//////////////////////////////
//
// processNotes -- play all kern notes in the current record and
// return the shortest note duration.
//
void processNotes(HumdrumFile& data, int line, Array& timings) {
HumdrumRecord& record = data[line];
NoteEvent note;
int pitch = 0;
double duration = 0.0;
int staccatoQ = 0;
int accentQ = 0;
int sforzandoQ = 0;
int i, j;
int notecount = 0;
int endline = 0;
char buffer[128] = {0};
for (i=0; i<record.getFieldCount(); i++) {
if (record.getExInterpNum(i) == E_KERN_EXINT) {
notecount = record.getTokenCount(i);
if (strcmp(record[i], ".") == 0) {
continue;
}
for (j=0; j<notecount; j++) {
record.getToken(buffer, i, j);
if (strchr(buffer, '[')) {
// total tied note durations
endline = getendline(data, line, data.getTiedDuration(line,i,j));
duration = timings[endline] - timings[line];
} else {
endline = getendline(data,line,Convert::kernToDuration(buffer));
duration = timings[endline] - timings[line];
}
pitch = Convert::kernToMidiNoteNumber(buffer);
// skip rests
if (pitch < 0) {
continue;
}
// skip tied notes
if (strchr(buffer, '_') || strchr(buffer, ']')) {
continue;
}
accentQ = (int)strchr(buffer, '^');
sforzandoQ = (int)strchr(buffer, 'z');
staccatoQ = (int)strchr(buffer, '\'');
note.setChannel(0);
note.setKey(pitch);
note.setOnTime(t_time);
if (shortenQ) {
// duration -= shortenamount;
if (duration < mine) {
duration = mine;
}
}
note.setDur((int)(duration+0.5));
if (staccatoQ) {
note.setDur((int)(0.5 * note.getDur()));
}
note.setVelocity(velocity);
if (accentQ) {
note.setVelocity((int)(note.getVelocity() * 1.3));
}
if (sforzandoQ) {
note.setVelocity((int)(note.getVelocity() * 1.5));
}
if (!midifileQ) {
note.activate();
note.action(eventBuffer);
eventBuffer.insert(note);
} else {
mididata[0] = (0x0f & note.getChannel()) | 0x90;
mididata[1] = note.getKey();
mididata[2] = note.getVelocity();
midifile.addEvent(0, t_time - initial_time, mididata);
mididata[2] = 0;
midifile.addEvent(0,
t_time - initial_time + (int)(duration+0.5),
mididata);
}
}
}
}
}
//////////////////////////////
//
// getendline -- get the absolute duration in milliseconds of the given
// score position
//
int getendline(HumdrumFile& data, int startline, double duration) {
double starttime = data[startline].getAbsBeat();
double endtime = starttime;
int i = startline;
double matchdur = 0.0;
while (i<data.getNumLines()) {
i++;
endtime = data[i].getAbsBeat();
matchdur = endtime - starttime;
if (matchdur >= duration) {
return i;
}
}
return i;
}
//////////////////////////////
//
// getDeltaTimeValue -- return the current absolute time minus
// the previous absolute time.
//
double getDeltaTimeValue(HumdrumFile& data, int linenum,
Array<double>& timings) {
if (linenum == timings.getSize()-1) {
return 0.0;
}
return timings[linenum+1] - timings[linenum];
}
// md5sum: a55e37e4741c297a838bd18b67886ecb timeplay.cpp [20050403]