week 14- final project

Concept

A physically interactive “Piano Tiles” game where players tap hand-buttons or pedal-buttons in time to falling/color coordinated tiles. Red tiles correspond to hand buttons (pins 2–5), blue tiles to foot pedals (pins 6–9). Finish the song without losing all three lives to win!

Interaction Demo

user testing:

copy_71E82669-1896-402A-A155-F76C8BE9E1AD

Me testing it:

copy_0FB66FA1-2527-4DBE-B6C6-86ACE6076571 2

Implementation Overview

Startup

-Menu screen with background image and “Start”/“Info” buttons

-Info screen explains with instructions on how to play

Song and Difficulty Selection

-Custom “song.png” and “difficulty.png” backgrounds

-3 levels (easy, medium, hard), as difficulty increased, speed of tiles moving down increases

Gameplay

-Tiles fall at a speed set by difficulty

-Serial data from Arduino (1–4 = hand zones, 5–8 = foot zones) drives hit detection

-Score and lives update live; MISS flashes when button misscliks/spam

Game Over

-Game over screen, shows score of the user: whether lost or won

-“Try Again” or “Home” appear and auto-returns to home screen after 5s

Interaction Design

Color coding: red/blue zones on screen match button colors

Audio cues: Crazy Frog sample plays in background

Lives and Score: heart icons and score button reinforce progress

Arduino Sketch

const int pins[] = {2,6,  3,7,  4,8,  5,9}; // even indices = hand, odd = foot

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < 8; i++) pinMode(pins[i], INPUT);
}

void loop() {
  for (int i = 0; i < 8; i++) {
    if (digitalRead(pins[i]) == HIGH) {
      int baseZone = (i / 2) + 1;          // 1–4
      int sendVal  = (i % 2 == 0)
                     ? baseZone           // hand: 1–4
                     : baseZone + 4;      // foot: 5–8

      Serial.println(sendVal);

      // wait release
      while (digitalRead(pins[i]) == HIGH) delay(5);
      delay(300);
    }
  }
}

This Arduino sketch  scans eight digital inputs (pins 2–9, paired as hand vs. foot buttons), and whenever it detects a button press it:

  1. Determines which of the four “zones” you pressed (buttons in pairs 2/6: zone 1, 3/7: zone 2, etc).

  2. Adds 4 to the zone number if it was a “foot” button, so hands send 1–4 and feet send 5–8.

  3. Sends that zone ID over the serial port.

  4. Waits for you to release the button and debounces for 300 ms before scanning again.

Circuit Schematic

p5.js Sketch

let menuImg, infoImg, gameOverImg;
let axelSound;           
let songStarted = false;

let serial, canvas, scoreBtn;
let gameState = 'menu';

let useSound = false; 
let osc;

function preload(){ //preloading images and sounds
  menuImg = loadImage('menu.png');
  infoImg  = loadImage('info.png');
  gameOverImg = loadImage('gameover.png');
  axelSound = loadSound('axelf.mov');
  songImg = loadImage('song.png');
  diffImg = loadImage('difficulty.png');

}



const tileH = 60, zoneH = 20,  
      zoneY = 320, // tiles get removed at this point
      removalY = 400, colWidth = 400 / 4;


// UI buttons for navigation buttons, menu and gameover
const navButtons = { back:{x:10,y:10,w:80,h:30,label:'Back'}, home:{x:100,y:10,w:80,h:30,label:'Home'} };

const menu = { title:'Piano Tiles', start:{x:159,y:222,w:86,h:20,label:'Start'}, info:{x:160,y:261,w:86,h:20,label:'Info'} };

const gameOverButtons = { tryAgain:{x:150,y:180,w:100,h:40,label:'Try Again'}, home:{x:150,y:240,w:100,h:40,label:'Home'} };

