//
// Programmer: Dan Gang <dang@ccrma.stanford.edu>
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed May 5 21:42:45 PDT 1999
// Last Modified: Sun May 9 14:49:50 PDT 1999
// Filename: ...sig/doc/examples/all/nana1/nana1.cpp
// Syntax: C++; synthImprov 2.1; sigNet 1.0
//
// Description: Auto acompaniment of a simple melody.
// Initial conversion of rule-based automatic
// accompaniment.
//
#include "synthImprov.h" /* MIDI interface handling functions */
// program defines:
#define ON 1 /* play a chord */
#define OFF 0 /* turn off a chord */
#define CH_1 0 /* MIDI channel 1 */
#define CH_10 9 /* MIDI channel 10 */
#define LOUD_TICK 115 /* Metronome accented(beat 1) */
#define SOFT_TICK 75 /* Metronome unaccented(beats 2-4) */
#define VEL 75 /* default attack velocity of chords */
#define HARMONICRHYTHM 2 /* 2=harmonic rhythm of half-notes */
/* note that this program probably */
/* cannot handle any changes to this */
/* define other than original value 2 */
#define LAGMAXINSEC 0.075 /* maximum 50 millisecond decision */
/* delay in output of chord from */
/* occurance of chordal beat */
#define DIMINISHED_TRIAD 0 /* the following defines are for */
#define MAJOR_TRIAD 1 /* determining the chord quality */
#define MINOR_TRIAD 2 /* in the playChord functions */
#define HALF_DIM_7TH 3
#define Mm_7TH 4
// program typedefs:
typedef void(*ChordPlayFunction)(int note, int chordstate);
// non-improv assistant function declarations defined at bottom of program:
int chordBeat(int beat, int meter);
void playChordByNet(int note, int chordstate);
void playChordByRules(int note, int chordstate);
double calculateMaxWait(double tempo);
void playMetronome(int beat);
// global variables for the program:
double tempo = 80; // tempo of the accompaniment
int currentnote = 60; // note which was used to play the current chord
int note = 0; // last notemessage note coming from synth
SigTimer metronome; // object to handle time for beats
int meter = 4; // meter to play chords in
int beat; // the current beat in the meter (offset by 1)
double beatfraction; // the fractional part of the beat
int notestates[128] = {0}; // booleans for keyboard notes on/off
int oldstate; // previous state position in beat
int state; // current state position in beat
int stateChange; // boolean for testing if state has changed
int notescan = 0; // boolean for looking for a new currentnote
double maxwait = 0.10; // maximum wait time after a beat before
// choosing a default chord
MidiMessage notemessage; // for extracting notes from the Synthesizer
double lagmaxinsec = 0.075; // maximum 75 millisecond decision
// delay in output of chord from
// occurance of chordal beat
ChordPlayFunction playChord = NULL; // func for playing chordal accompaniment
/*----------------- beginning of improvization algorithms ---------------*/
/*--------------------- 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 <<
"NaNa ver. 6 May 1999: Automatic Chordal Accompaniment of simple melodies\n"
" Computer Keyboard commands: \n"
" , or < = slow down the tempo\n"
" . or > = speed up the tempo\n"
" [ = decrease chord decision lag time\n"
" ] = increase chord decision lag time\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) {
cout << "Enter a tempo for melody performance: ";
echoKeysOn();
cin >> tempo;
echoKeysOff();
metronome.setTempo(tempo);
maxwait = calculateMaxWait(tempo);
playChord = playChordByRules;
cout << "Using rules for playing accompaniment" << endl;
}
//////////////////////////////
//
// 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) { }
/*-------------------- 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 function.
//
void mainloopalgorithms(void) {
// 1. check to see if we are in a new measure and update the
// metronome accordingly. If in 4/4, then the metronome will
// be guarenteed to be between 0 and 3.99999 after the following
// code is run. The update will make sure that the metronome remains
// synced exactly in time with the absolute beat. (Useful for
// polyphony, not really necessary in monophonic cases).
if (metronome.expired() >= meter) {
metronome.update(meter);
}
// 2. Determine the current beat of the meter.
// We will want to play automated chords on beats one and three.
beatfraction = metronome.getPeriodCount();
beat = (int)beatfraction + 1;
beatfraction -= beat - 1;
// 3. Process the incoming MIDI note messages (if any), keeping track
// of the last note, and whether it is currently on or off.
while (synth.getNoteCount() > 0) {
notemessage = synth.extractNote();
if (notemessage.p2() != 0) {
note = notemessage.p1();
notestates[note] = 1;
} else {
notestates[notemessage.p1()] = 0;
}
}
// 4. Determine the position in time in the current beat.
// There are two beat-parts which are called states:
// state == 0: we are at the start of the beat and may need to
// choose a new chord.
// state == 1: we are past the maximum wait time for a chord decision
// Also, check to see if the state has changed from 0 to 1 or 1 to 0.
oldstate = state;
state = beatfraction < maxwait ? 0 : 1;
stateChange = (state != oldstate);
// 5. Check to see if a chord needs to be played.
if (stateChange && state == 0) {
playMetronome(beat);
if (chordBeat(beat, meter)) {
notescan = 1;
} else {
playChord(currentnote, OFF);
}
}
if (notescan && notestates[note]) { // if note played in beat window
currentnote = note;
playChord(currentnote, ON);
notescan = 0;
} else if (notescan && state == 1) { // if too late for a new note
playChord(currentnote, ON);
notescan = 0;
}
}
/*-------------------- triggered algorithms -----------------------------*/
///////////////////////////////
//
// 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) {
switch (key) {
case ',': // slow the tempo down
case '<':
tempo *= 0.95;
metronome.setTempo(tempo);
cout << "Tempo = " << tempo << endl;
maxwait = calculateMaxWait(tempo);
break;
case '.': // speed the tempo up
case '>':
tempo *= 1.05;
metronome.setTempo(tempo);
cout << "Tempo = " << tempo << endl;
maxwait = calculateMaxWait(tempo);
break;
case '[': // decrease the beat lag time in determing a chord
lagmaxinsec -= 0.05;
if (lagmaxinsec < 0.05) {
lagmaxinsec = 0.05;
}
cout << "Chord decision time set to " << lagmaxinsec << endl;
maxwait = calculateMaxWait(tempo);
break;
case ']': // increase the beat lag time in determing a chord
lagmaxinsec += 0.05;
if (lagmaxinsec > 60.0/tempo - 0.05) {
lagmaxinsec = 60.0/tempo - 0.05;
}
cout << "Chord decision time set to " << lagmaxinsec << endl;
maxwait = calculateMaxWait(tempo);
break;
default:
cout << "Undefined keyboard command" << endl;
}
}
/*------------------ end improvization algorithms -----------------------*/
/*------------------ assisting functions for NaNa program ---------------*/
//////////////////////////////
//
// chordBeat -- returns true if we need to play a chord on this
// beat, otherwise return false. False will be used to
// turn off the current chord in the mainloopalgorithms() function.
//
int chordBeat(int beat, int meter) {
if ((beat-1) % HARMONICRHYTHM == 0) {
return 1;
} else {
return 0;
}
}
//////////////////////////////
//
// playChordByRules -- choose a chord and play the chosen chord. If
// note == 0, then turn off the current chord. Chooses a chord to
// play as accompaniment according to the input note which was played
// on the beat (or last chord beat if no note is being played now).
// This function uses a neural network to decide the output chord.
//
void playChordByRules(int note, int chordstate) {
static int rootNote = 60;
static int chordQuality = MAJOR_TRIAD;
int numNotes = 0; // the number of notes to play
static int chordNote[4] = {0}; // the notes of the chord to be calculated
int i;
// turn off the last chord notes if necessary:
for (i=0; i<4; i++) {
if (chordNote[i] != 0) {
synth.play(CH_1, chordNote[i], 0);
chordNote[i] = 0;
}
}
if (chordstate == OFF) {
return;
}
switch (note % 12) {
case 0:
case 4:
case 7:
rootNote = 48;
chordQuality = MAJOR_TRIAD;
break;
case 5:
case 9:
rootNote = 53;
chordQuality = MAJOR_TRIAD;
break;
case 2:
case 11:
rootNote = 55;
chordQuality = Mm_7TH;
break;
default: ;
// use the last chord if playing some chromatic notes
}
chordNote[0] = rootNote;
switch (chordQuality) {
case DIMINISHED_TRIAD:
chordNote[1] = chordNote[0] + 3;
chordNote[2] = chordNote[0] + 6;
numNotes = 3;
break;
case MINOR_TRIAD:
chordNote[1] = chordNote[0] + 3;
chordNote[2] = chordNote[0] + 7;
numNotes = 3;
break;
case MAJOR_TRIAD:
chordNote[1] = chordNote[0] + 4;
chordNote[2] = chordNote[0] + 7;
numNotes = 3;
break;
case HALF_DIM_7TH:
chordNote[1] = chordNote[0] + 3;
chordNote[2] = chordNote[0] + 6;
chordNote[3] = chordNote[0] + 10;
numNotes = 4;
break;
case Mm_7TH:
chordNote[1] = chordNote[0] + 4;
chordNote[2] = chordNote[0] + 7;
chordNote[3] = chordNote[0] + 10;
numNotes = 4;
break;
default:
return; // unknown chord quality
}
for (i=0; i<numNotes; i++) {
synth.play(CH_1, chordNote[i], VEL);
}
}
//////////////////////////////
//
// calculateMaxWait -- use lagmaxinsec to calculate the beat fraction
// of the wait period
//
double calculateMaxWait(double tempo) {
double output = tempo/60.0 * lagmaxinsec;
return output;
}
//////////////////////////////
//
// playMetronome --
//
void playMetronome(int beat) {
synth.play(CH_10, GM_CLAVES, 0);
if (beat == 1) {
synth.play(CH_10, GM_CLAVES, LOUD_TICK);
} else {
synth.play(CH_10, GM_CLAVES, SOFT_TICK);
}
}
// md5sum: 7fd948192cd5b3c1b5f070fd70742da4 nana1.cpp [20050403]