// // Programmer: Dan Gang // Programmer: Craig Stuart Sapp // 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