// Song and pattern deciding what foot/hand tile goes to what column in order
const songs = [
   { id: 'axelf', name: 'Crazy Frog', sample: axelSound, pattern: [
  { col: 0, isFoot: true  },  
  { col: 1, isFoot: true },  
  { col: 0, isFoot: true  },  
  { col: 2, isFoot: false  },  
  { col: 3, isFoot: false },
  { col: 3, isFoot: true  },  
  { col: 0, isFoot: false }, 
  { col: 2, isFoot: false  },  
  { col: 0, isFoot: true  },  
  { col: 2, isFoot: false }, 
  { col: 1, isFoot: true  }, 
  { col: 1, isFoot: false },  
  { col: 3, isFoot: true  },  
  { col: 1, isFoot: true  },  
  { col: 0, isFoot: false },
  { col: 3, isFoot: true  },  
  { col: 2, isFoot: false },  
  { col: 1, isFoot: true  }, 
  { col: 0, isFoot: true  },  
  { col: 3, isFoot: true },
  { col: 0, isFoot: true  },  
  { col: 1, isFoot: true },  
  { col: 0, isFoot: true  },  
  { col: 2, isFoot: false  },  
  { col: 3, isFoot: false },
  { col: 3, isFoot: true  },  
  { col: 0, isFoot: false },  
  { col: 2, isFoot: false  },  
  { col: 0, isFoot: true  },  
  { col: 2, isFoot: false }, 
  { col: 1, isFoot: true  },  
  { col: 1, isFoot: false },  
  { col: 3, isFoot: true  }, 
  { col: 1, isFoot: true  },  
  { col: 0, isFoot: false },
  { col: 3, isFoot: true  },  
  { col: 2, isFoot: false },  
  { col: 1, isFoot: true  },  
  { col: 0, isFoot: true  },  
  { col: 3, isFoot: true }, 
 { col: 3, isFoot: true  },  
  { col: 2, isFoot: false },  
  { col: 1, isFoot: true  },  
  { col: 0, isFoot: true  },  
  { col: 3, isFoot: true },
  { col: 3, isFoot: true  },  
  { col: 2, isFoot: false }
] }
];
const songBoxes = []; 
// speed increases as difficulty increases
const difficulties = [ {label:'Easy',speed:4}, {label:'Medium',speed:6}, {label:'Hard',speed:8} ];
const diffBoxes = [];


let currentSong, noteIndex=0;
let score=0, lives=3, currentSpeed=0; // set score to 0 and lives 3
let tile=null, missTime=0;
let gameOverStartTime=0, gameOverTimeout=5000;

function setup(){
    songs[0].sample = axelSound;

  canvas=createCanvas(400,400);
  canvas.mousePressed(handleMouse);
  canvas.elt.oncontextmenu=()=>false;

  // audio
  useSound = typeof p5.Oscillator==='function';
  if(useSound) osc=new p5.Oscillator('sine');

  // serial
  serial=createSerial();
  const ports=usedSerialPorts(); if(ports.length) serial.open(ports[0],{baudRate:9600});

  // UI boxes for the song
  songBoxes.push({ x: 129, y: 216, w: 145, h:  75, idx: 0 });

  // and for difficulties:
  diffBoxes.length = 0; 

  //levels
diffBoxes.push({ x: 158, y: 182, w:  86, h:  32, idx: 0 }); //easy
diffBoxes.push({ x: 158, y: 235, w:  86, h:  32, idx: 1 }); //med
diffBoxes.push({ x: 158, y: 289, w:  86, h:  32, idx: 2 }); //hard

  // white button that represents score
  scoreBtn=createButton('Score: 0');
  scoreBtn.position(width-90,10);
  scoreBtn.style('background-color','#FFFFFF').style('color','rgb(25,1,1)').style('padding','6px 12px');
}

function draw(){
  
    // console.log(`x: ${mouseX}, y: ${mouseY}`); used for coordinates for the ui boxes

  background(30);
  switch(gameState){
    case 'menu':            drawMenu();         break;
    case 'info':            drawInfo();         break;
    case 'songSelect':      drawSongSelect();   break;
    case 'difficultySelect':drawDiffSelect();   break;
    case 'playing':         drawGame();         break;
    case 'gameOver':        drawGameOver();     break;
    
  }
}

