//
// 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: Mon May 10 22:29:39 PDT 1999
// Last Modified: Tue May 18 17:31:56 PDT 1999
// Filename: ...sig/doc/examples/all/nana2/nana2.cpp
// Syntax: C++; synthImprov 2.1; sigNet 1.0
//
// Description: Auto acompaniment of a simple melody.
// Initial conversion of rule-based automatic accompaniment
// to integrated version that includes automatic accompaniment
// using a Jordan Neural Network
//
#include "synthImprov.h" /* MIDI interface handling functions */
#include "sigNet.h" /* basic defintions for C++ NN's */
// program defines:
#define ON 1 /* play a chord */
#define OFF 0 /* turn off a chord */
#define CH_1 0 /* MIDI channel 1 */
#define CH_2 1 /* MIDI channel 2 */
#define CH_10 9 /* MIDI channel 10 */
#define LOUD_TICK 105 /* Metronome accented(beat 1) */
#define SOFT_TICK 85 /* 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);
void displayTrace(int phrase, int measure, int beat, int pc, int chord);
const char* name(int pitchclass);
const char* chordName(int chord);
// 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 = 1; // previous state position in beat
int state = 1; // 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
MidiMessage keymessage; // for spoofing MIDI input frm computer keyboard
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
SigTimer timer; // for timing the network activation etc.
int time1, time2; // for timing the network activation etc.
int pauseQ = 0; // for pausing the program
int autoQ = 0; // for automatic accompaniment on Yamaha PSR-520
int syncQ = 0; // for metronome synchronization with keyboard
SigTimer autoTimer; // for automatic accompaniment on Yamaha PSR-520
int displayQ = 1; // true to display input note and decision
// Neural Network variables for automatic chordal accompaniment:
ChordNet ann; // Jordan Neural Network for auto accompaniment
int phraseCount = 0; // the current phrase number
int measureCount = 0; // the current measure number
int beatCount = 0; // the current beat count (0=1, 1=3)
int clickCount = 0; // for counting out the start of the piece
/*----------------- 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. 18 May 1999: Automatic Chordal Accompaniment of simple melodies\n"
" Computer Keyboard commands: \n"
" n = use neural network to play chords\n"
" r = use rules to play chords\n"
" d = toggle display of input note and chord decisions\n"
" , or < = slow down the tempo\n"
" . or > = speed up the tempo\n"
" z = reset the metrical variables to beginning of piece\n"
" [ = decrease chord decision lag time\n"
" ] = increase chord decision lag time\n"
" p = pause/unpause the program \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();
if (tempo > 220) {
tempo = 220;
} else if (tempo < 40) {
tempo = 40;
}
metronome.setTempo(tempo);
metronome.reset();
autoTimer.setTempo(tempo*24); // 24 clock ticks per quarter note
maxwait = calculateMaxWait(tempo);
playChord = playChordByNet;
cout << "Using network for playing accompaniment" << endl;
keymessage.p0() = 0x90;
/* char netfilename[1024] = {0};
fstream netfile;
netfile.open("chord.net", ios::in | ios::nocreate);
echoKeysOn();
while (!netfile.is_open()) {
cout << "Enter filename of network connection weights";
cin >> netfilename;
}
echoKeysOff();
cout << "Reading neural network connection weights, etc..." << flush;
ann.initialize(netfile);
cout << " Done." << 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) {
if (pauseQ) {
return;
}
if (syncQ && autoQ && autoTimer.expired()) {
synth.rawsend(0xf8);
autoTimer.update();
}
// 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 (autoQ && syncQ == 0) {
syncQ = 1;
synth.rawsend(0xf8);
autoTimer.reset();
}
clickCount++;
if (clickCount == 5) {
clickCount = 10;
metronome.update(4);
}
if (clickCount > 4) {
if (chordBeat(beat, meter)) {
notescan = 1;
} else {
playChord(currentnote, OFF);
}
} else {
return;
}
}
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;
if (tempo > 220) {
tempo = 220;
} else if (tempo < 40) {
tempo = 40;
}
metronome.setTempo(tempo);
autoTimer.setTempo(tempo*24);
cout << "Tempo = " << tempo << endl;
maxwait = calculateMaxWait(tempo);
break;
case '.': // speed the tempo up
case '>':
tempo *= 1.05;
if (tempo > 220) {
tempo = 220;
} else if (tempo < 40) {
tempo = 40;
}
metronome.setTempo(tempo);
autoTimer.setTempo(tempo*24);
cout << "Tempo = " << tempo << endl;
maxwait = calculateMaxWait(tempo);
break;
case 'a': // start/stop the autoaccompaniment
autoQ = !autoQ;
if (autoQ) {
synth.rawsend(0xfa);
cout << "\nStarting auto accompaniment mode" << endl;
} else {
syncQ = 0;
synth.rawsend(0xfc);
cout << "\nStopping auto accompaniment mode" << endl;
}
break;
case 'n': // use the neural network to play the chordal accompaniment
playChord = playChordByNet;
cout << "Using network to play chordal accompaniment" << endl;
break;
case 'r': // use rules to play the chordal accompaniment
playChord = playChordByRules;
cout << "Using rules to play chordal accompaniment" << endl;
break;
case 'z': // reset the metrical variables to start of piece
cout << "resetting metrical information to beginning of piece" << endl;
phraseCount = 0;
measureCount = 0;
beatCount = 0;
clickCount = 0;
break;
case 'd': // toggle display of input note and chord decisions
displayQ = !displayQ;
if (displayQ) {
cout << "\nDisplay turned on" << endl;
} else {
cout << "\nDisplay turned off" << endl;
}
break;
case '[': // decrease the beat lag time in determing a chord
lagmaxinsec -= 0.005;
if (lagmaxinsec < 0.020) {
lagmaxinsec = 0.020;
}
cout << "Chord decision time set to "
<< lagmaxinsec*1000 << " ms"<< endl;
maxwait = calculateMaxWait(tempo);
break;
case ']': // increase the beat lag time in determing a chord
lagmaxinsec += 0.005;
if (lagmaxinsec > 60.0/tempo - 0.005) {
lagmaxinsec = 60.0/tempo - 0.005;
}
cout << "Chord decision time set to "
<< lagmaxinsec*1000 << " ms" << endl;
maxwait = calculateMaxWait(tempo);
break;
case 'p': // pause/unpause
pauseQ = !pauseQ;
if (pauseQ) {
cout << "\nProgram paused..." << endl;
} else {
cout << "\nProgram unpaused." << endl;
}
break;
case '1': // C note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 60;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '2': // D note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 62;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '3': // E note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 64;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '4': // F note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 65;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '5': // G note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 67;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '6': // A note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 69;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '7': // B note
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 71;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '8': // C octave
keymessage.p2() = 0;
synth.insert(keymessage);
keymessage.p1() = 72;
keymessage.p2() = VEL;
synth.insert(keymessage);
break;
case '9': // Rest
keymessage.p2() = 0;
synth.insert(keymessage);
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;
}
}
//////////////////////////////
//
// chordName -- display the current chord being played
//
const char* chordName(int chord) {
switch (chord) {
case 0: return "C";
case 1: return "Dm";
case 2: return "Em";
case 3: return "F";
case 4: return "G";
case 5: return "Am";
case 6: return "Bdim";
case 7: return "C7";
case 8: return "D7";
case 9: return "E7";
case 10: return "F7";
case 11: return "G7";
case 12: return "A7";
case 13: return "Bb5m7";
case 14: return "Fm";
}
return "unknown";
}
//////////////////////////////
//
// displayTrace -- display the current note and playing choice
//
void displayTrace(int phrase, int measure, int beat, int pc, int chord) {
int mphrase = phrase % 4;
int mmeasure = measure % 4;
int mbeat = (beat-1) % 2;
if (mphrase == 0 && mmeasure == 0 && mbeat == 0) {
cout << "\n\n++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
}
if (mmeasure == 0 && mbeat == 0) {
cout << endl;
}
if (mbeat == 0) {
cout << "\nbar:";
cout.width(2);
cout << measure % 16 + 1 << ", ";
}
cout << "note = " << name(pc) << ", output chord = "
<< chordName(chord) << ",\t" << flush;
}
//////////////////////////////
//
// name -- for printing the ascii note name of a given MIDI note.
//
const char* name(int pc) {
pc = pc % 12;
switch (pc) {
case 0: return "C ";
case 1: return "C#";
case 2: return "D ";
case 3: return "Ef";
case 4: return "E ";
case 5: return "F ";
case 6: return "F#";
case 7: return "G ";
case 8: return "Af";
case 9: return "A ";
case 10: return "Bf";
case 11: return "B ";
}
return "unknown";
}
//////////////////////////////
//
// playChordByNet -- 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 playChordByNet(int note, int chordstate) {
static int rootNote = 48;
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;
int winnerUnit;
// 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;
}
// 1. Map note to internal neural network representation
// 1a. Set the external input note
int pitchclass = note % 12;
ann.Melody.zero();
ann.Melody[pitchclass] = 1.0;
// 1b. Set the external meter input
// 1b1: set phrase number (first two units: 1=0a, 2=aa, 3=00, 4=aa)
// 1b2: set measure number in phrase (next two units: 1=00, 2=0a, 3=a0, 4=aa)
// 1b3: set beat number in measure (4/4 meter) (last two units: 1=a0, 3=0a)
////////
ann.Meter.zero();
switch (phraseCount % 4) {
case 0: ann.Meter[1] = 1; break; // phrase 1
case 1: ann.Meter[0] = 1; ann.Meter[1] = 1; // phrase 2
case 2: break; // phrase 3
case 3: ann.Meter[0] = 1; ann.Meter[1] = 1; // phrase 4
}
switch (measureCount % 4) {
case 0: break; // measure 1
case 1: ann.Meter[3] = 1; // measure 2
case 2: ann.Meter[2] = 1; // measure 3
case 3: ann.Meter[2] = 1; ann.Meter[3] = 1; // measure 4
}
switch (beatCount % 2) {
case 0: ann.Meter[4] = 1; // beat 1
case 1: ann.Meter[5] = 1; // beat 3
}
////////
/* switch (phraseCount % 4) {
case 0: ann.Meter.set(0, "0a"); break; // phrase 1
case 1: ann.Meter.set(0, "aa"); break; // phrase 2
case 2: ann.Meter.set(0, "00"); break; // phrase 3
case 3: ann.Meter.set(0, "aa"); break; // phrase 4
}
switch (measureCount % 4) {
case 0: ann.Meter.set(2, "00"); break; // measure 1
case 1: ann.Meter.set(2, "0a"); break; // measure 2
case 2: ann.Meter.set(2, "a0"); break; // measure 3
case 3: ann.Meter.set(2, "aa"); break; // measure 4
}
switch (beatCount % 2) {
case 0: ann.Meter.set(4, "a0"); break; // beat 1
case 1: ann.Meter.set(4, "0a"); break; // beat 3
}
*/
// increment the meter tracking variables:
beatCount++;
if (beatCount % 2 == 0) {
measureCount++;
if (measureCount % 4 == 0) {
phraseCount++;
}
}
// 2. Activate the Neural Network
ann.activate();
// 3. Map from Net Output to chord. Get the rootNote from the Network.
winnerUnit = 0;
double max = -2;
for (int z=0; z<ann.Output.getSize(); z++) {
if (max < ann.Output[z]) {
max = ann.Output[z];
winnerUnit = z;
}
}
if (displayQ) {
displayTrace(phraseCount, measureCount, beatCount,
pitchclass, winnerUnit);
}
// 4. Now that the rootNote has been determined, choose the correct
// chord quality and play.
switch (winnerUnit) {
case 0: rootNote = 0; chordQuality = MAJOR_TRIAD; break;
case 1: rootNote = 2; chordQuality = MINOR_TRIAD; break;
case 2: rootNote = 4; chordQuality = MINOR_TRIAD; break;
case 3: rootNote = 5; chordQuality = MAJOR_TRIAD; break;
case 4: rootNote = 7; chordQuality = MAJOR_TRIAD; break;
case 5: rootNote = 9; chordQuality = MINOR_TRIAD; break;
case 6: rootNote = 11; chordQuality = DIMINISHED_TRIAD; break;
case 7: rootNote = 0; chordQuality = Mm_7TH; break;
case 8: rootNote = 2; chordQuality = Mm_7TH; break;
case 9: rootNote = 4; chordQuality = Mm_7TH ; break;
case 10: rootNote = 5; chordQuality = Mm_7TH ; break;
case 11: rootNote = 7; chordQuality = Mm_7TH ; break;
case 12: rootNote = 9; chordQuality = Mm_7TH ; break;
case 13: rootNote = 11; chordQuality = Mm_7TH ; break;
case 14: rootNote = 5; chordQuality = HALF_DIM_7TH; break;
default: ;
}
chordNote[0] = rootNote + 48;
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);
}
}
//////////////////////////////
//
// 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: 24ebd1688a49e4b8fc5b584c5c969e4c nana2.cpp [20050403]