//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Jun 3 10:04:21 PDT 2000
// Last Modified: Tue Jul 4 17:11:21 PDT 2000
// Filename: ...sig/doc/examples/all/taptempo/taptempo.cpp
// Syntax: C++; synthImprov 2.0
//
// Description: A demonstration of various tempo beating algorithms.
// which use a binary switch.
//
#include "synthImprov.h"
#include <string.h>
/*----------------- beginning of improvization algorithms ---------------*/
#define MAXLAGTIME 70 /* max latency of beat simultaneaity */
// various note algorithms
#define GEN_MONO 1
#define GEN_CHROM 2
// various tempo algorithms
#define BEAT_AUTO 0 /* computer controlls tempo */
#define BEAT_CONST 1 /* next beat is always constant */
#define BEAT_FIXED 2 /* next beat is always fixed */
#define BEAT_LAST 3 /* next beat is last's beats tempo */
int generation = GEN_CHROM; // note generation algorithm to use
int control = BEAT_AUTO; // tempo generation algorithm to use
int divisions = 4; // number of notes per beat
int divisionChange = 0; // true when divisions change
int newdivisions = 4; // new division setting
int keyflag = 0; // for options control from computer keyboard
int keypresstime = 0; // for elegance of interface
CircularBuffer<int> beattime(1000); // time that the last beat occured
EventBuffer constcase; // for BEAT_CONST tempo case
Voice voice;
int running = 0; // 0 = off, 1 = on
SigTimer beatlocation; // for keeping track of time
double tempo = 60.0; // tempo for fixed tempo algorithms
double beatfraction = 0.0; // location in the beat
int displayBeatQ = 1; // boolean for displaying subbeat locations
// function declarations
void beat(void);
void checkfornote(double fraction);
const char* genString(int generation);
const char* tempoString(int tempotype);
void playnote(int subbeat, int subcount);
void constcaseinsert(int count, double totaldur);
void displaySubbeat(int subbeat, int div);
/*--------------------- maintenance algorithms --------------------------*/
//////////////////////////////
//
// description -- this function is called by the improv interface
// whenever a capital "D" is pressed on the computer keyboard.
// Put a description of the program and how to use it here.
//
void description(void) {
cout <<
"TEMPODEMO -- Craig Stuart Sapp <craig@ccrma.stanford.edu> June 2000\n"
"Commands:\n"
" g# = choose a note generation algorithm\n"
" d# = choose a beat division amount\n"
" t# = choose a tempo tracking algorithm\n"
" b = print the current beat fraction\n"
" s = toggle subbeat display\n"
" p = toggle periodic tempo display\n"
" z = toggle performance on/off\n"
" <,> = slow/speed tempo for certain tempo types\n"
<< endl;
}
//////////////////////////////
//
// initialization -- this function is called by the improv
// interface once at the start of the program. Put items
// here which need to be initialized at the beginning of
// the program.
//
void initialization(void) {
voice.setChannel(0);
voice.setPort(synth.getInputPort());
beatlocation.setTempo(tempo);
eventIdler.setPeriod(0);
constcase.setPollPeriod(10);
displayBeatQ = 1;
beattime.reset();
beattime[0] = 0;
beattime[1] = 0;
beattime[2] = 0;
beattime[3] = 0;
}
//////////////////////////////
//
// finishup -- this function is called by the improv interface
// whenever the program is exited. Put items here which
// need to be taken care of when the program is finished.
//
void finishup(void) {
voice.off();
}
/*-------------------- main loop algorithms -----------------------------*/
//////////////////////////////
//
// mainloopalgorithms -- this function is called by the improv interface
// continuously while the program is running. The global variable t_time
// which stores the current time is set just before this function is
// called and remains constant while in this functions.
//
void mainloopalgorithms(void) {
if (control == BEAT_AUTO) {
beatfraction = beatlocation.getPeriodCount() - beatlocation.expired();
} else {
beatfraction = beatlocation.getPeriodCount();
}
if (running) {
if (control == BEAT_CONST) {
constcase.checkPoll();
} else {
checkfornote(beatfraction);
}
} else {
beatfraction = -1;
}
}
//////////////////////////////
//
// checkfornote -- determine if it is time to play a new note
// based on the beat location.
//
void checkfornote(double fraction) {
// always wait for the next beat to occur
if (fraction > 1.0) {
return;
}
static double lastfraction = 0.0;
static int lastnote = -1;
static int subbeat = 0;
if (fraction < lastfraction && divisions == 1 && control != BEAT_CONST) {
playnote(0, 1);
lastfraction = fraction;
displaySubbeat(0, divisions);
if (divisionChange) {
divisions = newdivisions;
divisionChange = 0;
}
return;
}
if (control == BEAT_CONST) {
lastfraction = fraction;
return;
}
if (fraction >= 1.0 && control == BEAT_FIXED) {
lastfraction = fraction;
return;
}
subbeat = (int)(fraction * divisions);
if (lastnote != subbeat) {
if (subbeat == 0 && divisionChange) {
divisions = newdivisions;
divisionChange = 0;
}
if (subbeat == 0 && lastnote != divisions - 1) {
for (int j=0; j<divisions - 1 - lastnote; j++) {
playnote(lastnote + j + 1, divisions);
displaySubbeat(lastnote + j + 1, divisions);
}
}
playnote(subbeat, divisions);
lastnote = subbeat;
displaySubbeat(subbeat, divisions);
}
if (subbeat > divisions) {
subbeat = divisions - 1;
}
if (lastnote > divisions) {
lastnote = divisions - 1;
}
lastfraction = fraction;
}
void displaySubbeat(int subbeat, int div) {
if (displayBeatQ == 0) {
return;
}
if (subbeat == 0) {
cout << endl;
} else {
cout << " ";
}
cout << subbeat << flush;
}
/*-------------------- triggered algorithms -----------------------------*/
/* uncomment these line to use with the Radio Baton void stick1trig(void) { beat(); }
void stick2trig(void) { beat(); }
void b14plustrig(void) { beat(); }
void b15plustrig(void) { beat(); }
void b14minusuptrig(void) { beat(); }
void b14minusdowntrig(void) { beat(); }
void b15minusuptrig(void) { beat(); }
void b15minusdowntrig(void) { beat(); }
*/
///////////////////////////////
//
// keyboardchar -- this function is called by the improv interface
// whenever a key is pressed on the computer keyboard.
// Put commands here which will be executed when a key is
// pressed on the computer keyboard.
//
void keyboardchar(int key) {
keypresstime = t_time;
switch (key) {
case 'b': // print the current beat fraction
cout << "Beat fraction = " << beatfraction << endl;
break;
case 's': // toggle showing of subbeat locations
displayBeatQ = !displayBeatQ;
if (displayBeatQ) {
cout << "Subbeat display is turned ON" << endl;
} else {
cout << "Subbeat display is turned OFF" << endl;
}
break;
case 'z': // toggle performance
running = !running;
if (running) {
cout << "Performing" << endl;
beatlocation.reset();
} else {
cout << "Performance stopped" << endl;
}
break;
case ' ':
beat();
break;
case 'g':
cout << "Choose a number between 1 and 9 to control note algorithm"
<< endl;
keyflag = 'g';
break;
case 'd':
cout << "Choose a number between 1 and 9 to select beat division"
<< endl;
keyflag = 'd';
break;
case 't':
cout << "Choose a number between 1 and 9 to control tempo algorithm"
<< endl;
keyflag = 't';
break;
case '<': // slow the tempo down for auto/fixed/const tempo options
case ',': // slow the tempo down for auto/fixed/const tempo options
tempo /= 1.02;
beatlocation.setTempo(tempo);
cout << "Fixed tempo set to: " << tempo << endl;
break;
case '>': // speed up the tempo down for auto/fixed/const tempo options
case '.': // speed up the tempo down for auto/fixed/const tempo options
tempo *= 1.02;
beatlocation.setTempo(tempo);
cout << "Auto/const/fixed tempo set to: " << tempo << endl;
break;
}
if (isdigit(key)) {
switch (keyflag) {
case 'g':
generation = key - '0'; // note generation algorithm to use
cout << "Generation algorithm is: " << genString(generation)
<< endl;
break;
case 'd':
if (key == '0') {
break;
}
if (keypresstime - beattime[0] < MAXLAGTIME) {
divisions = key - '0'; // number of notes per beat
cout << "Time diff: " << keypresstime - beattime[0] << endl;
cout << "Beat division: " << divisions << endl;
divisionChange = 0;
} else {
newdivisions = key - '0'; // number of notes per beat
cout << "Time diff: " << keypresstime - beattime[0] << endl;
cout << "Beat division: " << divisions << endl;
divisionChange = 1;
}
break;
case 't':
control = key - '0'; // tempo generation algorithm to use
cout << "Tempo algorithm is: " << tempoString(control) << endl;
break;
case 'q': // quiet
running = 0;
break;
default:
keyflag = 0;
}
}
}
/*------------------ end improvization algorithms -----------------------*/
//////////////////////////////
//
// beat -- cause a new beat to occur.
//
void beat(void) {
beattime.insert(t_time);
beatlocation.reset();
if (running == 0) {
running = 1;
beatlocation.reset();
}
if (control == BEAT_CONST) {
constcaseinsert(divisions, beatlocation.getPeriod());
return;
}
int diff = beattime[0] - beattime[1];
// ignore initial beats
if (diff > 5000) {
return;
}
return;
beatlocation.setPeriod(diff);
}
//////////////////////////////
//
// genString -- print the note generation algorithm type.
//
const char* genString(int generation) {
static char string[128] = {0};
switch (generation) {
case GEN_MONO:
strcpy(string, "Unison");
break;
case GEN_CHROM:
strcpy(string, "Chromatic");
break;
default:
strcpy(string, "Unknown");
break;
}
return string;
}
//////////////////////////////
//
// playnote -- play notes for beating
//
void playnote(int subbeat, int subcount) {
if (control == BEAT_AUTO && subbeat == 0) {
beattime.insert(t_time);
}
if (subbeat == subcount) {
cout << "Illegal beat: " << subbeat << "\tdivisions = "
<< subcount << endl;
}
voice.play(60 + subbeat, 64);
}
//////////////////////////////
//
// tempoString -- print the note generation algorithm type.
//
const char* tempoString(int tempotype) {
static char string[128] = {0};
switch (tempotype) {
case BEAT_AUTO:
strcpy(string, "Auto Tempo");
break;
case BEAT_CONST:
strcpy(string, "Const Tempo");
break;
case BEAT_FIXED:
strcpy(string, "Fixed Tempo");
break;
case BEAT_LAST:
strcpy(string, "Last Beat-duration Tempo");
break;
default:
strcpy(string, "Unknown");
break;
}
return string;
}
//////////////////////////////
//
// constcaseinsert -- add all notes
//
void constcaseinsert(int count, double totaldur) {
static NoteEvent note; // temporary note for placing in buffer
double starttime = mainTimer.getTime();
double duration;
if (count > 0) {
duration = totaldur / count;
} else {
duration = 0.0;
}
int basenote = 60;
int i;
for (i=0; i<count; i++) {
note.setOnDur((int)starttime, (int)duration);
note.setKey(basenote + i);
note.setVel(64);
note.setChan(0);
note.activate();
constcase.insert(note);
starttime += duration;
}
}
// md5sum: b1cbaa2e0800e5e5e10f804ebd01e97f taptempo.cpp [20050403]