function drawMenu()
{ 

  textAlign(CENTER,CENTER);
  fill(255); textSize(32);
  text(menu.title, width/2, 100);
  drawButton(menu.start);
  drawButton(menu.info);
  
    // draw menu background image
  image(menuImg, 0, 0, width, height);
} 

function drawInfo() {
  // full screen info background
  image(infoImg, 0, 0, width, height);

  // then draw back button on top:
  drawButton(navButtons.back);
}

function drawSongSelect()

{ 
    image(songImg, 0, 0, width, height);

 
drawButton(navButtons.back); 
drawButton(navButtons.home); }

function drawDiffSelect()
{ 
    image(diffImg, 0, 0, width, height);
  //  // debug draw the clickable areas
  // diffBoxes.forEach(b => {
  //   noFill();
  //   stroke(255, 0, 0);
  //   rect(b.x, b.y, b.w, b.h);
  // });
drawButton(navButtons.back); drawButton(navButtons.home); }

function drawGame(){
  
  // Lives
  noStroke(); fill('red'); for(let i=0;i<lives;i++) ellipse(20+i*30,20,20,20);
// Dividers and zones in the game
rectMode(CORNER);
stroke(255); strokeWeight(4);
line(100,0,100,height);
line(200,0,200,height);
line(300,0,300,height);

noStroke();
// draws the 4 coloured hit zones:
const colors = ['#3cb44b','#4363d8','#ffe119','#e6194b'];
noStroke();
colors.forEach((c,i) => {
  fill(c);
  rect(i * colWidth, zoneY, colWidth, zoneH);
});

// draws the falling tiles
if (tile) {
  tile.y += tile.speed;

  if (tile.y - tileH/2 > removalY) {
    missTime = millis();
    advanceTile(false);
  } else {
    rectMode(CENTER);
    noStroke();
    fill(tile.isFoot ? '#4363d8' : '#e6194b');
    rect(tile.x, tile.y, tile.w, tileH);
  }
}




  if(serial && serial.available()>0){
    let raw=serial.readUntil('\n').trim(); let z=int(raw)-1;
    if(z>=0&&z<8){ let col=z%4; let isHand=z<4; handleHit(col,isHand); }
  }


  // keeps count of the score
  noStroke(); 
  fill(255); 
  textAlign(LEFT,TOP); 
  textSize(16); 
  text('Score:'+score,10,40);
  if(millis()-missTime<500){ textAlign(CENTER,CENTER); textSize(32); fill('red'); text('MISS',width/2,height/2); }
}

function startPlaying() {
  // only play once
  if (currentSong.sample && !songStarted) {
    currentSong.sample.play();
    songStarted = true;
  }
}


function drawGameOver()
{ 
  if (currentSong.sample && songStarted) {
  currentSong.sample.stop();
}
  image(gameOverImg, 0, 0, width, height);
 
 let e=millis()-gameOverStartTime; 
 if(e>=gameOverTimeout){ resetGame(); gameState='menu'; return;} let r=ceil((gameOverTimeout-e)/1000); 
 textAlign(RIGHT,BOTTOM); 
 textSize(14); 
 fill(255); 
 text('Menu in:'+r,width-10,height-10); }

function drawButton(b)
{ rectMode(CORNER);
 fill(100); 
 rect(b.x,b.y,b.w,b.h);
 fill(255); 
 textAlign(CENTER,CENTER);
 textSize(16); 
 text(b.label,b.x+b.w/2,b.y+b.h/2); }

