Michael Feldman
CCRMA – Music 220C Project
Interactive Musical Games
Week 1 (note: weeks are relative i am not actually sure which week we were in at any given time.)—
Brainstorming Ideas for my project
General ideas – possibly working with interactive music making? Real-time composition? Real time performance? Game?
Basically decided to work on a game.
Mark Applebaum had already done a version of the piano composition I wanted to do and there are other problems with a serious 2-person musical instrument.
Week 2 –-
Show 250A project –
Decide what I want to work on.
Where I am starting from:
Offence/defense
Not really creating music, just playing “samples” (not really samples, but it’s the same idea)
Requires listening room
Goals:
Simultaneous offense/defense game
Does not use listening room.
Dynamic or interesting interaction between the players.
If possible make it real time – not turn by turn play (but not necessary)
Week 3 –-
Games ideas –
2 ideas:
(1) turn by turn
like connect 4, except with 4 pitches instead of 4 spaces
4x4 grid of 16 buttons
the advantage of sound is that there can be a shift button, where the lights move and the pitch remains or the pitch moves and the light remains. Becomes a memory game as well at this point and more than just visual. Strategic – can set up a situation where a shift wins the game for you.
(2) Dynamic control
like a balance game where players are working to achieve a goal
but competitive: one tries to create dissonance, the other consonance.
A king-of-the-hill style game where if within realm of consonance the consonance person gets a point.
Buttons to control whether the slider is controlling pitch, rhythm or chords, also button to shift whether you are controlling consonance or dissonance.
Week 4 –-
Chris would rather I move forward with the dynamic rather than turn-based game so I started to work on possible rules for a dynamic game.
Sliders need to push and pull relative to starting point rather than in absolute space
Players switch off between consonance and dissonance.
One button to select intervals of a chord – slider shifts
One button to select primary pitch – slider shifts
One button to control rhythmic stability – slider balances (maybe the only one in absolute rather than relative space for the slider)
LED shows who is winning or whether it’s in limbo. (light on player 1 side is on if more consonant, light on player 2 side is on if more dissonant. If a players light is on for a total of 10 seconds he/she wins a point.
**Possible problem with consonance and dissonance being very close together.
Week 5 –-
Pd Patch work
I started by laying out the controls set-up
Two sliders each with 4 toggle switches. Then as I progressed to writing the code to make the sliders relative and to slide the chord tones apart, I created individual, self-contained blocks to simplify.
Week 6 –-
AVR board programming beginnings
In changing the nature of the game I wish to design, I started with programming the AVR board.
I set it up such that Pd can read the a2dvalues coming out of the board and so that the counter increments when the slider is within a certain range of the variable I have called “marble”
I worked on creating the basic physics for the game – getting the buttons to work and wiring up the board
I actually needed more inputs that the board had, so i multiplexed the LEDs. That made them flicker, so i multiplexed the buttons instead. In this way i created the basic functionality for the board (but with regular sliders (not haptic sliders)
Week 7 --
Construction
This week i constructed the hardware. Soldering all the buttons i needed to the wires and hooking them up.
I built the casing in the product realization lab and learned how to use the lasercad machine that i needed to use to cut the square holes in the acrylic for the sliders and square buttons.
I also mounted the haptic sliders and the motor board.
Week 9 --
Working with Michael Guerevich and Bill Verplank to get teh haptics on the sliders functioning
I wired them up, learned how to deal with the driver motors, (and how to fix it when all the wires fell out as they constantly did.
Week 10 --
Trying to compile the program.
I tried to compile teh haptic code, regular C code and pd code to finally construct the project.
I burned out one microprossesor and apparently taught some of teh systems staff about wonderful and new problems that the CCRMA computers could have.
I also created the basics for a second game – the balance game in which players have to identify different pitch spaces, by selecting them with the slider and button.
I have copied and pasted the c code below.
// compiler includes
#include <avr/io.h>
#include <avr/pgmspace.h>
// avrlib includes
#include "timer.h"
#include "global.h"
#include "uart.h"
#include "ccrma/osc.h"
#include "rprintf.h"
#include "spyglass.h"
#include "a2d.h"
#include "i2c.h"
#include "lcd.h"
//define motor things
#define DEBOUNCE_THRESHOLD 10
#define PWMB PD4
#define PWMA PD5
#define DIRA PD6
#define DIRB PD7
u08 Contrast=20; // spyglass lcd contrast
u16 d0, d1; // analog inputs 0-1023
s16 p0, p1, pos, f0, f1; // position with -512 to +511 (0 in middle)
u08 pwm;
s32 dutymax;
u16 duty; // output to pwm
u32 runs;
s16 forceout; // to motor
s32 W = 100; // width
s32 D = 60; // depth
//global variables for game
u08 buttonconf[8];
u08 startgame;
u08 buttonon[8];
u08 counter;
//function prototypes
u08 checkButton(u08 whichButton);
void setLED(u08 whichLED, u08 on);
void PERMsetLED(u08 whichLED, u08 on);
int main(void)
{
s32 a2dvalues[2]; // variable to store a/d value
u08 i;
u08 tmp;
s32 marble[2];
s32 bump[4];
u08 points[2];
u08 testbutton[8];
u08 bpoint[4];
s32 spoint[4];
u08 testpattern;
u08 p0yay[4];
u08 p1yay[4];
u08 ptotal[2];
//oscMessage stuff
uartInit();
oscInit();
timerInit();
uartSetBaudRate(115200);
//LCD Stuff
i2cInit();
spyglassInit();
spyglassLcdInit();
rprintfInit(spyglassLcdWriteChar);
lcdClear();
lcdHome();
//Initialize timer
timerInit();
// set PortD pins 6 and 7 to output
sbi(DDRD, PWMA);
sbi(DDRD, DIRA);
sbi(DDRD, PWMB);
sbi(DDRD, DIRB);
// set PWM resolution to low 8,9 or 10
pwm = 8;
dutymax = (1<<pwm) - 1;
timer1PWMInit(pwm);
timer1PWMAOn();
timer1PWMBOn();
// connect AVR to MotorDriver
#define PWMA PD5 // PortD pin 6 (blue) -> PortX pin 6
#define DIRA PD6 // PortD pin 7 (purple) -> PortX pin 7
//set ports A,B,C to a2d and buttons
a2dInit();
outb(DDRA, 0x00); // set low 3 pins to output for mux addressing and
// pins 4-7 as inputs for a2d conversion
outb(PORTA, 0x00); // set pull-ups to off
outb(DDRB, 0xE7); // set port B pins to output (For MUX ctl)
tmp=DDRC&0xFC;
outb(DDRC,0xFC|tmp); // set PORTC masking 0,1,7
timer0SetPrescaler(TIMER_CLK_DIV8);
timer1SetPrescaler(TIMER_CLK_DIV1);
lcdHome();
rprintf("Michael's Interface");
// allow for user to lift buttons after reset
lcdGotoXY(0,2);
rprintf("***wait for it...***");
timerPause(3000);
lcdGotoXY(0,2);
rprintf("Press1:Octave Slide ");
lcdGotoXY(0,3);
rprintf("Press2:Balancing Act");
//set startgame initial value
startgame = 0;
marble[0] = 0;
marble[1] = 0;
//wait for user to start program
while (startgame == 0){
counter++;
for (i=0;i<8; i++){
tmp = PORTB&0xF8; //Mask non-output for PORTB
outb(PORTB,i|tmp); //select MUX channel PORTB
setLED(i, checkButton(3)); //checkButton for Mux
buttonon[i] = checkButton(3);
if (buttonon[i] != testbutton[i]){
oscSendMessageIntInt(PSTR("/button"),i,buttonon[i]);
testbutton[i] = buttonon[i];
}
}
// hack for unknown button problem
if(buttonon[7]) {setLED(7,1);}
else {setLED(7,0);}
if (buttonon[0]){startgame = 1;}
if (buttonon[1]){startgame = 2;}
if (buttonon[4]){startgame = 1;}
if (buttonon[5]){startgame = 2;}
}
oscSendMessageInt(PSTR("/opening"),startgame);
timerPause(500);
srand(counter);
lcdGotoXY(0,1);
rprintf(" Ready? ");
timerPause(200);
lcdGotoXY(0,1);
rprintf(" ");
lcdGotoXY(0,2);
rprintf(" ");
lcdGotoXY(0,3);
rprintf(" ");
timerPause(3000);
while (1) { //press all 8 buttons to reset
if (!(buttonon[0] & buttonon[1] & buttonon[2] & buttonon[3] & buttonon[4] & buttonon[5] & buttonon[6] & buttonon[7])){
for (i=0; i<8; i++) {
tmp = PORTB&0xF8; //Mask non-output for PORTB
outb(PORTB,i|tmp); //select MUX channel PORTB
setLED(i, checkButton(3)); //checkButton for Mux
buttonon[i] = checkButton(3);
if (buttonon[i] != testbutton[i]){
if(testbutton[i] = 1){
oscSendMessageIntInt(PSTR("/button"),i,buttonon[i]);
}
testbutton[i] = buttonon[i];
}
}
// hack for unknown button problem
if(buttonon[7]) {setLED(7,1);}
else {setLED(7,0);}
if(startgame == 1){
if(points[0]+points[1]<2){
lcdGotoXY(6,1);
rprintf("SliderGame");
if(buttonon[0]){
if(p0>(marble[0]-125)){
if(p0<marble[0]){
marble[0] = marble[0] - p0*0.5806 + 75.5806; //equation of the line where silder can
} //be within 125 and shoots it a max of 75
}
}
if(buttonon[1]){
if(p0>(marble[1]-125)){
if(p0<marble[1]){
marble[1] = marble[1] - p0*0.5806 + 75.5806; //equation of the line where silder can
} //be within 125 and shoots it a max of 75
}
}
if(buttonon[4]){
if(p0>(marble[0]+125)){
if(p0<marble[0]){
marble[0] = marble[0] + p1*0.5806 + 75.5806; //equation of the line where silder can
} //be within 125 and shoots it a max of 75
}
}
if(buttonon[5]){
if(p0>(marble[0]+125)){
if(p0<marble[0]){
marble[0] = marble[0] + p1*0.5806 + 75.5806; //equation of the line where silder can
} //be within 125 and shoots it a max of 75
}
}
oscSendMessageIntInt(PSTR("/marble"),marble[0],marble[1]);
lcdGotoXY(0,1);
rprintfNum(10, 4, TRUE, ' ', marble[0]);
//scoring points
if(marble[0]>250){ //****change this back to 500 once working
marble[0] = 1000;
PERMsetLED(3,1);
points[0] = points[0] + 1;
}
if(marble[0]<-500){
marble[0] = -1000;
PERMsetLED(7,1);
points[1] = points[1] + 1;
}
if(marble[1]>500){
marble[1] = 1000;
PERMsetLED(4,1);
points[0] = points[0] + 1;
}
if(marble[1]<-500){
marble[1] = -1000;
PERMsetLED(8,1);
points[1] = points[1] + 1;
}
}
else{
startgame =0;
oscSendMessageInt(PSTR("/opening"),startgame);
lcdGotoXY(0,0);
rprintf(" --GAMEOVER-- ");
lcdGotoXY(0,3);
rprintf("all buttons to reset");
for (i=0; i<8; i++) {
tmp = PORTB&0xF8; //Mask non-output for PORTB
outb(PORTB,i|tmp); //select MUX channel PORTB
setLED(i, buttonon[i]); //checkButton for Mux
buttonon[i] = checkButton(3);
if (buttonon[i] != testbutton[i]){
oscSendMessageIntInt(PSTR("/button"),i,buttonon[i]);
testbutton[i] = buttonon[i];
}
}
if (points[0]>points[1]){
lcdGotoXY(0,1);
rprintf(" Player 1 Wins! ");
}
if (points[0]>points[1]){
lcdGotoXY(0,1);
rprintf(" Player 2 Wins! ");
}
else{
lcdGotoXY(0,1);
rprintf(" It's a Tie Game ");
}
if ((buttonon[0] & buttonon[1] & buttonon[2] & buttonon[3] & buttonon[4] & buttonon[5] & buttonon[6] & buttonon[7])){
points[0]=0;
points[1]=0;
main();
}
//**********************************************************************************
//second game - the balance game
if(startgame == 2){
if( (p0 != 3) & (p1 != 3)){
lcdGotoXY(6,1);
rprintf("BalanceGame");
if(testpattern != 1){
for(i=0;i<3;i++){
bpoint[i] = (rand() % 25) + 50;
spoint[i] = (bpoint[i]-62.5)/0.025;
oscSendMessageIntInt(PSTR("/bpoint"),i,bpoint[i]);
}
testpattern = 1;
}
if(buttonon[3]){
for(i=0;i<3;i++){
oscSendMessageIntInt(PSTR("/bpoint"),i,bpoint[i]);
}
}
if(buttonon[7]){
for(i=0;i<3;i++){
oscSendMessageIntInt(PSTR("/bpoint"),i,bpoint[i]);
}
}
for(i=0;i<3;i++){
if(buttonon[i]){
if(p1<(spoint[i]+100)){
if(p1>(spoint[i]-100)){
PERMsetLED(i,1);
p0yay[i] = 1;
}
}
}
}
for(i=4;i<7;i++){
if(buttonon[i]){
if(p1<(spoint[i]+100)){
if(p1>(spoint[i]-100)){
PERMsetLED(i,1);
p1yay[i] = 1;
}
}
}
}
} // end of p0 !=3.... loop
else{
startgame = 0;
oscSendMessageInt(PSTR("/opening"),startgame);
lcdGotoXY(0,0);
rprintf(" --GAMEOVER-- ");
lcdGotoXY(0,3);
rprintf("all buttons to reset");
for (i=0; i<8; i++) {
tmp = PORTB&0xF8; //Mask non-output for PORTB
outb(PORTB,i|tmp); //select MUX channel PORTB
setLED(i, buttonon[i]); //checkButton for Mux
buttonon[i] = checkButton(3);
if (buttonon[i] != testbutton[i]){
oscSendMessageIntInt(PSTR("/button"),i,buttonon[i]);
testbutton[i] = buttonon[i];
}
ptotal[0] = p0yay[0] + p0yay[1] + p0yay[2];
ptotal[1] = p1yay[0] + p1yay[1] + p1yay[2];
}
if (ptotal[0] == 3){
lcdGotoXY(0,1);
rprintf(" Player 1 Wins! ");
}
if (ptotal[1] == 3){
lcdGotoXY(0,1);
rprintf(" Player 2 Wins! ");
}
else{
lcdGotoXY(0,1);
rprintf(" It's a Tie Game ");
}
if ((buttonon[0] & buttonon[1] & buttonon[2] & buttonon[3] & buttonon[4] & buttonon[5] & buttonon[6] & buttonon[7])){
main();
}
} //end of else loop
}
}// end of adding points if statement
} //end of game 1
bump[0] = 200;
bump[1] = -200;
// Haptic controls
p0 = a2dConvert10bit(0) - 512;
if (ABS(p0)>0)f0 = - p0*D/W;
if (ABS(p0)>W){
if(p0<0)f0 = ((p0 + 2*W)*D)/W;
else f0 = ((p0 - 2*W)*D)/W;
}
if (ABS(p0)>2*W)f0 = 0;
if (f0>0) cbi(PORTD, DIRA);
else sbi(PORTD,DIRA);
d0 = MIN(ABS(f0),dutymax);
timer1PWMASet(d0);
p1 = -(a2dConvert10bit(1) - 512);
if (ABS(p1)>0)f1 = p1*D/W;
if (ABS(p1)>W){
if(p1<0)f1 = -((p1 + 2*W)*D)/W;
else f1 = -((p1 - 2*W)*D)/W;
}
if (ABS(p1)>2*W)f1 = 0;
if (f1>0) cbi(PORTD, DIRB);
else sbi(PORTD,DIRB);
d1 = MIN(ABS(f1),dutymax);
timer1PWMBSet(d1);
oscSendMessageIntInt(PSTR("/a2dvalues"),p0,p1);
if(runs++%10==0){// read and display a2d A0 and A1
spyglassLcdGotoXY(0,2);
rprintf("p0:");
rprintfNum(10, 4, TRUE, ' ', p0);
rprintf(" f0:");
rprintfNum(10, 4, TRUE, ' ', f0);
spyglassLcdGotoXY(0,3);
rprintf("p1:");
rprintfNum(10, 4, TRUE, ' ', p1);
rprintf(" f1:");
rprintfNum(10, 4, TRUE, ' ', f1);
}
} // end of if statement
else {main();}
} // end of while(1) loop
a2dOff();
return 0;
}
u08 checkButton(u08 whichButton) {
return (! bit_is_set(PINB,whichButton));
/* Logical negation is because when the button is pushed, the pin
is drawn to ground, so the button is "on" when the bit is zero. */
}
void setLED(u08 i, u08 on) {
u32 msec[8];
u32 latetime[8];
if (on) {
//light the LED
msec[i] = 1389*(u32)timer0GetOverflowCount()/10000; //start each timer
if (buttonconf[i]==0){ //read button status
oscSendMessageIntInt(PSTR("/button"),i, 1);
if (i<3){sbi(PORTB,i+5);}
else {sbi(PORTC,i-1);}
buttonconf[i] = 1; //reset button status
}
}
else {
//turn off the LED
latetime[i] = ((1389*(u32)timer0GetOverflowCount()/10000) - msec[i]);
if (latetime[i] > 1000){ // set the amount of time the light stays on
if (buttonconf[i]==1){ //read button status
oscSendMessageIntInt(PSTR("/button"),i, 0);
if (i<3){cbi(PORTB,i+5);}
else {cbi(PORTC,i-1);}
buttonconf[i] = 0; //reset button status
}
}
}
}
void PERMsetLED(u08 i, u08 on) {
if (on) {
//light the LED
if (buttonconf[i]==0){ //read button status
oscSendMessageIntInt(PSTR("/button"),i, 1);
if (i<3){sbi(PORTB,i+5);}
else {sbi(PORTC,i-1);}
buttonconf[i] = 1; //reset button status
}
}
}