//
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Programmer: Seny Lee <senylee@ccrma.stanford.edu>
// Creation Date: Mon May 17 10:25:04 PDT 1999
// Last Modified: Tue May 25 20:46:16 PDT 1999
// Last Modified: Fri Jun 25 16:34:54 PDT 1999
// Filename: .../improv/examples/synthImprov/seny/seny.cpp
// Syntax: C++; batonSynthImprov 2.1
//
#include "batonSynthImprov.h"
#include <ctype.h>
#ifndef OLDCPP
#include <iostream>
using namespace std;
#else
#include <iostream.h>
#endif
#ifdef VISUAL
double drand48(void) {
return (double)rand()/RAND_MAX;
}
#endif
#define BOUND1 50 /* boundary note of low section of keyboard */
#define BOUND2 79 /* boundary note of high section of keybd */
#define VARIATION 3.0 /* std devs to traverse w/drum controls */
#define MAXVOICES 12 /* maximum drum voices at one time */
#define MINVEL 20 /* minimum attack velocity output */
#define MAXVEL 90 /* maximum attack velocity output */
/*----------------- beginning of improvization algorithms ---------------*/
// Global variables:
int activeQ = 1; // true if outputing notes false otherwise
int controlDisplayQ = 1; // for displaying baton control variables
// variables for keeping track of performer's notes with which baton will
// use to generate notes on the piano in an algorithmic fashion.
int lastPerformerTime = 0; // time of last performer note on/off
int performerNotes[127] = {0}; // all of the performer's notes being played
int performerPC[12] = {0}; // pitch class of the performer
int performerPCHistory[12] = {0}; // performer pitch class history
int keyDuration = 5000; // how long to remember performers notes
CircularBuffer<char> keys; // store keys for memory of previous notes
CircularBuffer<long> keytimes; // note times for keys buffer
// the variables for stick 1 x-axis density control
double density = 0; // 0=thick, 1=thin
// keep track of performer's notes for stick 1 y-axis register control
int performerRegion[3] = {0}; // location that the performer is playing
double pregister = 0; // 0=low, 1=high
// the variables for stick 1 z-axis controlling consonance and dissonance
double consonance = 0.0; // 0 = consonant, 1 = dissonant
// cintervals are a list of the intervals in an octave starting with
// the most consonant ones and then going to the most dissonant ones.
int cintervals[12] = {0,7,5,4,9,8,3,10,2,11,1,6};
// loudness variables for baton stick 2 x-axis control of velocity volumes
double avgVol = 0; // average volume
double avgVolRange = 0; // standard deviation of the durations
CircularBuffer<double> volumes; // duration of notes being held by performer
CircularBuffer<double> voltimes; // duration of notes being held by performer
int volDuration = 5000; // how long to keep track of volumes
double velocity = 0; // 0=similar, 1=inverted
// duration variables for baton stick 2 y-axis control of articulations
double avgDur = 0; // average duration
double avgDurRange = 0; // standard deviation of the durations
CircularBuffer<long> durations; // duration of notes being held by performer
CircularBuffer<long> durtimes; // duration of notes being held by performer
int durDuration = 5000; // how long to keep track of durations
double articulation = 0; // 0=staccato, 1=tenuto, 0.5=normal
// the variables for stick 2 z-axis controlling rhythm
double rhythmType = 0; // 0=constant, 1=random wide variation
int zmax = 120; // for z-axis normalizing
int zmin = 25; // for z-axis normalizing
// baton and buffer checking variables
SigTimer batonTimer; // time to get new state of baton
SigTimer offTimer; // time to check buffer for forgetting
SigTimer controlDisplayTimer; // time to check buffer for forgetting
//////////////////////////////
//
// Radio Drum performance variables
//
Voice computer;
MidiMessage computerMessage;
Voice voice[MAXVOICES];
int voiceState[MAXVOICES] = {0};
int voiceOnTime[MAXVOICES] = {0};
int voiceOffTime[MAXVOICES] = {0};
SigTimer voiceTimer;
int voicePeriod = 100;
int displayOutput = 0; // for displaying output notes of baton
// function declarations:
void checkBuffers(void);
void checkOffNotes(void);
int countActiveVoices(void);
void displayVariables(void);
void processBaton(void);
void processKeyboard(void);
void updateDuration(void);
void updatePerformRegions(void);
void updateVolume(void);
int midiLimit(int aNumber);
//////////////////////////////
//
// Radio Drum performance variables
//
void generateVoices(void);
/*--------------------- 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 <<
"Seny 1 -- project for \n"
" Keyboard commands: \n"
" n = display piano's history input pitch set\n"
" m = display piano's currently on pitch set\n"
" d = display baton's output notes\n"
" c = display baton's control variables\n"
" r = display piano's register tracking variables\n"
" ' ' = activate/deactivate program\n"
" v = display active voice count of baton's notes\n"
" z = forget the piano's current key history\n"
" [ = shorten note memory by 1 second\n"
" ] = length note memory by 1 second\n"
<< endl;
}
//////////////////////////////
//
// initialization -- this function is called by the improv
// intervace once at the start of the program. Put items
// here which need to be initialized at the beginning of
// the program.
//
void initialization(void) {
batonTimer.setPeriod(50); // time to get new state of baton
offTimer.setPeriod(200); // time to check buffer for forgetting
controlDisplayTimer.setPeriod(200); // time to check buffer for forgetting
// set the voice channels all to be 0 for the disklavier and so
// the channels do not have to be specified when playing the note.
for (int i=0; i<MAXVOICES; i++) {
voice[i].setChannel(0);
}
computer.setChannel(0);
computerMessage.time = t_time;
computerMessage.p0() = 0x90;
computerMessage.p1() = 0;
computerMessage.p2() = 0;
keys.setSize(1000); // store keys for memory of previous notes
keytimes.setSize(1000); // note times for keys buffer
volumes.setSize(1000); // duration of notes being held by performer
voltimes.setSize(1000); // duration of notes being held by performer
durations.setSize(1000); // duration of notes being held by performer
durtimes.setSize(1000); // duration of notes being held by performer
}
//////////////////////////////
//
// 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 functions.
//
void mainloopalgorithms(void) {
if (synth.getNoteCount() > 0) {
processKeyboard();
}
if (offTimer.expired()) {
checkBuffers();
checkOffNotes();
offTimer.reset();
}
if (batonTimer.expired()) {
processBaton();
batonTimer.reset();
}
voicePeriod = (int)(avgDur - avgDurRange);
if (voicePeriod <= 50) {
voicePeriod = 50;
}
voiceTimer.setPeriod(voicePeriod);
if (voiceTimer.expired()) {
generateVoices();
voiceTimer.reset();
}
if (controlDisplayQ && controlDisplayTimer.expired()) {
displayVariables();
controlDisplayTimer.reset();
}
}
/*-------------------- 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) {
if (isdigit(key)) {
int mkey = 5 * 12 + key - '0';
int mvel = (int)(drand48() * 40 + 40);
//turn off old note from computer's point of view
computerMessage.time = t_time;
computerMessage.p2() = 0;
synth.insert(computerMessage);
cout << "played note: " << mkey << " with velocity: " << mvel << endl;
computer.play(mkey, mvel);
computerMessage.time = t_time;
computerMessage.p0() = 0x90; // midi note-on command, channel 1
computerMessage.p1() = mkey;
computerMessage.p2() = mvel;
synth.insert(computerMessage);
cout << "played note: " << mkey << " with velocity: " << mvel << endl;
return;
}
switch (key) {
case ' ': // turn output notes on/off
activeQ = !activeQ;
if (activeQ) {
cout << "Program Activated" << endl;
} else {
cout << "Program Deactivated" << endl;
}
break;
case 'c': // toggle baton control display
controlDisplayQ = !controlDisplayQ;
if (controlDisplayQ == 0) {
cout << endl;
}
break;
case 'd': // display baton's output notes
displayOutput = !displayOutput;
if (displayOutput) {
cout << "Baton output notes display turned ON" << endl;
} else {
cout << "Baton output notes display turned OFF" << endl;
}
break;
case 'n': // display performerPCHistory pitches
{
cout << "Piano notes: ";
for (int i=0; i<11; i++) {
cout << performerPCHistory[i] << ", ";
}
cout << performerPCHistory[11] << endl;
}
break;
case 'm': // display performerPC pitches
{
cout << "Notes on: ";
for (int i=0; i<11; i++) {
cout << performerPC[i] << ", ";
}
cout << performerPC[11] << endl;
}
break;
case 'r': // display the register information
{
cout << "Register: "
<< performerRegion[0]
<< performerRegion[1]
<< performerRegion[2]
<< endl;
}
break;
case 'v': // display the active voice count for baton
{
cout << endl << "Active voices = " << countActiveVoices();
cout << ": ";
for (int z=0; z<MAXVOICES; z++) {
if (voiceState[z]) {
cout << " " << (int)voice[z].getKey();
} else {
cout << " 0";
}
}
cout << endl;
}
break;
case 'z': // forget the current piano's key history
{
for (int z=0; z<12; z++) {
performerPCHistory[z] = 0;
}
keys.reset();
keytimes.reset();
}
break;
case '[': // decrease the note memory time
keyDuration -= 1000;
if (keyDuration < 1000) {
keyDuration = 1000;
}
cout << "Note memory time set to : " << keyDuration/1000
<< " seconds" << endl;
break;
case ']': // increase the note memory time
keyDuration += 1000;
if (keyDuration > 60000) {
keyDuration = 60000;
}
cout << "Note memory time set to : " << keyDuration/1000
<< " seconds" << endl;
break;
case '-': // turn off computer keyboard note
// turn off old note from computer's point of view
computerMessage.time = t_time;
computerMessage.p2() = 0;
synth.insert(computerMessage);
// turn off old note from synthesizer's point of view
computer.off();
break;
}
}
void stick1trig(void) {
generateVoices();
}
void stick2trig(void) {
generateVoices();
}
void b14plustrig(void) { }
void b15plustrig(void) { }
void b14minusuptrig(void) { }
void b14minusdowntrig(void) { }
void b15minusuptrig(void) { }
void b15minusdowntrig(void) { }
/*------------------ begining of assistant functions --------------------*/
//////////////////////////////
//
// checkBuffers -- look at the history buffers and determine if any
// of the buffers needs to be emptied of any contents.
//
void checkBuffers(void) {
// look at the volume buffers for old volumes:
while ( (voltimes.getCount() > 0) && (voltimes[voltimes.getCount()-1] <
t_time - volDuration) ) {
volumes.extract();
voltimes.extract();
}
// look at the duration buffers for old durations:
while ( (durtimes.getCount() > 0) && (durtimes[durtimes.getCount()-1] <
t_time - durDuration) ) {
durations.extract();
durtimes.extract();
}
// look at the key buffers for old keys:
int oldkey;
while ( (keytimes.getCount() > 0) && (keytimes[keytimes.getCount()-1] <
t_time - keyDuration) ) {
oldkey = keys.extract();
keytimes.extract();
performerPCHistory[oldkey%12]--;
if (performerPCHistory[oldkey%12] < 0) {
performerPCHistory[oldkey%12] = 0;
}
}
}
//////////////////////////////
//
// checkOffNotes -- check the voices array to see if any of the notes
// need to be turned off.
//
void checkOffNotes(void) {
for (int i=0; i<MAXVOICES; i++) {
if (voiceState[i] && voiceOffTime[i] < t_time) {
voice[i].off();
voiceState[i] = 0;
}
}
}
//////////////////////////////
//
// countActiveVoices -- return the number of baton voices that
// are currently on.
//
int countActiveVoices(void) {
int output = 0;
for (int i=0; i<MAXVOICES; i++) {
if (voiceState[i]) {
output++;
}
}
return output;
}
//////////////////////////////
//
// displayVariables -- display the baton's control variables in
// real-time on the computer monitor.
//
void displayVariables(void) {
// move cursor to the beginning of the line
cout << "\r";
// erase previous line
cout <<
" ";
cout << "\r";
// print out each axis variable:
cout << "den:";
cout.width(6);
cout << (int)(MAXVOICES*density);
cout << " reg:";
cout.width(6);
cout << (int)(3*pregister);
cout << " con:";
cout.width(6);
cout << cintervals[(int)(consonance*12)];
cout << " vel:";
cout.width(6);
cout << ((int)(velocity*100))/100.0;
cout << " art:";
cout.width(6);
cout << ((int)(articulation*100))/100.0;
cout << " rhy:";
cout.width(6);
cout << ((int)(rhythmType*100))/100.0;
cout << flush;
}
//////////////////////////////
//
// generateVoices --
//
void generateVoices(void) {
int voiceson = 0;
int i;
// count how many voices are on
for (i=0; i<MAXVOICES; i++) {
if (voiceState[i]) {
voiceson++;
}
}
// determine how many voices should be on.
int voicesnow = (int)(MAXVOICES * density);
if (voicesnow == voiceson) {
return;
}
if (voicesnow < voiceson) {
// need to erase voices
int eraseindex;
for (i=0; i<voiceson-voicesnow; i++) {
eraseindex = rand()%MAXVOICES;
if (voiceState[eraseindex] == 1) {
voiceState[eraseindex] = 0;
voice[eraseindex].off();
} else {
int errorprotect = 0;
while (voiceState[eraseindex] == 0 && errorprotect < MAXVOICES*2) {
errorprotect++;
eraseindex = (eraseindex + 1) % MAXVOICES;
if (voiceState[eraseindex] == 1) {
voiceState[eraseindex] = 0;
voice[eraseindex].off();
}
}
}
}
} else {
// need to add voices
int totalnotes = 0;
for (i=0; i<12; i++) {
totalnotes += performerPCHistory[i];
}
if (totalnotes == 0) {
return;
}
double noteDist[12];
for (i=0; i<12; i++) {
noteDist[i] = performerPCHistory[i]/(double)totalnotes;
}
double randomval;
int j;
for (i=0; i< voicesnow-voiceson; i++) {
randomval = drand48(); // random number between 0.0 and 1.0
double sum = 0.0;
// choose a pitch class from the piano performer
// you are more likely to choose a pitch which the pianist
// has play alot.
for (j=0; j<12; j++) {
sum += noteDist[j];
if (sum > randomval) {
break;
}
}
int choosenpc = j;
// now that a pitch class from the pianist has been choosen
// choose the output pitch class from the radio drum's
// consonance dissonance control
int outputpc = (choosenpc + cintervals[(int)(consonance*12)]) % 12;
// now that we have the output pitch class, assign a register
// to it.
// choose a register, then look to find if there is a free register
// associated with the chosen register
int outputregister = (int)(pregister * 3);
if (performerRegion[outputregister] != 0) {
if (pregister < 0.5) {
outputregister = (outputregister+1) % 3;
} else {
outputregister = (outputregister-1+30) % 3;
}
}
if (performerRegion[outputregister] != 0) {
if (pregister < 0.5) {
outputregister = (outputregister+1) % 3;
} else {
outputregister = (outputregister-1+30) % 3;
}
}
if (performerRegion[outputregister] != 0) {
if (pregister < 0.5) {
outputregister = (outputregister+1) % 3;
} else {
outputregister = (outputregister-1+30) % 3;
}
}
if (performerRegion[outputregister] != 0) {
continue;
}
int octave = rand()%2 + outputregister * 2 + 3;
int outputpitch = outputpc + octave * 12;
int initialvolume = (int)(avgVol - VARIATION * avgVolRange +
VARIATION * 2 * avgVolRange * velocity);
if (initialvolume < MINVEL) {
initialvolume = MINVEL;
} else if (initialvolume > MAXVEL) {
initialvolume = MAXVEL;
}
// int oppositevol = 127 - initialvolume;
// int outputvolume = (int)((oppositevol - initialvolume) * velocity +
// initialvolume);
int outputvolume = initialvolume;
if (outputvolume < MINVEL) {
outputvolume = MINVEL;
} else if (outputvolume > MAXVEL) {
outputvolume = MAXVEL;
}
// we now know the velocity and pitch of the output note.
// now we need to calculate the duration and the voice slot
// to place the note in.
double outduration = avgDur - VARIATION * avgDurRange +
VARIATION * 2 * drand48() * avgDurRange;
// adjust duration of notes according to baton control
outduration += VARIATION * (avgDurRange * (articulation *
2 - 1));
outduration *= 3.0 * rhythmType;
// prevent very short notes, nothing less than 50 ms in duration
if (outduration < 50) {
outduration = 50.0;
}
if (avgDur < 50) {
outduration = 150.0;
}
int outputduration = (int)outduration;
// choose a voice slot to store the note.
int assignment = (int)(drand48() * MAXVOICES);
int errorprotect = 0;
while (voiceState[assignment] != 0 && errorprotect < MAXVOICES*2) {
errorprotect++;
assignment = (assignment + 1) % MAXVOICES;
}
if (voiceState[assignment] == 0 && activeQ) {
voiceState[assignment] = 1;
voiceOnTime[assignment] = t_time;
voiceOffTime[assignment] = t_time + outputduration;
voice[assignment].play(outputpitch, outputvolume);
if (displayOutput) {
cout << "Note: " << outputpitch << "\tvolume = "
<< outputvolume << "\tduration = " << outputduration
<< endl;
}
}
}
}
}
//////////////////////////////
//
// midiLimit -- guarentee that the given number is in the range
// from 0 to 127.
//
int midiLimit(int aNumber) {
if (aNumber < 0) {
return 0;
} else if (aNumber > 127) {
return 127;
} else {
return aNumber;
}
}
//////////////////////////////
//
// processBaton -- update the program variables related to the radio
// batons.
//
void processBaton(void) {
// update the variable for keeping track of texture density:
// density 0 = thick, density 1 = thin
density = baton.x1p/127.0; // baton.x1p == xt1
// update the variable for keeping track of articulation
// pregister 0 = low, pregister 1 = high
pregister = baton.y1p/127.0; // baton.y1p == yt1
// update the variable for keeping track of the consonance:
// consonance 0 = consonant, consonance 1 = dissonant
// zt1 = baton.z1p
if (zmax - zmin == 0) {
consonance = 0.5;
} else {
consonance = 1.0 - (baton.z1p - zmin) * 1.0 / (zmax - zmin);
if (consonance < 0.0) {
consonance = 0.0;
}
}
// update the variable for keeping track of velocity
// velocity 0 = similar, velocity 1 = inverted
velocity = baton.x2p/127.0; // baton.x2p == xt2
// update the variable for keeping track of articulation
// articulation 0 = staccato, articulation 1 = tenuto, 0.5 = normal
articulation = baton.y2p/127.0; // baton.y2p == yt2
// update the variable for keeping track of rhythm type:
// rhythmType 0 = even, rhythmType 1 = random
// zt2 = baton.z2p
if (zmax - zmin == 0) {
rhythmType = 0.5;
} else {
rhythmType = 1.0 - (baton.z2p - zmin) * 1.0 / (zmax - zmin);
}
if (rhythmType < 0.0) {
rhythmType = 0.0;
}
}
//////////////////////////////
//
// processKeyboard -- get notes from the piano and store them as needed
// the the radio baton monitoring variables.
//
void processKeyboard(void) {
MidiMessage message;
int key;
int vel;
int command;
while (synth.getNoteCount() > 0) {
message = synth.extractNote();
command = message.p0();
key = message.p1();
vel = message.p2();
if (vel == 0 || (command & 0xf0 == 0x80)) {
// note-off command section
long duration = t_time - performerNotes[key];
durations.insert(duration);
durtimes.insert(t_time);
performerNotes[key] = 0;
performerPC[key%12]--;
if (performerPC[key%12] < 0) {
performerPC[key%12] = 0;
}
} else { // note-on command
performerNotes[key] = t_time;
performerPC[key%12]++;
performerPCHistory[key%12]++;
keys.insert(key);
keytimes.insert(t_time);
volumes.insert(vel);
voltimes.insert(t_time);
} // end of the note-on command section
} // end of the while loop for processing notes from the performer
// update the time that the performer last play a note on/off:
lastPerformerTime = t_time;
updatePerformRegions();
updateDuration();
updateVolume();
}
//////////////////////////////
//
// updateDuration -- recalculate the average duration and its possible
// range. Calculate average duration and the standard deviation
// of durations.
//
void updatePerformRegions(void) {
// determine which region of the piano the performer is
// playing on:
performerRegion[0] = 0;
performerRegion[1] = 0;
performerRegion[2] = 0;
for (int i=0; i<127; i++) {
if (performerNotes[i] > 0) {
if (i < BOUND1) {
performerRegion[0] = 1;
} else if (i < BOUND2) {
performerRegion[1] = 1;
} else {
performerRegion[2] = 1;
}
}
}
}
//////////////////////////////
//
// updateDuration -- recalculate the average duration and its possible
// range. Calculate average duration and the standard deviation
// of durations.
//
void updateDuration(void) {
int i;
double dursum = 0.0;
for (i=0; i<durations.getCount(); i++) {
dursum += durations[i];
}
if (durations.getCount() != 0) {
avgDur = dursum / durations.getCount();
} else {
avgDur = 0.0;
}
// get the variance
dursum = 0.0;
for (i=0; i<durations.getCount(); i++) {
dursum += (avgDur - durations[i]) * (avgDur - durations[i]);
}
avgDurRange = sqrt(dursum);
}
//////////////////////////////
//
// updateVolume -- recalculate the average volume and it possible
// range. Calculate average volume and the standard deviation
// of volumes
//
void updateVolume(void) {
int i;
double volsum = 0.0;
for (i=0; i<volumes.getCount(); i++) {
volsum += volumes[i];
}
if (volumes.getCount() != 0) {
avgVol = volsum / volumes.getCount();
} else {
avgVol = 0.0;
}
// get the variance
volsum = 0.0;
for (i=0; i<volumes.getCount(); i++) {
volsum += (avgVol - volumes[i]) * (avgVol - volumes[i]);
}
avgVolRange = sqrt(volsum);
}
/*------------------ end improvization algorithms -----------------------*/
// md5sum: c8bbb162e65d0138d1619ab7891fc58a seny.cpp [20050403]