function handleMouse() {
  // game over screen
  if (gameState === 'gameOver') {
    if (hitBox(gameOverButtons.tryAgain)) {
      resetGame();
      noteIndex = 0;
      spawnNextTile();
      // replay sample if any
      if (currentSong.sample) {
        let s = (typeof currentSong.sample === 'function')
                  ? currentSong.sample()
                  : currentSong.sample;
        if (s && typeof s.play === 'function') s.play();
      }
      gameState = 'playing';
    } else if (hitBox(gameOverButtons.home)) {
      resetGame();
      gameState = 'menu';
    }
    return;
  }

  // info screen
  if (gameState === 'info') {
    if (hitBox(navButtons.back)) {
      gameState = 'menu';
    }
    return;
  }

  // navigation (Home/Back) before the game starts
  if (gameState !== 'playing') {
    if (hitBox(navButtons.home)) {
      resetGame();
      gameState = 'menu';
      return;
    }
    if (gameState === 'songSelect' && hitBox(navButtons.back)) {
      gameState = 'menu';
      return;
    }
    if (gameState === 'difficultySelect' && hitBox(navButtons.back)) {
      gameState = 'songSelect';
      return;
    }
  }

  // main menu
  if (gameState === 'menu') {
    if (hitBox(menu.start)) {
      gameState = 'songSelect';
    } else if (hitBox(menu.info)) {
      gameState = 'info';
    }
    return;
  }

  if (gameState === 'songSelect') {
    songBoxes.forEach(b => {
      if (hitBox(b)) {
        currentSong = songs[b.idx];
        gameState = 'difficultySelect';
      }
    });
    return;
  }

  // select difficulty
if (gameState === 'difficultySelect') {
  diffBoxes.forEach(b => {
    if (hitBox(b)) {
      currentSpeed = difficulties[b.idx].speed;
      resetGame();
      noteIndex = 0;
      spawnNextTile();

      // play sample only once
      if (currentSong.sample && !songStarted) {
        currentSong.sample.play();
        songStarted = true;
      }

      gameState = 'playing';
    }
  });
  return;
}


}


function resetGame() {
  score = 0;
  scoreBtn.html('Score: 0');
  lives = 3;
  missTime = 0;
  gameOverStartTime = 0;
  tile = null;
  songStarted = false;   // reset song playing here
}


function handleHit(col, isHandButton) {
  // wrong button type
  if ((tile.isFoot && isHandButton) ||
      (!tile.isFoot && !isHandButton)) {
    missTime = millis();
    lives--;
    if (lives <= 0) {
      if (currentSong.sample) axelSound.stop();
      if (useSound)         osc.stop();
      gameState = 'gameOver';
      gameOverStartTime = millis();
    }
    return;
  }

  // otherwise the normal hit test
  if (tile && col === tile.col
      && tile.y - tileH/2 < zoneY + zoneH
      && tile.y + tileH/2 > zoneY) {
    advanceTile(true);
  } else {
    missTime = millis();
    lives--;
    if (lives <= 0) {
      if (currentSong.sample) axelSound.stop();
      if (useSound)         osc.stop();
      gameState = 'gameOver';
      gameOverStartTime = millis();
    }
  }
}




function advanceTile(sc) {
  if (sc) {
    // check win condition: if this was the last note in the pattern, flag a win and go straight to Game Over
    if (noteIndex === currentSong.pattern.length - 1) {
      isWinner = true; // mark winner
      gameState = 'gameOver';
      gameOverStartTime = millis();
      return;
    }

    if (useSound && !currentSong.sample) {
      osc.freq(currentSong.melody[noteIndex]);
      osc.start();
      osc.amp(0.5, 0.05);
      setTimeout(() => osc.amp(0, 0.5), 300);
    }

    score++;
    scoreBtn.html('Score:' + score);

  } else {
    lives--;
    if (lives <= 0) {
      // lose flag
      isWinner = false;
      if (currentSong.sample) axelSound.stop();
      if (useSound)         osc.stop();

      gameState = 'gameOver';
      gameOverStartTime = millis();
      return;
    }
  }

  // advance to the next note
  noteIndex = (noteIndex + 1) % currentSong.pattern.length;
  spawnNextTile();
}


function spawnNextTile() {

  const p = currentSong.pattern[noteIndex];
  tile = {
    x:    p.col * colWidth + colWidth/2,
    y:    0,
    w:    colWidth,
    h:    tileH,
    speed: currentSpeed,
    col:  p.col,
    isFoot: p.isFoot,
    immune: false
  };
}



function hitBox(b){ return mouseX>=b.x&&mouseX<=b.x+b.w&&mouseY>=b.y&&mouseY<=b.y+b.h; }



 

Key p5.js Highlights

