Well, here it is. I’m back for one last documentation on the Intro to IM website, and what looks like it will be the last one of the semester. It feels like it’s been a while since I’ve done one of these. I promise I’ll try to be organised this time, use sub-headings and all.
Before I go ahead, this is Gopika’s documentation for 90% completion of our project. It might be interesting to look at the changes.
The Idea and Story
We wanted to make an interactive murder mystery game with Indian elements. We had already come up with our story by last time – a rich Indian businesswoman has been murdered in a town which seems to be convinced that Enakshi killed her. Enakshi is a supernatural creature of our own creation – based on the south Indian yakshis but employing subtler methods (her eyes of anger) to kill. However, she claims that she is not the one to do it – and it is up to you to find out the truth. We introduce a whole cast of characters including the dead woman’s young husband, his two siblings, his mother, the pujari (priest) of the temple and her dearest friend and her kid.
The Implementation
Early on, we had divided our work. Gopika would work on the main screen, introduction to the legend, interacting with Enakshi, and the wiring of the four clues (and figuring out the capacitance). I worked on the Morse code (and button), the character dialogue screens, the slider mechanism for choosing the killer and the two end screens. Most of these were incorporated into the main code by Gopika because she already had an established system for the buttons. We mostly used a legend variable which would increase or decrease based on the button pressings, and each different value of legend corresponded to a different thing. After that we worked on writing out and planning the clues together.
The Changes
After the user feedback we got from some players and also professor, we decided to change quite a few things and make our game simpler. First we decided to only have a one killer storyline (we were planning to make it randomised before). We also rethought the physical interaction of our clues. This was the game run-down:
Main game screen with About and Play
The Legend
Interaction with Enakshi (with red eyes this time)
Introduction of the murder and characters
Morse code (finally figured out without delay and incorporated as a single unit into the main code now)
Pressing a force sensor to act as a fingerprint scanner and gain access to the clues
Clue 1: Turning to the diary page figured out through Morse and rubbing your finger on the page to have it appear on screen (through capacitance)
Clue 2: Lifting a newspaper attached to a foam board covered in foil and kept on foil (breaking the digital switch)
Clue 3: Lifting a toy from a larger rectangular force sensor (courtesy my previous experience with soft toys and force sensors) so that the reading gives 0
Clue 4: A mechanism where a few layers of tissue separate a card (backed with foil) and a base of aluminium foil, where keeping a heavy object on the edge completes the circuit and lifting it breaks it.
Final sliding screen – made the animation effect more pronounced, and had a clue log (discarded the character log idea as it was too much info dumping)
Which leads to the two ending screens – winning and losing – if you know, you know.
We also incorporated Shamma’s feedback and changed the main screen’s yellow highlights to white. And the biggest task of all – we had to make a control box and use buttons which would be easier for gameplay. After a lot of effort and truly ingenious tinkering (we had to use the sharp edges of the aluminium foil box for cutting), we were able to bring our conveniently blood splattered box to life. We also decided to use crime scene type evidence markers based on Professor’s feedback, and did a bit of decorating after that. The result was something we were really pleased with.
In the final show, we also got a monitor, speaker (we had added a game theme fittingly called ‘Tense Detective’ and winning and losing sounds), and a webcam to connect on the monitor so users wouldn’t have to switch between two physical screens. Though these all acted funky initially, we got them to all work in the end.
I am going to attach some photos of our Arduino circuits too but unfortunately we took the photos after it was already inside the box.
So yeah, it is a bit of a mess. But it should be easy enough to recreate it if we look at the code. Speaking of..
The Code
Since our code is very long, here is the github repo for it. It also has all the other visuals and sounds if you wanna take a look.
The Challenges
Like I already mentioned, everything we tried seemed to not work at first. Or it would be working one day and then not work the next, or not work when we incorporate it with the other clues. For example, the force sensors were messing with each other’s readings, and Gopika ended up finding that (void)analogRead(variable); before reading the actual variable could help. We used this but we never knew what worked in the end. Even the digital switches were very difficult to make fool proof, because we knew they had to be reset again and again for people to play. Even going through test runs was hard because of the length of the game, but in the end the final result was so worth it!
Final Showcase, Reflections and Funnies
We had sorted out all our problems by the final day and even printed a little nameplate of our own.
It was so so satisfying to see people finally play our game and figure out the killer – and a lot of times not. This is a fun game trailer style edit of some of our interactions during the showcase, with the beloved losing screen added this time.
Everyone who played seemed to really enjoy and be invested in it. A few notable funny/scared reactions came every time when
The legend video started moving
Enakshi through the fog
“I miss Mummy.”
The sliding screen animation
“Starting with you…” (for those who got to see it)
We were glad that the mother-in-law wasn’t the totally obvious choice and people were getting quite confused between her, the sister-in-law, and the husband. We even had some people choose the brother-in-law.
A few special mentions must go to Leo who kept insisting that it was the kid and almost made us change the choosing line-up, and to our green button. After so much time spent soldering our beautiful buttons, we didn’t account for the difference in their sizes and the green one ended up being a bit loose- and had the honour of being pushed through hard by a lot of people, starting with Fatima on our class in Monday. It was almost like she had predicted the future.
Overall this was a very rewarding and fun project to work on and it was great working with Gopika, I feel like our visions really aligned for this and it shows in the final product. We took on a lot and I am glad all our hard work was worth it. The showcase where people were loving our game was honestly the highlight of our Finals Week.
I can’t believe this semester and class is at its end now, I truly had such a great time and learnt so many new things, from Professor and also from the other people in the class. Everyone was really kind and helpful, and without much further ado I am going to sign off and wish everyone a great winter break.
So finally, I present to you my magical, whimsical, and bobbing art piece that will let you make your own melody. So here’s the Bobbing melody in action.
To explain the implementation, here is the Arduino circuit.
The potentiometer values are mapped to create a color wheel (the red knob) that will control the color of visualization in real-time. The values of the ultrasonic sensor fitted in the mysterious black box are mapped into the x-axis for visualization. So the hand position in front of the screen is the position of the new balls dropping from the top of the screen. In visualization, these balls bounce at the bottom of the screen and upon hitting the bottom, the color of the just hit ball changes to yellow to eventually later fade back into its original color. The whole screen is divided into twenty-one different notes of a piano and the x position of the hit ball determines the corresponding note to play. The music for piano is implemented using MIDI library and using tools like sforzando and loopMIDI.
While this project was very challenging and frustrating at times to finish, I surely had a great time working on it. I am really glad how it turned out. A lot of the parts, especially the visualization was a trial and error process. I played around with a lot of color schemes and the speed and frame rate, took a lot of time, but finalized on my current settings and I love it. I hope you enjoyed this too. If time permits, I would love to work on this project more to have various different instruments and visualizations to create a whole tune using different musical instruments and different visualization. Surely, this project and the class have inspired me a lot creatively.
Below is the code for Processing and Arduino.
//PROCESSING
//Serial connection and sound
import processing.serial.*;
import themidibus.*;
MidiBus myBus;
Serial myPort;
//variables for color mapping
int count = 400;
float radToDeg = 180.0 / PI;
float countToRad = TWO_PI / float(count);
float currentcolor;
float Xslider;
boolean fade = false;
//array list for balls
ArrayList<Ball> balls;
//sounds 21
int[] pitches = {48,52,53,55,59,60,64,65,67,71,72,76,77,79,83,84,88,89,91,95,96};
void setup() {
fullScreen();
//size(800,600);
noStroke();
frameRate(54);
//port communication
String portname=Serial.list()[0];
myPort = new Serial(this,portname,9600);
//midibus
myBus = new MidiBus(this,-1,"loopMIDI Port");
// Create an empty ArrayList (will store Ball objects)
balls = new ArrayList<Ball>();
//color mode
colorMode(HSB,360,99,99);
}
void draw() {
background(0,0,0);
//adding new balls on screen
if(fade == false){
addBalls(1);
}
//display all balls in action
for (int i = balls.size()-1; i >= 0; i--) {
Ball ball = balls.get(i);
if(ball.colorhue != 0){
ball.move();
}
if(ball.colorhue != 0){
ball.display();
}
//if no motion or very away from slider, reduce their life
if(ball.x <= Xslider-width/6|| ball.x >= Xslider+width/6 || fade==true){
if(ball.life > 100){
ball.life = 100;
}
}
//sound and colorshift on bouncing back from bottom
if(ball.y >= height){
ball.colorhue = 60;
int numsound = int(map(ball.x,0,width,0,19));
if(numsound<0 || numsound>20){
numsound = abs(numsound%21);
}
if(ball.life<100){
myBus.sendNoteOn(0,pitches[numsound],50);
}
else{
myBus.sendNoteOn(0,pitches[numsound],127);
}
}
//set the color back to orignal
if(ball.y < height-height/9){
ball.colorhue = ball.originalhue;
}
//remove the balls from array with finished lives
if (ball.finished()) {
balls.remove(i);
}
}
}
// A new set of x ball objects are added to the ArrayList
void addBalls(int count) {
for(int i=0; i<count; i++){
float randomS = random(0,10);
float ballWidth = random(4,30);
float xPos = random(Xslider-width/20, Xslider+width/20);
float yPos = random(-20,0);
float angle = currentcolor * countToRad;
int hue = int(angle * radToDeg);
balls.add(new Ball(xPos, yPos, ballWidth,randomS,hue));
}
}
//Serial Communication
void serialEvent(Serial myPort){
String s=myPort.readStringUntil('\n');
s=trim(s);
if (s!=null){
int values[]=int(split(s,','));
if (values.length==2){
currentcolor = map(values[0],19,925,380,290);
print(values[0]);
print(",");
println(values[1]);
if(values[1]>150 && values[1]<3000){
fade= false;
Xslider= map(values[1],150,3000,width, 0);
}
else{
fade = true;
}
}
}
}
// Simple bouncing ball class
class Ball {
float x;
float y;
float speed;
float acceleration;
float w;
float life = 255;
int colorhue;
int originalhue;
float drag;
boolean bounced= false;
Ball(float tempX, float tempY, float tempW, float tempS, int hue) {
x = tempX;
y = tempY;
w = tempW;
speed = tempS;
colorhue = hue;
originalhue = hue;
acceleration = 0.3;
drag = 0.99;
}
void move() {
//add speed
speed = speed + acceleration;
speed = speed * drag;
// Add speed to y location
y = y + speed;
// If ball reaches the bottom
// Reverse speed
if (y > height) {
// Dampening
speed = speed * -0.9;
y = height;
bounced = true;
}
if(bounced==true && y<=10){
// Dampening
speed = speed * -0.9;
y = 10;
bounced = false;
}
}
boolean finished() {
// Balls fade out
life--;
if (life < 0) {
return true;
} else {
return false;
}
}
void display() {
// Display the circle
if(colorhue == 0){
fill(color(colorhue,0,99),life);
}
else{
fill(color(colorhue,99,99),life);
}
ellipse(x,y,w,w);
}
}
//ARDUINO
int trigger_pin = 2;
int echo_pin = 3;
long distance, pulse_duration;
int starttime;
void setup() {
Serial.begin(9600);
pinMode(trigger_pin, OUTPUT);
pinMode(echo_pin, INPUT);
digitalWrite(trigger_pin, LOW);
Serial.println("0,0");
}
void loop() {
int sensor = analogRead(A0);
digitalWrite(trigger_pin, HIGH);
digitalWrite(trigger_pin, LOW);
pulse_duration = pulseIn(echo_pin, HIGH);
Serial.print(sensor);
Serial.print(',');
Serial.println(pulse_duration);
}
The curses have random x and y positions and they move at random speeds. Once they collide with Yuji (the character) they disappear and the player loses a life. (You start with 5 lives, and you can also lose a life when you allow the ghosts to go offscreen). The curses also disappear when you shoot them. This class has two functions: display and move. Display shows the image if the boolean (disp) is true, and move makes the ghosts move in the x direction and come back on the left side of the screen if they leave the screen on the right.
This is the bullets class: (I have 2 in my code but they are essentially the same so I’ll only copy one onto here):
class Bullet {
float posx, posy;
float speed;
float a; //acceleration
float r;
boolean disp = true;
color col;
Bullet(float _posx, float _posy, float _speed, float _a, color _col) {
posx = _posx;
posy = _posy;
col = _col;
speed = _speed;
a = _a;
r =50;
}
void display() {
fill(col);
if (disp) {
circle(posx, posy, r);
}
}
void move() {
posx -= speed;
speed += a;
}
float distance(float x, float y) {
return dist(posx, posy, x, y);
}
boolean done() {
// removing bullets once they leave the screen
if (posx < 0-r) {
return true;
} else {
return false;
}
}
}
The bullets have the same functions as the curses class, with the addition of a color argument in the constructor (since there are two types of bullets in the game , one for the fingers and the other for the curses).
There is a distance variable which measures the distance of each bullet, since the bullets are in an array list.
The boolean ‘done’ removes the bullets once they’re offscreen to save memory!
The ‘Sukuna’Class refers to the fingers, here it is:
It only has a display function, which is exactly the same as the other classes display functions, since the finger doesn’t move on screen once it appears.
As for the main code, I’ll go through it bit by bit:
Here is my beginning code, which is mostly filled with initializing variables:
import ddf.minim.*;
Minim minim;
AudioPlayer powerup;
AudioPlayer theme;
AudioPlayer gameover;
AudioPlayer life;
AudioPlayer woosh;
AudioPlayer winsound;
import processing.serial.*;
Serial myPort;
int score = 0;
float analog;
float starttime;
int fingercount = 0;
int lives = 5;
float prevbullettime=0;
int yujix;
PImage yuji, yujishoot, yujiidle, logo, battlebg, leftghost, finger, win, lose;
PFont gamefont;
int direction = 1;
int yujiR = 70;
int ghostR =60;
int bulletR =40;
int fingerR =70;
int speed = 3;
float acc = .2;
float gamemode = 1;
float countdown=0;
float respawntimer=0;
float fingertimer;
boolean timerActivated = false;
ArrayList<Bullet> bullets;
ArrayList<SBullet> sbullets;
Curse[] curses;
Sukuna[] sukuna;
Here is my setup code:
void setup () {
fullScreen();
battlebg = loadImage("battlebg.png");
battlebg.resize(displayWidth, displayHeight);
translate((displayWidth-1425)/2, (displayHeight-780)/2 +100);
yujix = width-100;
yujiidle = loadImage("yujiidle.png");
yujishoot= loadImage("yujishoot.png");
logo = loadImage("logo.png");
leftghost = loadImage("leftghost.png");
finger = loadImage("finger.png");
win = loadImage("win.jpg");
lose = loadImage("losepic.png");
bullets = new ArrayList<Bullet>();
sbullets = new ArrayList<SBullet>();
minim = new Minim(this);
powerup =minim.loadFile("powerup.wav");
theme = minim.loadFile("jjkop.wav");
theme.loop();
gameover = minim.loadFile("gameover.wav");
life = minim.loadFile("life.wav");
woosh = minim.loadFile("swoosh.wav");
winsound = minim.loadFile("winsound.wav");
gamefont = createFont("/Users/fatimaaljneibi/Library/Fonts/Anime Inept.otf", 32);
imageMode(CENTER);
textAlign(CENTER);
//ghosts:
curses = new Curse[8] ;
for (int i = 0; i<curses.length; i++) {
curses[i]= new Curse(0, random(200, height-200), random(2, 8), random(-5, 5) );
}
sukuna = new Sukuna[1] ;
for (int i = 0; i<sukuna.length; i++) {
sukuna[i]= new Sukuna(random(300, width-300), random(200, height-200));
sukuna[i].disp = false;
}
//serial setup stuff :
printArray(Serial.list());
String portname=Serial.list()[17];
println(portname);
myPort = new Serial(this, portname, 9600);
myPort.clear();
myPort.bufferUntil('\n');
}
I set the game to be in fullscreen, loaded all my images and fonts, and called my classes ( there are 8 ghosts, 1 finger, and array lists for the bullets). I ended up using Minim instead of the processing sound library, because I found it easier to use for my sounds to play once.
My void draw code has all 4 of my game screens, here’s the code for the starting screen:
// starting screen:
if (gamemode == 1) {
if (!theme.isPlaying()) {
theme.rewind();
theme.loop();
}
background(#728DC6);
textFont(gamefont, 150);
image(logo, width/2, height/2-270, 400, 200);
fill(#1D194D);
text("CURSE BREAKERS!", width/2, height/2-100);
////title ^^ instructions below
textFont(gamefont, 40);
text("-press the red button to start-", width/2, height/2);
//personal note: might turn the controller instructions into an image
text("help yuji kill the curses and collect sukuna's fingers!", width/2, height/2+60);
text("use the slider to move yuji, the yellow button to attack", width/2, height/2+100);
text("and the blue button to collect the fingers!", width/2, height/2+140);
text("collect 5 fingers within the time limit to win!", width/2, height/2+190);
} //start screen end
It’s mostly just instructional text and starting the theme song loop. Here’s a screenshot of what it looks like in the game!
Here’s the main game code:
//game screen:
if (gamemode == 2) {
background(battlebg);
if (!theme.isPlaying()) {
theme.loop();
}
image(yuji, yujix, analog, 140, 200);
// respawn curses:
if (timerActivated==true) {
if (millis()>respawntimer ) {
timerActivated = false;
for (int i=0; i<curses.length; i++) {
if (curses[i].disp == false) {
curses[i].curx = random(-100, -10) ;
curses[i].cury = random(200, height-200);
curses[i].disp=true;
}
}
}
}
if (timerActivated == false && gamemode == 2) {
respawntimer = millis()+3000; // the curses reappear on screen
timerActivated = true;
}
//collision detection:
for (int i=0; i<curses.length; i++) {
float d1 = dist(yujix, analog, curses[i].curx, curses[i].cury);
if ( d1 <= ghostR + yujiR && curses[i].disp==true) {
lives-= 1;
life.rewind();
life.play();
curses[i].disp=false; //subtracts a life from yuji when he gets in contact with a ghost and makes it disappear on contact
}
for (int j = bullets.size()-1; j >= 0; j--) {
float d2 = bullets.get(j).distance(curses[i].curx, curses[i].cury);
if ( d2 <= ghostR + bulletR && curses[i].disp==true) {
curses[i].disp=false;
bullets.get(j).disp=false;
bullets.remove(j);
score += 10;
woosh.rewind();
woosh.play();
}
}
}
//shooting the fingers:
for (int i=0; i<sukuna.length; i++) {
for (int j = sbullets.size()-1; j >= 0; j--) {
float d2 = sbullets.get(j).distance(sukuna[i].posx, sukuna[i].posy);
if ( d2 <= fingerR + bulletR && sukuna[i].disp==true) {
sukuna[i].disp=false;
sbullets.get(j).disp=false;
sbullets.remove(j);
fingercount += 1;
powerup.rewind();
powerup.play();
}
}
}
//scoreboard + timer:
pushStyle();
textAlign(LEFT);
textFont(gamefont, 30);
fill(255);
text(" Fingers collected: " + fingercount, 0, 30);
text(" Score: " + score, 0, 60);
text(" Lives left: " + lives, 0, 90);
countdown = 60 -int((millis()-starttime)/1000);
text(" Time left: " + countdown, 0, 120);
popStyle();
// calling class functions for the curses + bullets:
for (int i=0; i<curses.length; i++) {
curses[i]. display();
curses[i]. move();
}
for (int i = bullets.size()-1; i >= 0; i--) {
Bullet bullet = bullets.get(i);
bullet.display();
bullet.move();
if (bullet.done()) {
bullets.remove(i);
// removes the bullets from the arraylist once they leave the screen to save memory
}
}
for (int i = sbullets.size()-1; i >= 0; i--) {
SBullet sbullet = sbullets.get(i);
sbullet.display();
sbullet.move();
if (sbullet.done()) {
sbullets.remove(i);
// removes the bullets from the arraylist once they leave the screen to save memory
}
}
//win condition:
if (countdown<=0 && fingercount <5) {
gamemode = 4;
theme.pause();
winsound.rewind();
for (int i = sbullets.size()-1; i >= 0; i--) {
sbullets.remove(i);
}
for (int i = bullets.size()-1; i >= 0; i--) {
bullets.remove(i);
}
//lose condition 2: (if time runs out)
} else if (countdown>=0 && fingercount == 5) {
gamemode =3;
theme.pause();
for (int i = sbullets.size()-1; i >= 0; i--) {
sbullets.remove(i);
}
for (int i = bullets.size()-1; i >= 0; i--) {
bullets.remove(i);
}
}
//finger mechanics:
for (int i=0; i<sukuna.length; i++) {
sukuna[i].display();
}
for (int i=0; i<sukuna.length; i++) {
if (score % 100 ==0 && score >0) {
fingertimer =3000+millis();
sukuna[i].disp = true;
}
if (millis() > fingertimer) {
sukuna[i].disp = false;
fingertimer =0;
}
}
// if the curses leave the screen without being shot, you lose 1 life
for (int i=0; i<curses.length; i++) {
if (curses[i].curx > width && curses[i].disp == true) {
lives -= 1;
life.rewind();
life.play();
curses[i].disp = false;
}
}
// you lose if your lives are at 0
if (lives <= 0) {
gameover.rewind();
gamemode = 4;
}
} // game screen end
There’s quite a lot going on here. The character is drawn here, with his Y position being the slider (analog input). We’ll go over the serial communication in a bit!
I made a respawn timer for the curses, basically the logic here is that if the curses disp = false ( i.e. they’re not visible on screen) and the current time > respawn timer, the curses will respawn at a new random height on screen, with the width being offscreen on the left, so they move forward to the right.
After that, there’s the code for collision detection. The logic here is that if the distance between the two objects is less than the sum of their radii (which are variables that I set at the very beginning of my code) then something happens! The code for what that ‘something’ is differs depending on what two objects they are. There’s collision detection for the bullets and the curses, and for the curses and yuji, and for the bullets and the fingers.
Then, there’s code for the scoreboard and the timer. You can see the scoreboard in the top left of this screenshot: It has a finger counter(increases whenever you shoot a finger), a lives counter(starts at 5, decreases by one if Yuji gets hit by a ghost, or lets one get away), a score(based on the ghosts that you hit) and a timer! The timer is 60 seconds from the start of the game.
Following this code is the code to call class functions for the curses and bullets.
After this comes the code for win conditions. You can only win after collecting 5 of Sukuna’s fingers. A finger appears after you hit a score that’s divisible by 100, and it disappears after a few seconds if you don’t collect it in time! Once you win, you go to the winning screen!
To lose the game, you either lose all of your lives or you don’t collect 5 fingers by the time the timer ends! Once you lose, you are taken to the losing screen ><.
Then, we have the code for the finger mechanics! It’s pretty simple, I called the display function for the finger, and the disp boolean is initially false. It becomes true once you hit a score divisible by zero, and disappears shortly after.
The code following that is to decrease the lives variable by 1 every time a ghost leaves the screen.
Heres the winning and losing screen’s code, accompanied by screenshots of both in-game:
// winning & losing screens:
//win screen, score/time , sound effect:
if (gamemode == 3) {
theme.pause();
winsound.play();
background(#1D194D);
textFont(gamefont, 100);
fill(#728DC6);
text("YOU WIN!", width/2, height-250);
//title ^^ info below
textFont(gamefont, 50);
text("score:"+score, width/2, height-200);
text("time left:"+countdown, width/2, height-150);
text("fingers collected:"+ fingercount, width/2, height-100);
textFont(gamefont, 40);
text("-press the red button to try again!-", width/2, height-50);
image(win, width/2, 250, 700, 400);
}//win screen end
if (gamemode == 4) {
//losing screen, click to try again, sound effect
theme.pause();
gameover.play();
background(#1D194D);
textFont(gamefont, 100);
fill(#728DC6);
text("YOU LOSE:(", width/2, height-200);
//title ^^ info below
textFont(gamefont, 50);
text("score:"+score, width/2, height-150);
text("fingers collected:"+ fingercount, width/2, height-100);
textFont(gamefont, 40);
text("-press the red button to try again!-", width/2, height-50);
image(lose, width/2, 300, 700, 400);
}//losing screen end
}//draw bracket
Also, I’ve added various sound effects to the game, I’ll list them here:
A ‘whoosh’ sound when you shoot the curses
A powerup sound effect when you collect a finger
The theme song (which plays only during the start screen and the game screen)
A victory sound plays in the win screen
A game over sound plays on the lose screen
Here is the serial communications code:
Processing:
void serialEvent(Serial myPort) {
String s=myPort.readStringUntil('\n');
s=trim(s);
//int values = parseInt(s);
//boolean shoottest = false;
if (s!=null) {
//println(s);
int values[]=int(split(s, ','));
// note: values: 0 = slider, 1= yellow, 2= blue, 3= red
//slider:
analog =map(values[0], 0, 1023, 100, height-70);
yuji = yujiidle;
//attacking (yellow) button:
if (values[2] == 1 && gamemode == 2) {
// not working:
yuji = yujishoot;
color Ccircle = color(#98F7FF, 150);
noStroke();
if (millis() - prevbullettime > 400) {
bullets.add(new Bullet(width-150, analog+20, speed, acc, Ccircle));
prevbullettime= millis();
}
}
// start button:
if (values[3] == 1 && gamemode == 1) {
starttime=millis();
fingercount = 0;
score = 0;
gamemode = 2;
//replay : (red button)
} else if (values[3] == 1 && gamemode == 3 || values[3] == 1 && gamemode ==4) {
fingercount = 0;
lives = 5;
gamemode =1;
delay(1000); // so you dont skip to the start screen too quickly after pressing the button
for (int i=0; i<curses.length; i++) {
curses[i].curx = random(-100, -10);
}
} //fingers (blue)
if (values[1] == 1 && gamemode == 2) {
yuji = yujishoot;
color Scircle = color(#FF8B8B, 150);
noStroke();
if (millis() - prevbullettime > 400) {
sbullets.add(new SBullet(width-150, analog+20, speed, acc, Scircle ));
prevbullettime= millis();
}
}
}
myPort.write(int(0)+"\n");
}
Arduino:
int bluepin = 5;
int yellowpin = 3;
int redpin = 6;
int sliderpin = A5;
void setup() {
pinMode(bluepin , INPUT);
pinMode(yellowpin , INPUT);
pinMode(redpin , INPUT);
pinMode(sliderpin , INPUT);
Serial.begin(9600);
Serial.println("0,0,0,0");
}
void loop() {
int bluebutton = digitalRead(bluepin);
int yellowbutton = digitalRead(yellowpin);
int redbutton = digitalRead(redpin);
int slider = analogRead(sliderpin);
//checking if everything is connected properly:
//comment this out to run processing
// Serial.print(bluebutton);
// Serial.print(yellowbutton);
// Serial.print(redbutton);
// Serial.println(slider);
while (Serial.available()) {
int fromP = Serial.parseInt();
if (Serial.read() == '\n') {
Serial.print(slider);
Serial.print(",");
Serial.print(yellowbutton);
Serial.print(",");
Serial.print(bluebutton);
Serial.print(",");
Serial.println(redbutton);
}
}
}
I have 4 parts connected to my circuit: 3 buttons and a slider. I built a controller box out of foam to safely conceal my circuit while also making it easy for players to use! Here is my process making that:
It looked like this at first, but I spray painted it black afterwards and added a 3rd button! (the red one, to start playing the game. initially, you would use either the blue or yellow to start playing the game and switch between screens, but that wasn’t giving me great results, so I decided to add a third button)
Here’s the final product!
Aaaaaand that’s it! TA-DA!! I’m very proud of what I’ve learned and achieved in this course, I had lots of fun!! Thank you 🙂
Operations: The note pads correspond to the pentatonic C scale: C, D, E, G, A. As shown on the pads, they represent all the things I hate, I assume you too, the most throughout this semester. To win the game, you must recreate a melody by smashing the pads.
Symbols: The things I hate the most this semester
C note: COVID
D note: Deadlines
E note: course Enrollment
G note: GPA
A note: Anxiety
IMPLEMENTATION:
Title Page
Rules
Practice: plays demo melody
Test
Win or Lose
video of im showcase:
Most people got it correct after 2-3 tries, around 3-5 succeeded on the first try (talk about music talents), and 3 people just gave up. People enjoyed it a lot more than I thought they would!
CODES:
//Processing Codes
/*
name: Chi-Ting Tsai
assignment: final project
date: 12/07/2021
"Smash It!" is a music memory game where player would be instructed to memorize a melody
and recreate it by stepping on the pads corresponding to notes on the pentatonic C scale.
*/
import processing.sound.*;
SoundFile demoMelody;
SoundFile recC;
SoundFile recD;
SoundFile recE;
SoundFile recG;
SoundFile recA;
SoundFile winning;
SoundFile losing;
int size = 200;
color frame = color(237, 168, 17);
int stage;
int noteC = 1;
int noteD = 2;
int noteE = 3;
int noteG = 5;
int noteA = 6;
int[] correctMelody = {noteD, noteC, noteE, noteG, noteA, noteG, noteD, noteC};
void setup() {
size (displayWidth, displayHeight);
background(color(99, 144, 130));
String portname=Serial.list()[3];
println(portname);
myPort = new Serial(this, portname, 9600);
myPort.clear();
myPort.bufferUntil('\n');
demoMelody = new SoundFile (this, "/Users/chi-tingtsai/Downloads/5-seconds-of-silence_gcP1Vako.wav");
recC = new SoundFile (this, "/Users/chi-tingtsai/Downloads/C.wav");
recD = new SoundFile (this, "/Users/chi-tingtsai/Downloads/D.wav");
recE = new SoundFile (this, "/Users/chi-tingtsai/Downloads/E.wav");
recG = new SoundFile (this, "/Users/chi-tingtsai/Downloads/G.wav");
recA = new SoundFile (this, "/Users/chi-tingtsai/Downloads/A.wav");
winning = new SoundFile (this, "/Users/chi-tingtsai/Downloads/Winning Sound Effect.wav");
losing = new SoundFile (this, "/Users/chi-tingtsai/Downloads/The Price is Right Losing Horn - Gaming Sound Effect (HD).wav");
}
void draw() {
//scene change
if (stage == 0) {
rank();
} else if (stage == 1) {
rules();
} else if (stage == 2) {
practice();
} else if (stage == 3) {
game();
} else if (stage == 4) {
lose();
} else if (stage == 5) {
win();
}
}
//Processing
void rules() {
background(color(99, 144, 130));
PFont F = createFont("TimesNewRomanPS-BoldMT", 1);
noStroke();
fill(255);
textAlign(CENTER);
//explain how to play
textFont(F, 50);
text("RULES: Memory Game", width/2, height/4);
textFont(F, 30);
text("INSTRUCTIONS:\n The note pads correspond to the pentatonic C scale: C,D,E,G,A.\n As shown on the pads, they represent all the things I hate,\nI assume you too, the most throughout this semester.\n To win the game, you must recreate a melody by smashing the pads.", width/2, height/2-120);
textFont(F, 30);
text("Smash Deadlines to play the melody ONCE(AND ONLY ONCE!) and practice!", width/2, height-200);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-60, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
//Processing Codes
int i = 0;
char[] refMelody = {' ', ' ', ' ', 'D', 'C', 'E', 'G', 'A', 'G', 'D', 'C'};
void practice() {
background(color(99, 144, 130));
PFont F = createFont("TimesNewRomanPS-BoldMT", 1);
noStroke();
fill(255);
textAlign(CENTER);
//text
textFont(F, 50);
text("LISTEN CAREFULLY & PRACTICE (8 NOTES)", width/2, height/4);
textFont(F, 30);
text("Practice to familiarize with how the notes sound!\nRememer: your job is to smash the pads to recreate the melody!\n", width/2, height/2-120);
textFont(F, 50);
textFont(F, 50);
text("Smash Space Bar to begin smashing!", width/2, height-170);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-60, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
//telling player which note it is
if (frameCount%118 == 0 && i < 10) {
i++;
}
text("Now playing: \n" + refMelody[i], width/2, height/2+75);
}
//Processing Codes
import processing.serial.*;
Serial myPort;
int aSignal = 0;
int prevASignal = 0;
int val = 0;
void game() {
if (i == 10) {
i = 0;
}
//background
background(color(99, 144, 130));
PFont F = createFont("TimesNewRomanPS-BoldMT", 1);
noStroke();
fill(255);
textAlign(CENTER);
textFont(F, 50);
text("Listen Carefully and Recreate!", width/2, height/4);
textFont(F, 30);
text("The things I hate the most this semester\nC note: COVID\nD note: Deadlines\nE note: course Enrollment\nG note: GPA\nA note: Anxiety\n", width/2, height/2-80);
fill(frame);
textFont(F, 50);
text(val + "/8" + " Notes Correct! Keep smashing!", width/2, height-170);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-60, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
void serialEvent(Serial myPort) {
String s=myPort.readStringUntil('\n');
s=trim(s);
if (s!= null) {
aSignal = parseInt(s);
//scene transition
if (stage == 0 && aSignal == noteC) {
stage = 1;
recC.play();
val = 0;
}
if (stage == 1 && aSignal == noteD) {
stage = 2;
demoMelody.amp(0.8);
demoMelody.play();
}
if ((stage == 4 || stage == 5) && aSignal == noteG) {
stage = 0;
recG.play();
}
//note playing and verification
if ((stage == 2 || stage == 3) && prevASignal!= aSignal) {
if (aSignal == noteC) {
recC.play();
} else if (aSignal == noteD) {
recD.play();
} else if (aSignal == noteE) {
recE.play();
} else if (aSignal == noteG) {
recG.play();
} else if (aSignal == noteA) {
recA.play();
}
//verify note in array
if (stage == 3 && aSignal != 0) {
if (correctMelody[val] == aSignal) {
val++;
prevASignal = aSignal;
if (val == 8) {
delay(2000);
stage = 5;
winning.play();
}
} else {
delay(2000);
stage = 4;
losing.amp(0.3);
losing.play();
}
}
prevASignal = aSignal;
}
}
myPort.write('\n');
}
void keyReleased() {
if (key == ' ' && stage == 2) {
stage = 3;
game();
}
}
//Processing Codes
void lose() {
background(color(99, 144, 130));
PFont F = createFont("TimesNewRomanPS-BoldMT", 1);
noStroke();
fill(255);
textAlign(CENTER);
textFont(F, 50);
text("Awwww... Smash GPA to play again.", width/2, height*3/4);
text("Not the right note!\nYou've reached the " + (val) + "th note in the melody!", width/2, height/2-80);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-60, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
//Processing
void win() {
background(color(99, 144, 130));
PFont F = createFont("TimesNewRomanPS-BoldMT", 1);
noStroke();
fill(255);
textAlign(CENTER);
textFont(F, 50);
text("Congratulations! You have recreated the entire melody!", width/2, height/2);
textFont(F, 50);
text("Smash GPA to play again!", width/2, height/2+50);
textFont(F, 30);
fill(frame);
text("Have you found your peace by smashing those that give off negative energy?", width/2, height/2-50);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-60, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
//Arduino codes
//variable declaration
const int C = 1;
const int D = 2;
const int E = 3;
const int G = 5;
const int A = 6;
const int in_C = 2;
const int in_D = 3;
const int in_E = 4;
const int in_G = 5;
const int in_A = 6;
const int led_C = 9;
const int led_D = 10;
const int led_E = 11;
const int led_G = 12;
const int led_A = 13;
int switch_C, switch_D, switch_E, switch_G, switch_A = 0;
int smash = 0;
void setup() {
// put your setup code here, to run once:
pinMode(in_C, INPUT);
pinMode(in_D, INPUT);
pinMode(in_E, INPUT);
pinMode(in_G, INPUT);
pinMode(in_A, INPUT);
Serial.begin(9600);
Serial.println("0");
}
void loop() {
// put your main code here, to run repeatedly:
switch_C = digitalRead(in_C);
switch_D = digitalRead(in_D);
switch_E = digitalRead(in_E);
switch_G = digitalRead(in_G);
switch_A = digitalRead(in_A);
while (Serial.available()) {
smash = 0;
if (Serial.read() == '\n') {
//Note C
if (switch_C == 1) { //released, if the two aluminum foils touch
smash = C;
}
else if (switch_C && switch_D && switch_E && switch_G && switch_A == 0)
{ //released, if the two aluminum foils partLED
smash = 0;
}
//Note D
if (switch_D == 1) { //released, if the two aluminum foils touch
smash = D;
}
else if (switch_C && switch_D && switch_E && switch_G && switch_A == 0)
{ //released, if the two aluminum foils partLED
smash = 0;
}
//Note E
if (switch_E == 1) { //released, if the two aluminum foils touch
smash = E;
}
else if (switch_C && switch_D && switch_E && switch_G && switch_A == 0)
{ //released, if the two aluminum foils partLED
smash = 0;
}
//Note G
if (switch_G == 1) { //released, if the two aluminum foils touch
smash = G;
}
else if (switch_C && switch_D && switch_E && switch_G && switch_A == 0)
{ //released, if the two aluminum foils partLED
smash = 0;
}
//Note A
if (switch_A == 1) { //released, if the two aluminum foils touch
smash = A;
}
Serial.println(smash);
}
}
}
CHALLENGES:
After receiving feedback from classmates, I have made several improvements:
Adding visual cues with the demo melody
Informing players how many notes they have recreated correctly
At the beginning of the showcase, someone flagged out a few bugs in the interface but nothing major. They were removed swiftly and the rest of the show went by smoothly in back-to-back user testing!
last note
Thank you so much for the semester. Happy Christmas holidays and have a joyful New Year. Have a restful winter break as well.
This was quite a journey, with many ups and downs but definitely one that ended in relief and content! The idea of our final project revolved giving our audience a chance to go back to their childhood and just have fun! Anyone that interacts with our project is given the opportunity to just draw whatever their heart desires, and the physical interactivity (because users will draw with their hands) gives it an extra interesting and more immersive experience. We wanted our project to give the user complete freedom, so they users can choose between 5 different brushes and use the color they want to draw using the color selector.
Implementation:
There were mainly two aspects of this project, the processing aspect and the Arduino aspect (and serial communication). This is essentially also how we split the work between us. Fatema essentially took on the challenge of the processing side of things and I took on the challenge of the Arduino side of things and the blog documentations. So Fatema is the one that created the beautiful generative art pieces that played the role of our brushes, and she worked on the fiducial tracking as well (which was a pain! so she did a great job). While I focused on the arduino side of things, wiring up the buttons and slide potentiometer, creating the color gradient and its code on processing, and creating the serial communication between arduino and processing, I also took responsibility of the blog documentation. However, we also found ourselves working together most of the time, Fatema would help me with the arduino when I was stuck and vice versa. So it eventually turned into us working together on most of the aspects of the project rather than individually which worked out great!
lessons learned/how to improve:
This project is one that definitely helped us grow. Naturally it was not a perfect linear growth, hence there is always room for improvement. reflecting on where we can improve is definitely an essential thing to do. After the showcase these are the things we noticed could have been executed better:
During our presentation to the class on the 13th, a very inconvenient and stupid mistake (we just need to move the location of the project slightly due to lighting issue) caused us to panic and not be able to show the class our project. lesson learned! we should have set up earlier especially since our project was not very straight forward and is prone to spontaneous errors.Luckily it all worked out in the end.
When it comes to the project itself, we found that we should have included some sort of feedback for when the buttons are pressed, just to let the person know which brush they are using.
We also learned that sometimes the finer details take even more time and effort than the bigger aspects of the project! and they bring the project together at the end.
IM showcase:
Here are some of the very unique masterpieces that people created on our digital doodle in the showcase. It was so much fun seeing how different people interacted differently with our project!
And here are a few video out of many of people interacting with our project (you can see how people move in the camera on the screen)!!
Arduino code:
int left = 0;
int right = 0;
int buttonOne = 9;
int buttonTwo = 10;
int buttonThree = 11;
int buttonFour = 12;
int buttonFive = 13;
int buttonSix = 3;//reset
int buttonSeven = 5;//Draw
int buttonEight = 4;//saveScreen
int val1 = 0;
int val2 = 0;
int val3 = 0;
int val4 = 0;
int val5 = 0;
int val6 = 0;
int val7 = 0;
int val8 = 0;
void setup() {
pinMode(buttonOne, INPUT);
pinMode(buttonTwo, INPUT);
pinMode(buttonThree, INPUT);
pinMode(buttonFour, INPUT);
// pinMode(buttonFive, INPUT);
Serial.begin(9600);
Serial.println("0,0");
}
void loop() {
while (Serial.available()) {
right = Serial.parseInt();
left = Serial.parseInt();
if (Serial.read() == '\n') {
int sensor = analogRead(A0);
delay(1);
val1 = digitalRead(buttonOne);
delay(1);
val2 = digitalRead(buttonTwo);
delay(1);
val3 = digitalRead(buttonThree);
delay(1);
//
val4 = digitalRead(buttonFour);
delay(1);
val5 = digitalRead(buttonFive);
delay(1);
val6 = digitalRead(buttonSix);
delay(1);
val7 = digitalRead(buttonSeven);
delay(1);
val8 = digitalRead(buttonEight);
delay(1);
Serial.print(sensor);
Serial.print(',');
Serial.print(val1);
Serial.print(',');
Serial.print(val2);
Serial.print(',');
Serial.print(val3);
Serial.print(',');
Serial.print(val4);
Serial.print(',');
Serial.print(val5);
Serial.print(',');
Serial.print(val6);//
Serial.print(',');
Serial.print(val7);//
Serial.print(',');
Serial.println(val8);
}
}
}
It’s the final project! My project, named “Horror Journal”, is a personal diary simulator of sorts. This project is an interactive version of the novel “The Horla”. The diary has 39 entries in total, spanning from May to September. Find out more about how it works below!
Right in front of the user are two sheets of paper labeled “Previous Entry” and “Next Entry”. Below the hand icons printed on each paper is a Force Sensing Resistor (FSR) wired to the Arduino. Without user interaction, Arduino communicates a “0” to Processing. When the user pushes on the sheet, the FSR generates higher values. Once a specified threshold is passed, Arduino communicates a “1” to Processing. Processing then detects the “1” and switches to the next or previous diary entry depending on the user interaction.
Summoning the Demon:
In the novel being played in the project, the character writing the diary entries is experiencing some sort of mysterious illness. By the end of the story, we learn that the character is being possessed by some sort of demon who he names “The Horla”. Since this is the biggest plot point in the story, I thought it would only make sense to incorporate it in the user interaction. For this interaction, I used a webcam, a Big Demon Glove (also known as a beanie), and a servo. In Processing, I read frames from the webcam and applied frame differencing on them to detect motion within the area captured by the webcam. This area is outlined by the big red sheet seen on the table. The servo I’m using is taped on the Big Demon Glove. On the servo, a small paper strip which reads “The Horla” is taped. When the user places the Big Demon Glove on the red sheet area, the camera detects the motion of servo, or the paper strip more accurately. This triggers a red strip on the screen to grow. Once the red strip reaches the bottom of the screen, the interaction is triggered: the screen turns red and the voice of the narrator turns deeper, imitating a stereotypical demonic voice. Pictures of this interaction can be seen below:
Aside from the interactions, my project consists of the Processing screen, which displays the text of the diary entry being played, and audio readings of the current entry. The screen consists of yellow, red, and black colors. Originally, I wanted to create a more elaborate screen interface that simulates a personal room environment. I quickly realized that going with a simpler interface would make more sense with the interactions I planned. Additionally, the simpler interface looked generally cleaner and easier to follow. I was happy to hear that other people agreed with me.
Overall Thoughts:
I’m glad I went with this project. I have received variety of reactions at the showcase ranging from “This is a very specific project” to “Wow! [the demonic voice] is creepy”. I noticed that a video game type project might have been more approachable in the showcase, but I still had many people come over to take a look at my project. One limitation might have been that no one would actually be able to sit through the entire story being narrated in my project since it is so lengthy. I did think about this possibility in my design phase. I thought about several solutions such as cutting down on the content of the story, but keeping everything in and allowing the user to navigate through the content on their own made more sense to me. I also wish I had booked a speaker early on. When I tested my project in the lobby, it sounded fine, but I did not anticipate how loud it would actually be during the showcase. Either way, I was able to present my project to everyone who came by, so I’m glad it worked out.
I wanted to use the instrument that Gopika and I made for one of the earlier assignments in a creative way. Given that I had an instrument that people could play, I decided to make a project where people could choose a song that they wanted to hear, and then play that song on the instrument.
I had originally planned to have the only sound come from the Piezo buzzer, however the Professor told me about a program called Sforzando, which is a software which allows the sound of musical instruments to be played from a computer. I coded four songs that I wanted to be played from the computer in Processing, and connected that to Sforzando, allowing the songs to sound much better than if they had been played on the Piezo buzzer.
The serial communication happened between Processing and Arduino by lighting up LEDs depending on which note was being played in each song. These LEDs also lit up when a note was played on the instrument. The instrument itself used an Ultrasonic Distance Sensor, and had 10cm ranges that mapped on to a specific note, which would then be played from the Piezo buzzer.
Videos:
Reflections:
There were some aspects of the project that I wish I could have improved, but either didn’t have the time or the knowledge to.
The first is that I would have requested to use speakers, rather than my computer speakers. I hadn’t taken into account that the ambient noise of the show would drown out even the loudest volume on my computer, which had always been too loud in tests. The result was that it could be hard to pick out the melody that was being played from the computer.
I wanted to play instrument through Sforzando rather than the Piezo buzzer, as the buzzer doesn’t sound as good and was quieter than the computer speakers. Unfortunately, despite spending hours trying to figure it out, I just couldn’t get this type of serial communication to work in this direction. I had to settle for the buzzer in order to finish the rest of the project in time for the show.
The last major improvement would have been to have multiple octaves, rather than just one. Unfortunately this would have required more space and would have been inconvenient to play, so I settled on just the one octave. I also couldn’t add flat or sharp notes, as I had a limited amount of space and decreasing the distance that counts as a note below about 10 cm makes the instrument much harder to play.
Create a physically interactive system of your choice that relies on a multimedia computer for some sort of processing or data analysis. The Final should use BOTH Processing AND Arduino.Your focus should be on careful and timely sensing of the relevant actions of the person or people that you’re designing this for, and on clear, prompt, and effective responses. Any interactive system is going to involve systems of listening, thinking, and speaking from both parties. Whether it involves one cycle or many, the exchange should be engaging. You may work alone or in pairs.
Description of the Projects:
This game is a typical 90s arcade space shooter game where you control a space ship and shoot bullets at the asteroid to protect yourself. In this version of the game, instead of using the basic key controls, the player uses the motions of the hand to control the space ship and shoot bullets at the asteroid.
Process:
The command for the motion of the ship is given via the ultrasonic sensor.The distance between the players hand and the ultrasonic sensor determines the x-coordinate of the ship in the game. I used the to detect the change in light when the player closes his/her fist to shoot bullets. I mapped the sensor reading from 0 to 1023, to 0 to 10. Then I divided the readings into 2. When the photoresistor read above or equal to 5, the bullets would shoot, and the red light would light up, otherwise the blue led would light up indicating that the value is below 5.
For the processing , I created stars and asteroid in a loop as a separate function by putting them in an Array with random values across the width of the screen. The values for the bullets too are kept in an Array, but their x coordinate starts from the x-coordinate of the ship +100 (center of the ship) and the same as y -coordinate of the ship . For the spaceship, I used the following picture, resized to 200 by 100.
Here is also a picture of the overall set up:
Video:
Code:
Arduino:
int left = 0;
int right = 0;
//int buzzer = 5;
const int echoPin = 2; // attach pin D2 Arduino to pin Echo of HC-SR04
const int trigPin = 3; //attach pin D3 Arduino to pin Trig of HC-SR04
const int led_red = 9;
const int led_blue = 8;
const int photo = A0;
// defines variables
long duration; // variable for the duration of sound wave travel
int distance; // variable for the distance measurement
void setup() {
Serial.begin(9600);
Serial.println("0,0");
// pinMode(buzzer, OUTPUT);
pinMode(trigPin, OUTPUT); // Sets the trigPin as an OUTPUT
pinMode (led_red, OUTPUT);
pinMode(led_blue, OUTPUT);
pinMode(echoPin, INPUT); // Sets the echoPin as an INPUT
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
// Calculating the distance
distance = duration * 0.034 / 2; // Speed of sound wave divided by 2 (go and back)
int constrained_distance = constrain(distance, 0, 30); //constraining distance to be detected
int mapped_distance = map(constrained_distance, 0, 30, 0, 1300); // mapping the values as per convinience
int light = analogRead(photo); // reading values from photoresistor
int mapped_light = map(light, 0, 1023, 0, 10); //mapping the values from photoresistor as per convinience
int constrained_light = constrain(light, 900, 1000);
int value = 0;
int numReadings = 10;
for (int i = 0; i < numReadings; i++) {
value = value + mapped_distance;
delay(1);
}
int selected_value = value / numReadings; //taking avg of numbers in group of 10
int avg_value = selected_value / 4; //taking average of the above number
//establishing communication between processing and arduino
while (Serial.available()) {
right = Serial.parseInt();
left = Serial.parseInt();
if (Serial.read() == '\n') {
Serial.print(avg_value);
Serial.print(",");
Serial.println(mapped_light);
}
}
//condition for led lights to switch on and off
if (mapped_light <= 5) {
digitalWrite(led_red, HIGH); // turn the LED on
digitalWrite(led_blue, LOW);
} else {
digitalWrite(led_red, LOW); // otherwise turn it off
digitalWrite(led_blue, HIGH);
}
}
}
Processing:
import processing.serial.*;
import processing.sound.*;
Serial myPort;
SoundFile shoot;
SoundFile crash;
PImage ship;
int xPos=width/2;
int yPos=0;
float Ship;
ArrayList<Star> stars = new ArrayList<Star>(); //array for stars
int frequency = 4; //frequency of star
ArrayList<Asteroid> asteroids = new ArrayList<Asteroid>(); //array for asteroid
int asteroidFrequency = 25; //frequency of ateroid
ArrayList<Bullet> bullets = new ArrayList<Bullet>(); //array for bullets
int points;
boolean instruct = true;
EndScene end;
void setup() {
//establishing connection with Arduino
String portname=Serial.list()[4];
//println(portname);
myPort = new Serial(this, portname, 9600);
myPort.clear();
myPort.bufferUntil('\n');
fullScreen();
points = 0;
ship = loadImage("spaceplane.png"); //loading image
shoot = new SoundFile(this, "lazershoot_sound.wav");// shoot sound
crash = new SoundFile(this, "space_crash.mp3");//crash sound
}
void draw() {
if (end != null) {
end.drawEndScene();
} else {
background(0);
drawStar();
serialEvent(myPort);
fill(255);
image(ship, xPos, height - height/4, 200, 100);
drawBullet();
if (instruct == true) {
StartScene();
} else {
drawAsteroid();
fill(255, 0, 0);
stroke(255);
stroke(255);
fill(255);
textSize(30);
text("Points: " + points, 50, 50);
checkCollision();
}
}
}
void drawBullet() {
for (int i = 0; i<bullets.size(); i++) {
bullets.get(i).drawBullet();
}
}
void checkCollision() {
for (int i = 0; i < asteroids.size(); i++) {
Asteroid a = asteroids.get(i);
float tan_shoot = 10*tan(60);
float distance_ship= dist(a.x, a.y, xPos, (height-height/4)-tan_shoot);
float distance_shipleft= dist(a.x, a.y, xPos+200, (height-height/4)+tan_shoot);
if (distance_ship< a.size/2 +tan_shoot) {
crash.play();
end = new EndScene();
}
if (distance_shipleft<a.size/2+tan_shoot){
crash.play();
end = new EndScene();
}
for (int b = 0; b < bullets.size(); b++) {
Bullet bullet = bullets.get(b);
if (a.checkCollision(bullet) == true) {
points++;
asteroids.remove(a);
bullets.remove(b);
}
}
}
}
void drawAsteroid() {
if (frameCount % asteroidFrequency == 0) {
asteroids.add(new Asteroid(random(100, 250)));
}
for (int i = 0; i<asteroids.size(); i++) {
Asteroid currentAsteroid = asteroids.get(i);
currentAsteroid.drawAsteroid();
if (currentAsteroid.y > height + currentAsteroid.size) {
asteroids.remove(currentAsteroid);
i--;
points--;
}
}
}
void drawStar() {
strokeWeight(1);
stroke(255);
if (frameCount % frequency == 0) {
Star myStar = new Star();
stars.add(myStar);
}
for (int i = 0; i<stars.size(); i++) {
Star currentStar = stars.get(i);
currentStar.drawStar();
}
}
void serialEvent(Serial myPort) {
loop();
//frameRate(10);
String s=myPort.readStringUntil('\n');
s=trim(s);
if (s!=null) {
println(s);
int values[]=int(split(s, ','));
if (values.length==2 ) {
//if (values[0]>=0) {
xPos=(int)map(values[0], 0, 325, 0, width);
//println(values[0]);
println(xPos);
if (xPos < 0) {
xPos = 0;
//}
}
if (xPos > width) {
xPos = width;
}
}
if ( values[1] <= 5) {
Bullet b = new Bullet();
bullets.add(b);
shoot.amp(0.4);
//shoot.play();
}
}
myPort.write("0"+"\n");
}
void mousePressed() {
if (end != null && end.mouseOverButton() == true) {
resetGame();
}
}
void resetGame() {
instruct= true;
stars.clear();
bullets.clear();
asteroids.clear();
end = null;
points = 0;
}
void StartScene() {
strokeWeight(7);
stroke(0);
fill(255);
textSize(30);
textAlign(CENTER);
text("Try to move you hand within the taped area on the table", width/2, height/2-100);
text(" Try opening and closing your hand completely to shoot bullets", width/2, height/2);
text("PRESS SHIFT TO PLAY", width/2, height/2+100);
if (keyPressed) {
if (keyCode == SHIFT) {
instruct = false;
}
}
}
class Asteroid {
float size;
float x;
float y;
int num;
int speed_asteroid = 10; //speed of asteroid
Asteroid(float size) {
this.size = size;
this.x = random(width);
this.y = 0;
}
void drawAsteroid() {
fill(150);
stroke(150);
ellipse(x, y, size, size);
y+=speed_asteroid;
}
boolean checkCollision( Object crash) {
if (crash instanceof Bullet) {
Bullet bullet = (Bullet) crash;
float distance_bullet = dist(x, y, bullet.x, bullet.y);
if (distance_bullet <= size + bullet.size/2 ) {
//fill(0, 255, 0, 100);
//rect(0, 0, width, height);
fill(255);
return true;
}
}
return false;
}
}
class Bullet {
float x;
float y;
float velocity_y;
float size;
Bullet() {
this.x = xPos;
this.y = (height-height/4) - 15;
this.velocity_y = -10;
this.size = 10;
}
void drawBullet() {
fill(255);
ellipse(x+100, y, size, size);
y +=velocity_y;
}
}
class EndScene {
int button_x;
int button_y;
int button_width;
int button_height;
EndScene() {
this.button_width = 300;
this.button_height = 100;
this.button_x = width/2 - this.button_width/2;
this.button_y = height/2 - this.button_height/2;
}
void drawEndScene() {
//Background
fill(#FAE714);
rect(0, 0, width, height);
rect(button_x, button_y, button_width, button_height);
//Game over text
stroke(0);
fill(0);
textSize(150);
textAlign(CENTER);
text("GAME OVER!", width/2, height/4);
//Restart Button
fill(0);
stroke(200);
rect(button_x, button_y, button_width, button_height);
fill(200);
textSize(60);
textAlign(CENTER);
text("RETRY?", button_x+150, button_y+70);
//Player Score
stroke(0);
fill(0);
textSize(80);
textAlign(CENTER);
text("FINAL SCORE: " + points, width/2, height - height/4);
}
boolean mouseOverButton() {
return (mouseX > button_x
&& mouseX < button_x + button_width
&& mouseY > button_y
&& mouseY < button_y + button_height);
}
}
class Star {
float x;
float y;
int velocity_star;
Star() {
this.x = random(width);
this.y = 0;
this.velocity_star = 10; //velocity of falling star
}
void drawStar() {
y+=velocity_star;
fill(#FCEB24);
circle(x,y,5);
}
}
First, the player should toss the coin into the coin pusher, which will start the game. Then the player uses the joystick to move the claw left or right, and the button to make the claw go down for the toys. I also added sound effects for different moves, so that it’s more interesting.
There is also a hidden prize which only three lucky persons today got it. Some people really wanted to get the prize and tried so hard at it — almost grabbed every toy.
Challenges
The coding part on Processing is quite challenging, and I spent quite some time on the física package so that I know how it works. One of the difficulties I encounter was the part where the claw goes down to grab, then drops it and finally returns. It was such a headache to figure out how to make the claw know if it touches a toy or not. I solved this by setting another física object on the tip of the claw to detect physical collision. I also used many booleans to make this small animation.
The edge setting was also tricky. I have to make sure that the claw doesn’t go out of the screen or works weirdly. To avoid those rule-breaking people who wants to see what happens if the claw goes down in the drop box — I set it that the claw will just head back instead of going down. And if the claw doesn’t catch anything, as it touches the edge, it will automatically go back.
When it came to the Arduino part, the coding was much easier, and the physical setting wasn’t that complicated. The most hard part was the coin pusher, I had to glue the distance to the box really tight to make sure it measures the distance correctly.
But the joystick was really hard to control — the numbers it gave off were pretty random at first, but after I soldered it, I found the patterns of the numbers although it wasn’t really stable. Also, the cables and the distance sensor often fell off, and the port sometimes got disconnected.
Right before the IMA show, the port got disconnected and some wires fell off. Fortunately, during the entire show, everything worked perfectly and didn’t crash, which was amazing and surprising as well!
Conclusion
It was such an amazing journey of learning Processing and Arduino, and connecting them through serial communication to produce such a complex project. I could never expect to do something like this at the beginning of this semester.
I want to thank the professor for being supportive and giving great suggestions. Thank you for pointing out the problems with the machine and helping me fix them before the show, so that they all worked great for the entire time. I feel sorry for not being able to let you play the perfect version 🙁
Thank you all for this fabulous course! Everyone’s projects are so creative and interesting! It was pleasant to work with everyone :))
CODE
__________________
Arduino:
int joyX = A0;
int joyY = A1;
int button = 4;
int button_state;
const int trigPin = 7;
const int echoPin = 6;
int duration = 0;
int dist = 0;
int prize=0;
int red = 10;
int yellow = 9;
int blue = 8;
int posX;
int posY;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("0,0,0,0");
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(button, OUTPUT);
pinMode(trigPin , OUTPUT);
pinMode(echoPin , INPUT);
pinMode(red, OUTPUT);
pinMode(yellow , OUTPUT);
pinMode(blue , OUTPUT);
pinMode(2,INPUT);
digitalWrite(2,HIGH);
}
void loop() {
// put your main code here, to run repeatedly:
// int posX = analogRead(joyX);
// int posY = analogRead(joyY);
// Serial.println(posY);
// Serial.println(posX);
digitalWrite(red,prize);
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(2);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
dist = duration/58.2;
// Serial.println(dist);
while (Serial.available()) {
prize = Serial.parseInt();
if (Serial.read() == '\n'){
button_state = digitalRead(button);
// move 0
posX = analogRead(joyX);
posY = analogRead(joyY);
if (posX >= 550) {
Serial.print(1);
} else {
Serial.print(0);
}
Serial.print(",");
// move 1
if (posX <= 360) {
Serial.print(1);
} else {
Serial.print(0);
}
Serial.print(",");
// grab 2
if (button_state == 0) {
Serial.print(0);
} else {
Serial.print(1);
}
Serial.print(",");
// coin pusher 3
if ( dist <= 5 )
{
Serial.println(1);
} else {
Serial.println(0);
}
digitalWrite(red,prize);
digitalWrite(yellow,prize);
digitalWrite(blue,prize);
}
}
}
Processing:
import fisica.*;
import processing.serial.*;
import processing.sound.*;
Serial myPort;
PImage claw;
PImage[][] claws;
PImage bird;
PImage[][] birds;
PImage bg;
boolean start = false;
boolean grab = false;
boolean get = false;
boolean drop = false;
boolean ret = false;
boolean prize = false;
boolean coin = false;
boolean right = false;
boolean left = false;
float speed;
float posX=20, posY=10;
FCircle[] bodies;
ArrayList<FBody> touching = new ArrayList<FBody>();
FCircle tip;
FBody prev_touch;
FWorld world;
int toy_num = 30;
SoundFile grab_sound;
SoundFile drop_sound;
SoundFile prize_sound;
SoundFile start_sound;
void setup() {
fullScreen();
size(750,600);
printArray(Serial.list());
String portname=Serial.list()[1];
println(portname);
myPort = new Serial(this,portname,9600);
myPort.clear();
myPort.bufferUntil('\n');
//
bg = loadImage("bg.jpeg");
bg.resize(displayWidth, displayHeight);
//bg.resize(750, 600);
translate((displayWidth-750)/2, (displayHeight-600)/2+100);
generate_claw();
Fisica.init(this);
world = new FWorld();
world.setEdges(0, 0, 750, 530, 255);
generate_birds();
tip = new FCircle(35);
tip.setFill(0);
tip.setStatic(true);
tip.setDrawable(false);
world.add(tip);
grab_sound = new SoundFile(this, "pick_up.wav");
drop_sound = new SoundFile(this, "drop.wav");
prize_sound = new SoundFile(this, "prize.wav");
start_sound = new SoundFile(this, "coin.wav");
}
void draw() {
//println(mouseX,mouseY);
background(bg);
translate((displayWidth-750)/2, (displayHeight-600)/2+100);
world.step();
world.draw();
// box
fill(255, 200);
noStroke();
rect(603, 250, 200, 280);
strokeWeight(3);
stroke(0);
line(603, 250, 605, 530);
if (coin) {
start_sound.play();
coin = false;
}
if (start){
initialize_claw();
} else {
fill(#A09B10);
PFont f = createFont("Dialog-48", 40);
textFont(f);
text("SWIPE a coin to START", 145, -100);
fill(#671235);
text("SWIPE a coin to START", 140, -100);
}
}
void initialize_claw() {
if (posX >= 750 - 80) {
posX = 750-80;
}
if (posX <= 20) {
posX = 20;
}
if (!grab) {
if (ret) {
if (posX > 20) {
image(claws[0][0], posX, 10);
posX -=3;
if (prize) {
fill(#901758);
text("CONGRATULATIONS!!!", 150, 100);
text("You are so LUCKY to get an AWARD", 80, 100);
}
} else {
ret = false;
world.remove(prev_touch);
prize = false;
}
} else {
image(claws[0][0], posX, 10);
if (right) {
posX += speed;
}
if (left) {
posX -= speed;
}
}
} else {
if (posX >= 550 && !get) {
grab = false;
get = false;
ret = true;
} else {
claw_down();
if (posY<0) {
posY+=3;
}
if (!drop) {
FBody toy = world.getBody(posX+60, posY+80);
if (get && posY < 20) {
image(claws[6][0], posX, posY);
if (posX <= 598) {
posX += 3;
toy.setPosition(posX+60, posY+80);
} else {
drop = true;
drop_sound.play();
toy.setStatic(false);
grab = false;
get = false;
drop = false;
ret = true;
// prize
int ram = (int)random(100);
if (ram <= 1) {
prize = true;
prize_sound.play();
}
}
}
}
}
}
}
void claw_down() {
FBody touch;
tip.setPosition(posX+40, posY+90);
if (posY < 500 && posY > 0) {
touching = tip.getTouching();
if (get) {
image(claws[6][0], posX, posY);
posY -= 3;
if (touching.size()>0) {
touch = touching.get(0);
prev_touch = touch;
touch.setStatic(true);
touch.setPosition(posX+60, posY+80);
}
} else{
image(claws[7][0], posX, posY);
if (touching.size()>0) {
get = true;
grab_sound.play();
} else {
posY += 3;
if (posY >= 500) {
grab = false;
get = false;
ret = true;
}
}
}
}
}
void generate_claw() {
claw = loadImage("claw.png");
claws = new PImage[8][5];
int w = claw.width / 5;
int h = claw.height / 8;
for (int y=0; y < 8; y++) {
for (int x=0; x< 5; x++) {
claws[y][x] = claw.get(x*w, y*h, w, h);
claws[y][x].resize(100,120);
}
}
}
void generate_birds() {
bodies = new FCircle[30];
bird = loadImage("bird.png");
birds = new PImage[2][3];
int w1 = bird.width / 3;
int h1 = bird.height / 2;
for (int y=0; y < 2; y++) {
for (int x=0; x< 3; x++) {
birds[y][x] = bird.get(x*w1, y*h1, w1, h1);
birds[y][x].resize(85, 85);
}
}
for (int i=0; i<toy_num; i++) {
int radius = 50;
FCircle b = new FCircle(radius);
b.setPosition(random(100, 500), height/2);
b.setStroke(0);
b.setStrokeWeight(2);
b.setDensity(radius / 10);
int random_x = int(random(0,2));
int random_y = int(random(0,3));
b.attachImage(birds[random_x][random_y]);
world.add(b);
bodies[i] = b;
}
FBox edge = new FBox(10, 300);
edge.setPosition(600, 400);
edge.setStatic(true);
world.add(edge);
}
void mousePressed() {
// if button pressed, reset
world.clear();
grab = false;
ret = false;
get = false;
drop = false;
start = false;
bg = loadImage("bg.jpeg");
bg.resize(displayWidth, displayHeight);
translate((displayWidth-750)/2, (displayHeight-600)/2+100);
generate_claw();
Fisica.init(this);
world = new FWorld();
world.setEdges(0, 0, 750, 530, 255);
generate_birds();
tip = new FCircle(35);
tip.setFill(0);
tip.setStatic(true);
tip.setDrawable(false);
world.add(tip);
posX = 20;
posY = 10;
initialize_claw();
}
void serialEvent(Serial myPort){
String s=myPort.readStringUntil('\n');
s=trim(s);
if (s!=null){
println(s);
int values[]=int(split(s,','));
if (values.length>=1){
if (values[3] == 1) {
coin = true;
start = true;
}
if (values[0] == 1 && start) {
left = false;
right = true;
} else {
right = false;
}
if (values[1] == 1 && start) {
right = false;
left = true;
} else {
left = false;
}
if (values[2] == 1 && start) {
grab = true;
}
}
}
myPort.write(int(prize)+"\n");
}
Create a physically interactive system of your choice that relies on a multimedia computer for some sort of processing or data analysis. The Final should use BOTH Processing AND Arduino. Your focus should be on careful and timely sensing of the relevant actions of the person or people that you’re designing this for, and on clear, prompt, and effective responses. Any interactive system is going to involve systems of listening, thinking, and speaking from both parties. Whether it involves one cycle or many, the exchange should be engaging. You may work alone or in pairs.
Project
I started this project by working with a pulse sensor without thinking about what I would end up with. I progressed by displaying a heartbeat on the screen by using the data I received. I also wanted to work with LEDs, I attached the LEDs with the breadboard. I used two LEDs that turn on and off according to the pulse rate. I also checked the timings between heartbeats by subtracting the last-current, the variable I have used is TBH(time between heartbeats). I also made the heart pump by increasing the stroke weight. You can also use “S” or “s” to save the heartbeat in the folder where the code is saved and later you can compare the heartbeat. To make the pulse rate monitor more interesting, I added some questions to the screen to see how the heartbeat changed with respect to the question, which can be used as a lie detection test. If someone wanted to reset their heartbeats, they can press ‘r’ to reset it, and the monitor displays no heartbeat until some other person uses the sensor. For this project, I used two sensors, out of which one had technical problems. So, I couldn’t use that. But, the idea was that I would compare two people’s heartbeats by asking them questions.
I took some shots during the exhibition of people testing out the monitor and playing the yes/no question game.
The LEDs were covered with a white box that looked like an artificial heart because it would beat every time your pulse was beating. The circuit looked like this:
Certain challenges:-
It took me some time to figure out how the sensor worked because I have never worked with a pulse sensor before.
it was hard to display the pulse wave first, then I found some resources to make me understand the concept.
it was hard to add further things to the project to make it more useful.
The code is following:-
Arduino
#define USE_ARDUINO_INTERRUPTS true
#include <PulseSensorPlayground.h>
const int OUTPUT_TYPE = PROCESSING_VISUALIZER;
const int PULSE_SENSOR_COUNT = 2;
const int PULSE_INPUT0 = A0;
const int PULSE_BLINK0 = 13; //led
const int PULSE_FADE0 = 5;
const int PULSE_INPUT1 = A1;
const int PULSE_BLINK1 = 12;
const int PULSE_FADE1 = 11;
const int THRESHOLD = 550; // to avoid noise when idle
PulseSensorPlayground pulseSensor(PULSE_SENSOR_COUNT);
void setup() {
Serial.begin(250000);
pulseSensor.analogInput(PULSE_INPUT0, 0);
pulseSensor.blinkOnPulse(PULSE_BLINK0, 0);
pulseSensor.fadeOnPulse(PULSE_FADE0, 0);
pulseSensor.analogInput(PULSE_INPUT1, 1);
pulseSensor.blinkOnPulse(PULSE_BLINK1, 1);
pulseSensor.fadeOnPulse(PULSE_FADE1, 1);
pulseSensor.setSerial(Serial);
pulseSensor.setOutputType(OUTPUT_TYPE);
pulseSensor.setThreshold(THRESHOLD);
if (!pulseSensor.begin()) {
for (;;) {
digitalWrite(PULSE_BLINK0, LOW);
delay(50);
digitalWrite(PULSE_BLINK0, HIGH);
delay(50);
}
}
}
void loop() {
delay(20);
pulseSensor.outputSample();
for (int i = 0; i < PULSE_SENSOR_COUNT; ++i) {
if (pulseSensor.sawStartOfBeat(i)) {
pulseSensor.outputBeat(i);
}
}
}
Processing
import processing.sound.*;
SoundFile file;
import processing.serial.*;
PFont font;
Serial port;
int numSensors = 2; //variable that holds number of sensors
//the varible given below holds certain information
int[] Sensor;
int[] TBH;
int[] BPM;
int[][] RawPPG;
int[][] ScaledPPG;
int[][] ScaledBPM;
float offset;
color bgcolor = color(171,219,227);
int heart[]; //when timing the pulse
//determine the size
int pulseWidth;
int pulseHeight;
int pulseX;
int pulseY[];
int bpmWidth;
int bpmHeight;
int bpmX;
int bpmY[];
int spacer = 10;
boolean beat[]; //boolean to check if heart beat detected
//find serial port
String serialPort;
String[] serialPorts = new String[Serial.list().length];
boolean serialPortFound = false;
Radio[] button = new Radio[Serial.list().length*2];
int numPorts = serialPorts.length;
boolean refreshPorts = false;
int duration = 1000;
int pressTime;
void setup() {
fullScreen();
frameRate(100);
font = loadFont("Arial-BoldMT-24.vlw");
file = new SoundFile(this, "heart.mp3");
textFont(font);
textAlign(CENTER);
rectMode(CORNER);
ellipseMode(CENTER);
pulseWidth = width-520;
pulseHeight = 1080/numSensors;
pulseX = 10;
pulseY = new int [numSensors];
for(int i=0; i<numSensors; i++){
pulseY[i] = 43 + (pulseHeight * i);
if(i > 0) pulseY[i]+=spacer*i;
}
bpmWidth = 300;
bpmHeight = pulseHeight;
bpmX = pulseX + pulseWidth + 10;
bpmY = new int [numSensors];
for(int i=0; i<numSensors; i++){
bpmY[i] = 43 + (bpmHeight * i);
if(i > 0) bpmY[i]+=spacer*i;
}
heart = new int[numSensors];
beat = new boolean[numSensors];
// Data Variables Setup
Sensor = new int[numSensors];
TBH = new int[numSensors];
BPM = new int[numSensors];
RawPPG = new int[numSensors][pulseWidth];
ScaledPPG = new int[numSensors][pulseWidth];
ScaledBPM = new int [numSensors][bpmWidth];
//setting lines to 0
resetDataTraces();
background(0);
noStroke();
drawDataWindows();
drawHeart();
fill(bgcolor);
text("Select Your Serial Port",245,30);
listAvailablePorts();
}
void draw() {
if(serialPortFound){
// run only when port connected
background(0);
drawDataWindows();
drawPulseWaveform();
drawBpmWave();
drawHeart();
printDataToScreen();
if(spacePressed){
text("The following game involves Yes/No questions.\n Press 1 to view the first question.", 670, 500);
}
if(onePressed){
text("Are you happy with your life?", 700, 500);
}
if(twoPressed){
text("Have you ever truly been in love?", 700, 500);
}
if(threePressed){
text("Do you think you're successful in life?", 700, 500);
}
if(fourPressed){
text("Have you ever done something unforgivable?", 750, 500);
}
if(fivePressed){
text("Would you save your mother over 17 other random people?", 700, 500);
}
if(sixPressed){
text("Are you a good person?", 700, 500);
}
if(sevenPressed){
text("If you died tomorrow, would you have regrets?", 750, 500);
}
} else {
autoScanPorts(); //scan to find port
if(refreshPorts){
refreshPorts = false;
drawDataWindows();
drawHeart();
listAvailablePorts();
}
for(int i=0; i<numPorts+1; i++){
button[i].overRadio(mouseX,mouseY);
button[i].displayRadio();
}
}
}
void drawDataWindows(){
noStroke();
fill(bgcolor);
for(int i=0; i<numSensors; i++){
rect(pulseX, pulseY[i], pulseWidth, pulseHeight);
rect(bpmX, bpmY[i], bpmWidth, bpmHeight);
}
}
void drawPulseWaveform(){
for (int i=0; i<numSensors; i++) {
RawPPG[i][pulseWidth-1] = (1023 - Sensor[i]);
for (int j = 0; j < pulseWidth-1; j++) {
RawPPG[i][j] = RawPPG[i][j+1];
float dummy = RawPPG[i][j] * 0.625/numSensors;
offset = float(pulseY[i]);
ScaledPPG[i][j] = int(dummy) + int(offset);
}
stroke(250, 0, 0);
noFill();
beginShape();
for (int x = 1; x < pulseWidth-1; x++) {
vertex(x+10, ScaledPPG[i][x]);
}
endShape();
}
}
void drawBpmWave(){
//draw bpm wave
for (int i=0; i<numSensors; i++) {
if (beat[i] == true) {
file.play();
beat[i] = false;
for (int j=0; j<bpmWidth-1; j++) {
ScaledBPM[i][j] = ScaledBPM[i][j+1];
}
BPM[i] = constrain(BPM[i], 0, 200); // limit the highest BPM value to 200
float dummy = map(BPM[i], 0, 200, bpmY[i]+bpmHeight, bpmY[i]);
ScaledBPM[i][bpmWidth-1] = int(dummy);
}
}
// graph heart rate in small window
stroke(250, 0, 0); // color of heart rate graph
strokeWeight(2); // thicker line is easier to read
noFill();
for (int i=0; i<numSensors; i++) {
beginShape();
for (int j=0; j < bpmWidth; j++) {
vertex(j+bpmX, ScaledBPM[i][j]);
}
endShape();
}
}
void drawHeart(){
//make the heart and make it beat
fill(250,0,0);
stroke(250,0,0);
int bezierZero = 0;
for(int i=0; i<numSensors; i++){
heart[i]--;
heart[i] = max(heart[i], 0);
if (heart[i] > 0) {
strokeWeight(8);
}
smooth(); //draw hearts
bezier(width-100, bezierZero+70, width-20, bezierZero, width, bezierZero+160, width-100, bezierZero+170);
bezier(width-100, bezierZero+70, width-190, bezierZero, width-200, bezierZero+160, width-100, bezierZero+170);
strokeWeight(1); // reset the strokeWeight for next time
bezierZero += bpmHeight+spacer;
}
}
void listAvailablePorts(){
//println(Serial.list());
serialPorts = Serial.list();
fill(0);
textFont(font,16);
textAlign(LEFT);
int yPos = 0;
for(int i=numPorts-1; i>=0; i--){
button[i] = new Radio(35, 95+(yPos*20),12,color(180),color(80),color(255),i,button);
text(serialPorts[i],50, 100+(yPos*20));
yPos++;
}
int p = numPorts;
fill(233,0,0);
button[p] = new Radio(35, 95+(yPos*20),12,color(180),color(80),color(255),p,button);
text("Refresh Serial Ports List",50, 100+(yPos*20));
textFont(font);
textAlign(CENTER);
}
void autoScanPorts(){
if(Serial.list().length != numPorts){
if(Serial.list().length > numPorts){
println("New Ports Opened!");
int diff = Serial.list().length - numPorts;
serialPorts = expand(serialPorts,diff);
numPorts = Serial.list().length;
}else if(Serial.list().length < numPorts){
println("Some Ports Closed!");
numPorts = Serial.list().length;
}
refreshPorts = true;
return;
}
}
void resetDataTraces(){
for (int i=0; i<numSensors; i++) {
BPM[i] = 0;
for(int j=0; j<bpmWidth; j++){
ScaledBPM[i][j] = bpmY[i] + bpmHeight;
}
}
for (int i=0; i<numSensors; i++) {
Sensor[i] = 512;
for (int j=0; j<pulseWidth; j++) {
RawPPG[i][j] = 1024 - Sensor[i];
}
}
}
void printDataToScreen(){
fill(255);
text("Pulse Sensor to compare heart beats", 245, 30);
for (int i=0; i<numSensors; i++) {
text("Sensor " + (i+1), 1700, bpmY[i] + 220);
text(BPM[i] + " BPM", 1700, bpmY[i] +185);
text("TBH " + TBH[i] + "mS", 1700, bpmY[i] + 160);
}
}
class Radio {
int _x,_y;
int size, dotSize;
color baseColor, overColor, pressedColor;
boolean over, pressed;
int me;
Radio[] radios;
Radio(int xp, int yp, int s, color b, color o, color p, int m, Radio[] r) {
_x = xp;
_y = yp;
size = s;
dotSize = size - size/3;
baseColor = b;
overColor = o;
pressedColor = p;
radios = r;
me = m;
}
boolean pressRadio(float mx, float my){
if (dist(_x, _y, mx, my) < size/2){
pressed = true;
for(int i=0; i<numPorts+1; i++){
if(i != me){ radios[i].pressed = false; }
}
return true;
} else {
return false;
}
}
boolean overRadio(float mx, float my){
if (dist(_x, _y, mx, my) < size/2){
over = true;
for(int i=0; i<numPorts+1; i++){
if(i != me){ radios[i].over = false; }
}
return true;
} else {
over = false;
return false;
}
}
void displayRadio(){
noStroke();
fill(baseColor);
ellipse(_x,_y,size,size);
if(over){
fill(overColor);
ellipse(_x,_y,dotSize,dotSize);
}
if(pressed){
fill(pressedColor);
ellipse(_x,_y,dotSize,dotSize);
}
}
}
//understood some of these concepts throught additional information on web