-preload() loads menu.png, info.png, gameoverwinner.png, gameoverloser.png, and axelf.mov

-drawMenu()/drawInfo() render full screen images before buttons

-handleMouse() manages navigation 

-drawGame() reads serial, falls and draws tiles, enforces hit logic

-advanceTile() checks win/lose and plays audio

Arduino and p5.js Communication

-Arduino sends an integer (1–8) on each button press

-p5.js reads via serial.readUntil(‘\n’), maps zone%4 to columns and zone<5 to hand/foot

-Immediate feedback loop: tile hit/miss on-screen mirrors physical input

Proud of

i’m proud of how I designed my pedals to work so that it holds the button right under the top part of the pedal, and it doesn’t activate till stepped on it. I’m also proud of how I soldered the wires together since it’s my first time. I also like how my code aesthetics turned out.

Future Improvements

I was thinking of maybe trying to have a multiplayer mode would be cooler: split-screen hand vs. foot competition, and maybe implementing a leaderboard function: save high scores.

week 13- user test

Overall: All navigated through the Start, Song, Difficulty successfully and began playing within 30 s.

copy_71E82669-1896-402A-A155-F76C8BE9E1AD

Confusion points:

the hit zones being the same colors as the hand buttons, they think the colors of the tile corresponds to the ‘color’ of the hand button.

It was avoided when people actually read the instructions page.

What worked well:

Physical button feedback, Audio cues, Lives (heart icons) and score counter provided clear feedback.

Areas to improve:

Add visual cues for “red = hand” and “blue = foot” like an icon for each tile

week 12

Piano Tiles: Hand and Foot Rhythm Game

1. Concept

I’m building a two-input rhythm game that connects an Arduino button  (4 hand + 4 foot buttons) with a p5.js “Piano Tiles”

Goal: Hit falling tiles in time to the “Crazy Frog” melody.
Win: Complete the entire pattern before running out of lives.
Lose: Lose all 3 lives by hitting the wrong side (hand vs foot) or missing too many tiles.

2. Arduino to p5 Communication

-Inputs and Outputs

Inputs (Arduino): 8 buttons

Pins 2–5: “hand” buttons
Pins 6–9: “foot” buttons

Outputs (Arduino to p5):

On each press, Arduino sends a single integer ‘1…8’ over Serial:

3. Progress to Date

Arduino:

Wired 8 buttons with external pull-downs on pins 2–9

p5 Sketch detects presses and sends 1–8 over Serial

p5.js:

Created menu/info/song/difficulty screens with custom images
Implemented serial reading + tile engine + hit logic + lives/score UI
Integrated “Crazy Frog” sample playback and game over logic

Next Steps:

Fill out the full 44-step Crazy Frog pattern for a complete song

week 11

Piano Tiles: Hand and Foot

  1. overview

A rhythm-game where you must hit tiles falling in four columns using Arduino-connected hand (red) and foot (blue) buttons. Complete the Crazy Frog song to win; miss 3 times and you lose.

2. Arduino

Buttons: 4 hand inputs (pins 2–5), 4 foot inputs (pins 6–9)
Wiring: Buttons wired to +5 V with external pull-downs to GND

3. p5.js

Tile engine:

-Spawns one tile at a time per a predefined ;pattern[]’ of ‘{col, isFoot}’ steps
-Draws tile in its column, colored by ‘isFoot’ (blue) or hand (red)
-Moves tile downward at speed chosen by difficulty

-Lose if lives hit zero; win if you clear the last tile before the sample ends

week 11- reading

Reading this made me think about how design for people with disabilities is often overlooked or treated differently from regular products. I was surprised to learn that something as useful as a leg splint could also be inspiring in its design. It showed me that design doesn’t have to be boring just because it’s made for a medical use. I also liked how the author talked about fashion and disability. Glasses are a good example of how something can help people see better but still be stylish and accepted by everyone. The book made me reflect on how design can be both helpful and creative, and how we need more balance between solving problems and exploring new ideas in design especially in the disability world.

week 11- Homework

Task 1:

We made it so that the program shows a circle on the screen that moves left and right when u rotate the potentiometer. The Arduino sends values to p5, and p5 reads those values through the serial port. As the potentiometer is rotated, the circle moves across the canvas to match its position. Theres also a button in the sketch that lets you connect to the Arduino manually.

p5.js code:

let serialPort;         // connection to the arduino
let connectButton;      // button to connect to arduino
let serialSpeed = 9600; // speed of communication between p5 and arduino
let xCircle = 300;      // starting x position of circle

function setup() {
  createCanvas(300, 300);
  background(245);

  serialPort = createSerial();

  let previous = usedSerialPorts();
  if (previous.length > 0) {
    serialPort.open(previous[0], serialSpeed);
  }

  connectButton = createButton("Connect Arduino"); // connect button
  connectButton.mousePressed(() => serialPort.open(serialSpeed)); // when clicked, connect
}

function draw() {
  let data = serialPort.readUntil("\n");  // reads the data from arduino

  if (data.length > 0) {       // if data received
    let sensorValue = int(data); // convert it to a number
    xCircle = sensorValue;       // use it to update the circles x position
  }

  background(245);
  fill(255, 80, 100);
  noStroke();
  ellipse(xCircle, height/2, 35); // draw circle at new position
}

Arduino code:

void setup() {
  Serial.begin(9600); // initialize serial communications
}
 
void loop() {
  // read the input pin:
  int potentiometer = analogRead(A1);                  
  // remap the pot value to 0-300:
  int mappedPotValue = map(potentiometer, 0, 1023, 0, 300); 
  Serial.println(mappedPotValue);
  // slight delay to stabilize the ADC:
  delay(1);                                            
  delay(100);
}

Task 2:

When the Arduino receives the letter ‘H’, it turns the LED on. When it receives the letter ‘L’, it turns the LED off. This lets you control the LED p5 by pressing the “Turn ON” or “Turn OFF” buttons.

p5.js code:

let serialPort;
let connectButton;
let serialSpeed = 9600;

function setup() {
  createCanvas(300, 200);
  background(240);

  serialPort = createSerial(); // create serial port connection

  let prev = usedSerialPorts(); // check if theres a previously used port
  if (prev.length > 0) {
    serialPort.open(prev[0], serialSpeed);
  }

  connectButton = createButton("Connect Arduino");
  connectButton.position(10, 10); // button position
  connectButton.mousePressed(() => serialPort.open(serialSpeed)); // open port when button clicked

  let onBtn = createButton("Turn ON"); // button to turn the LED on
  onBtn.position(10, 50); // position of ON button
  onBtn.mousePressed(() => serialPort.write('H')); // Send 'H' to arduino when pressed

  let offBtn = createButton("Turn OFF"); // button to turn the LED off
  offBtn.position(90, 50); // position of OFF button
  offBtn.mousePressed(() => serialPort.write('L')); // send 'L' to arduino when pressed
}

function draw() {
}

Arduino code:

void setup() {
  pinMode(9, OUTPUT);        // LED on pin 9
  Serial.begin(9600);        // start serial communication
}

void loop() {
  if (Serial.available()) {
    char c = Serial.read();

    if (c == 'H') {
      digitalWrite(9, HIGH); // turn LED on
    }
    else if (c == 'L') {
      digitalWrite(9, LOW);  // turn LED off
    }
  }
}

Task 3:

We used serialPort to read the sensor value and mapped it to wind force. To light up the LED only once per bounce, we added a boolean flag (ledTriggered). When the ball hits the ground, it sends a signal like ‘H’ to the Arduino to turn on the LED and ‘L’ to turn it off.

p5.js code:

let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let serial;
let connectBtn;
let ledTriggered = false;

function setup() {
  createCanvas(640, 360);
  noFill();
  position = createVector(width / 2, 0);
  velocity = createVector(0, 0);
  acceleration = createVector(0, 0);
  gravity = createVector(0, 0.5 * mass);
  wind = createVector(0, 0);

  // setup serial connection
  serial = createSerial();
  let previous = usedSerialPorts();
  if (previous.length > 0) {
    serial.open(previous[0], 9600);
  }

  connectBtn = createButton("Connect to Arduino");
  connectBtn.position(10, height + 10); // button position
  connectBtn.mousePressed(() => serial.open(9600));
}

function draw() {
  background(255);
  // check if we received any data
  let sensorData = serial.readUntil("\n");

  if (sensorData.length > 0) {
  // convert string to an integer after trimming spaces or newline

    let analogVal = int(sensorData.trim());
    let windForce = map(analogVal, 0, 1023, -1, 1);
    wind.x = windForce; // horizontal wind force
  }

  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  ellipse(position.x, position.y, mass, mass);
  if (position.y > height - mass / 2) {
    velocity.y *= -0.9; // A little dampening when hitting the bottom
    position.y = height - mass / 2;

    if (!ledTriggered) {
      serial.write("1\n");   // trigger arduino LED
      ledTriggered = true;
    }
  } else {
    ledTriggered = false;
  }
}

function applyForce(force) {
  // Newton's 2nd law: F = M * A
  // or A = F / M
  let f = p5.Vector.div(force, mass);
  acceleration.add(f);
}

function keyPressed() {
  if (key === ' ') {
    mass = random(15, 80);
    position.y = -mass;
    velocity.mult(0);
  }
}

Arduino code:

int ledPin = 5;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // send sensor value to p5.js
  int sensor = analogRead(A1);
  Serial.println(sensor);
  delay(100);

  // check for '1' from p5 to trigger LED
  if (Serial.available()) {
    char c = Serial.read();
    if (c == '1') {
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin, LOW);
    }
  }
}

Video:

week 10- assignment

Concept: Music Box

Our inspiration for this project was from a music box, which is a pretty nostalgic instrument that plays melodies when you wind it up. Music boxes are usually these small, mechanical devices that play a set of tunes with a simple winding mechanism, and have a sweet, tinkling sound.

Our version of this is more interactive and customizable. Instead of just one melody, our “music box” allows the users to choose between two pre-programmed songs , which are Twinkle Twinkle Little Star and Frère Jacques. In addition to that, they can also increase or decrease the tempo of the song with a button, adjust the pitch of the melody with the potentiometer, and as the music plays, the LEDs flash and change, synchronized to the rhythm and notes.

Week 10

Code Snippet/Difficulties

One aspect we had difficulties with was getting the LED’s to align with music and the notes played, however after a few tries we were able to get this code. In summary, to get this “visualizer” effect, we used the flashVisualizer() function , and called it inside the playSong() function, right after each note was played. The i variable, which is the index of the current note in the song, is passed to flashVisualizer(), and so, as the song progresses, the i value increments, causing the 3 LEDs we used to cycle through in sequence. In general, every time a note is played, the flashVisualizer() function is called, which results in a flash of an LED that matches the timing of the note. So, the flashing LED visualizes the rhythm of the music, and since the song is made up of an array of notes, the LEDs change with each note.

// Function to play the melody
void playSong(int *melody, int *durations, int length) {
  for (int i = 0; i < length; i++) {
    int baseNote = melody[i];
    int noteDuration = tempo * (4.0 / durations[i]);  // Calculate note duration
    // Read potentiometer and calculate pitch adjustment
    int potVal = analogRead(potPin); // range: 0–1023
    float pitchFactor = 0.9 + ((float)potVal / 1023.0) * 0.4;  // pitch range: 0.9–1.3
    int adjustedNote = baseNote * pitchFactor;
    if (baseNote == 0) {
      noTone(buzzer); // Pause
    } else {
      tone(buzzer, adjustedNote, noteDuration); // Play adjusted tone
      flashVisualizer(i); // Flash LEDs
    }
    delay(noteDuration * 1.3); 
  }
  // Turn off all LEDs after song ends
  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  digitalWrite(led3, LOW);
}
// LED Visualizer Function
void flashVisualizer(int index) {
  // Turn off all LEDs first
  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  digitalWrite(led3, LOW);
  // Turn on one LED based on index
  if (index % 3 == 0) digitalWrite(led1, HIGH);
  else if (index % 3 == 1) digitalWrite(led2, HIGH);
  else digitalWrite(led3, HIGH);
}

 

Reflections/Improvements

Overall, this project was very fun and engaging, and we are very happy with how it turned out as we were able to implement most of the ideas we brainstormed. That said, there are a few things we’d improve. For one, we could expand the number of songs it can play. Also, the current LED visualizer is pretty simple and linear, so adding more LEDs or creating more complex patterns based on pitch, tempo, or things like that, could make it feel more like a true light show.

week 10- reading response

  1. interaction design

This reading really made me think about how much we’ve limited ourselves by relying so heavily on screens. It reminded me that our hands are capable of so much more than just tapping and swiping. I never really paid attention to how much we use our sense of touch in daily life until this made me look around and notice. It pushed me to question why future technology isn’t making more use of the abilities our bodies already have.

  1. response

Reading the responses helped me see that the original rant wasn’t just about complaining, it was a wake-up call. I liked how the writer defended their points and stayed focused on encouraging better research and thinking. It made me realize that we don’t always need to have the full solution to speak up. Sometimes pointing out a problem clearly is the first and most important step in fixing it.

week 9- reading assignment

physical computing:

The piece helped me realize that physical computing isn’t just about coding and hardware, it’s about designing meaningful experiences. The idea that interaction is a two way conversation made me see the importance of listening to users through how they react to what I build. It encouraged me to think more deeply about how the physical form and behavior of a project guide people without needing explanations.

interactive art:

This reading made me rethink what it means to create interactive art. Instead of focusing on showing my message, I should focus more on creating an environment where others can explore and make meaning themselves. It challenged my habit of controlling the viewer’s experience and reminded me that real interaction only happens when I let go of control and simply observe how people engage with the work.

week 9- sensor assignment

Concept
Sometimes in the UAE sunlight levels are pretty strong and sometimes harmful, so I created this project to help people know whether it is safe to go outside based on how bright the sunlight is. It uses an LDR to measure how strong the light is. If the light level is too high, a red LED turns on to show that it might be unsafe due to strong sunlight. If the light is at a normal or low level, a green LED turns on to show it is safe. The system only works when a button is pressed, giving the user control over when to check.

Implementation and Setup
To build the project, I used an LDR sensor, a push button, two LEDs (red and green), and some resistors. The LDR is connected to analog pin A0 to measure light. The button is connected to digital pin 11, and the LEDs are connected to pins 3 and 5. When the button is held down, the Arduino reads the light level. If it is above the threshold of 900, the red LED turns on meaning the sunlight levels are too high. If it is below, the green LED turns on, so the light levels are safe. I also used the Serial Monitor to display the light values for testing.

Code:

const int ldrPin = A0; // LDR sensor
const int buttonPin = 11; // push button
const int redLED = 3; // red LED (too bright)
const int greenLED = 5; // green LED (safe to go out)
const int threshold = 900; // brightness threshold

void setup() {
  pinMode(redLED, OUTPUT);
  pinMode(greenLED, OUTPUT);
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
}

void loop() {
  int lightValue = analogRead(ldrPin); // get light level
  int buttonState = digitalRead(buttonPin); // read button
  Serial.println(lightValue);
  // Serial.println(buttonState); //// to check button

  // only run if button is pressed
  if (buttonState == HIGH) {
    if (lightValue > threshold) { // if light value above threshold red LED on
      digitalWrite(redLED, HIGH);
      digitalWrite(greenLED, LOW); // green LED off
    } else {
      digitalWrite(redLED, LOW); // red LED off if light value below threshold
      digitalWrite(greenLED, HIGH); // green LED on
    }
  } else { // else both LEDS are off
    digitalWrite(redLED, LOW);
    digitalWrite(greenLED, LOW);
  }

  delay(100); // delay
}

Video:

IMG_0851

Future Improvements or Reflection
In the future, I could add a UV sensor to measure sunlight more accurately, since the LDR only measures brightness, not UV levels. I could also add a small screen or buzzer to make the warnings more clear.