Week 14 – Final Project

Concept

My project is a DIY Disco that combines Arduino and p5.js to create an interactive audio-visual experience. The Arduino handles the physical buttons, DC motor and controls a NeoPixel LED strip, while p5.js is responsible for generating a visualizer that reacts to the music.

How It Works

The project consists of two main components:

Arduino Control and LED Display

  • Three physical buttons are connected to the Arduino:
      • Button 3: Activates the airhorn sound effect
      • Button 2: Increases the speed of the music
      • Button 1: Shuffles to a different song
  • The NeoPixel strip lights up with different patterns that shuffle when Shuffle button is pressed

p5.js Visualizer and Music Control

  • p5.js generates a circular music visualizer that responds to the song’s frequencies. It receives signals from the Arduino through serial communication to trigger changes in the visualizer based on button presses.

Interaction Design

The interaction design is simple and engaging: pressing physical buttons on the Arduino changes the music’s speed, shuffles the track, or plays a sound effect while the NeoPixel lights and p5.js visualizer respond instantly.

Arduino Code

My project features three physical buttons connected to the Arduino. Button 1 triggers a signal (1) that is sent to p5.js and triggers the airhorn effect,  Button 2 sends a different signal (2) to p5.js, which affects the speed of the song playing. Button 3  sends a signal (3) to shuffle to the next song as well as cycles through five distinct LED animation patterns. Alongside this, the Arduino also manages the motor that spins the vinyl. Through serial communication with p5.js, the motor’s state is toggled based on signals received. A ‘1’ signal turns the motor on, while a ‘0’ stops it and clears the LED display. Below is the code:

#include <Adafruit_NeoPixel.h>

const int button1Pin = 2;
const int button2Pin = 3;
const int button3Pin = 4;
const int bin1Pin = 8;
const int bin2Pin = 7;
const int pwmBPin = 9;
bool motorRunning = true;

#define NEOPIXEL_PIN 6
#define LED_COUNT    60
#define Num_LED      56

Adafruit_NeoPixel strip(LED_COUNT, NEOPIXEL_PIN, NEO_GRBW + NEO_KHZ800);

unsigned long lastButtonPress1 = 0;
unsigned long lastButtonPress2 = 0;
unsigned long lastButtonPress3 = 0;
unsigned long lastActionTime1 = 0;
unsigned long lastActionTime2 = 0;
unsigned long lastActionTime3 = 0;
const unsigned long debounceDelay = 100;
const unsigned long cooldown = 1000;

int currentPattern = 0;
const int totalPatterns = 5;

unsigned long lastPatternUpdate = 0;
const unsigned long patternInterval = 80;

int snakeIndex = 0;
float hueOffset = 0;

void setup() {
  Serial.begin(9600);
  pinMode(button1Pin, INPUT_PULLUP);
  pinMode(button2Pin, INPUT_PULLUP);
  pinMode(button3Pin, INPUT_PULLUP);
  pinMode(bin1Pin, OUTPUT);
  pinMode(bin2Pin, OUTPUT);
  pinMode(pwmBPin, OUTPUT);
  strip.begin();
  strip.show();
  strip.setBrightness(180);
}

void loop() {
  unsigned long currentMillis = millis();

  handleButtons(currentMillis);
  handleSerial();
  controlMotor();

  if (currentMillis - lastPatternUpdate >= patternInterval) {
    lastPatternUpdate = currentMillis;
    runPattern(currentPattern);
  }
}

void handleButtons(unsigned long currentMillis) {
  if (digitalRead(button1Pin) == LOW && currentMillis - lastButtonPress1 >= debounceDelay && currentMillis - lastActionTime1 >= cooldown) {
    Serial.println("1");
    lastButtonPress1 = currentMillis;
    lastActionTime1 = currentMillis;
  }

  if (digitalRead(button2Pin) == LOW && currentMillis - lastButtonPress2 >= debounceDelay && currentMillis - lastActionTime2 >= cooldown) {
    Serial.println("2");
    lastButtonPress2 = currentMillis;
    lastActionTime2 = currentMillis;
  }

  if (digitalRead(button3Pin) == LOW && currentMillis - lastButtonPress3 >= debounceDelay && currentMillis - lastActionTime3 >= cooldown) {
    Serial.println("3");
    currentPattern = (currentPattern + 1) % totalPatterns;
    lastButtonPress3 = currentMillis;
    lastActionTime3 = currentMillis;
  }
}

void handleSerial() {
  if (Serial.available() > 0) {
    char incomingByte = Serial.read();
    if (incomingByte == '1') {
      motorRunning = true;
    } else if (incomingByte == '0') {
      motorRunning = false;
      digitalWrite(bin1Pin, LOW);
      digitalWrite(bin2Pin, LOW);
      analogWrite(pwmBPin, 0);
      strip.clear();
      strip.show();
    }
  }
}

void controlMotor() {
  if (motorRunning) {
    digitalWrite(bin1Pin, HIGH);
    digitalWrite(bin2Pin, LOW);
    analogWrite(pwmBPin, 50);
  } else {
    digitalWrite(bin1Pin, LOW);
    digitalWrite(bin2Pin, LOW);
    analogWrite(pwmBPin, 0);
  }
}

// === Pattern Dispatcher ===
void runPattern(int pattern) {
  switch (pattern) {
    case 0: discoFlash(); break;
    case 1: snakeCrawl(); break;
    case 2: colorWave(); break;
    case 3: sparkleStars(); break;
    case 4: fireGlow(); break;
  }
}

// Pattern 0: Disco Flash
void discoFlash() {
  for (int i = 0; i < Num_LED; i++) {
    strip.setPixelColor(i, randomColor());
  }
  strip.show();
}

// Pattern 1: Snake Crawl
void snakeCrawl() {
  strip.clear();
  int snakeLength = 6;
  for (int i = 0; i < snakeLength; i++) {
    int index = (snakeIndex + i) % Num_LED;
    strip.setPixelColor(index, Wheel((index * 5 + hueOffset)));
  }
  snakeIndex = (snakeIndex + 1) % Num_LED;
  hueOffset += 1;
  strip.show();
}

// Pattern 2: Smooth Rainbow Wave
void colorWave() {
  for (int i = 0; i < Num_LED; i++) {
    int hue = (i * 256 / Num_LED + (int)hueOffset) % 256;
    strip.setPixelColor(i, Wheel(hue));
  }
  hueOffset += 1;
  strip.show();
}

// Pattern 3: Sparkle Stars
void sparkleStars() {
  for (int i = 0; i < Num_LED; i++) {
    strip.setPixelColor(i, (random(10) < 2) ? strip.Color(255, 255, 255) : strip.Color(0, 0, 10));
  }
  strip.show();
}

// Pattern 4: Fire Glow
void fireGlow() {
  for (int i = 0; i < Num_LED; i++) {
    int r = random(180, 255);
    int g = random(0, 100);
    int b = 0;
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

// Helpers
uint32_t randomColor() {
  return strip.Color(random(256), random(256), random(256));
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

P5 code

The code uses createSerial() to open a serial port, allowing it to send and receive data between the p5 sketch and the arduino. The sensor values received from the Arduino (via serialPort.readUntil(“\n”)) trigger different actions within the p5 sketch based on specific sensor inputs. For example, a sensor value of 1 plays an airhorn sound, 2 toggles the playback speed of the song, and 3 shuffles to a new random song. The sensor values are continuously checked in the checkButtonPress() function, which responds accordingly by performing actions like playing sounds or changing song attributes.

The logic behind the visualizer relies on the FFT (Fast Fourier Transform) analysis of the audio. The fft.analyze() function breaks the audio into different frequency bands, so it will give a spectrum that represents the amplitude of different frequencies in the sound. The visualizer then maps these frequency intensities, for my project I decided to do so in a circular arrangement around the center of the screen, where each bar’s height is determined by the amplitude of its corresponding frequency band. The visualizer is updated in real time, so if the music is changed, or the speed is changed it will reflect those changes.

// === GLOBAL VARIABLES === //
let state = "landing";
let song;
let fft;
let selectedSong = "";
let sensorValue = 0;
let serialPort;
let serialSpeed = 9600;

let partySongs = ["nowahala.mp3", "umbrella.mp3", "yeah.mp3", "onlygirl.mp3", "hips.mp3","feeling.mp3","romance.mp3","monalisa.mp3","move.mp3","saywhat.mp3","yamore.mp3","adore.mp3","gorah.mp3"];
let airhornSound;

let startButton, continueButton, restartButton;
let isFastSpeed = false;

let bgImg;
let Instructions;
let vis;

// === PRELOAD === //
function preload() {
  bgImg = loadImage('bg.png');
  Instructions = loadImage('instructions.png');
  vis =loadImage('vis.png')
  airhornSound = loadSound("airhorn.mp3");
}

// === SETUP === //
function setup() {
  createCanvas(1460, 760);
  textAlign(CENTER, CENTER);
  angleMode(DEGREES);
  colorMode(HSB);

  // Start Button
  startButton = createButton("Start");
  styleButton(startButton, width / 2 - 45, height / 2 + 200);
  startButton.mousePressed(() => {
    state = "instructions";
    hideAllButtons();
    continueButton.show();
  });

  // Continue Button
  continueButton = createButton("Continue");
  styleButton(continueButton, width / 2 - 60, height / 2 + 200);
  continueButton.mousePressed(() => {
    selectedSong = random(partySongs);
    state = "visualizer";
    hideAllButtons();
  });
  continueButton.hide();

  // Restart Button
  restartButton = createButton("Restart");
  styleButton(restartButton, 20, 20, true);
  restartButton.mousePressed(() => {
    if (song && song.isPlaying()) song.stop();
    song = undefined;
    state = "landing";
    isFastSpeed = false;
    hideAllButtons();
    startButton.show();
    if (serialPort.opened()) serialPort.write("0");
  });
  restartButton.hide();

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

// === STYLE BUTTONS === //
function styleButton(btn, x, y, small = false) {
  btn.position(x, y);
  btn.style("padding", small ? "8px 16px" : "12px 24px");
  btn.style("font-size", small ? "16px" : "18px");
  btn.style("background-color", "#ffc700");
  btn.style("border", "none");
  btn.style("border-radius", "12px");
  btn.mouseOver(() => btn.style("background-color", "#FFFF"));
  btn.mouseOut(() => btn.style("background-color", "#ffc700"));
  btn.hide();
}

function hideAllButtons() {
  startButton.hide();
  continueButton.hide();
  restartButton.hide();
}

// === DRAW === //
function draw() {
  let data = serialPort.readUntil("\n");
  if (data.length > 0) {
    sensorValue = int(data);
    checkButtonPress(sensorValue);
  }

  if (state === "landing") {
    showLanding();
    startButton.show();
    if (serialPort.opened()) serialPort.write("0");
  } else if (state === "instructions") {
    showInstructions();
    continueButton.show();
  } else if (state === "visualizer") {
    restartButton.show();
    if (song === undefined) {
      loadSong(selectedSong);
    }
    runVisualizer();
    if (serialPort.opened()) serialPort.write("1");
  }
}

// === LANDING SCREEN === //
function showLanding() {
  image(bgImg, 0, 0, width, height);
}

// === INSTRUCTIONS SCREEN === //
function showInstructions() {
  image(Instructions, 0, 0, width, height);
}

// === LOAD SONG === //
function loadSong(songName) {
  song = loadSound(songName, startSong);
  fft = new p5.FFT();
}

function startSong() {
  song.rate(isFastSpeed ? 1.5 : 1.0);
  song.loop();
}

// === VISUALIZER === //
function runVisualizer() {
  let spectrum = fft.analyze();
  let lowerLimit = 0;
  let upperLimit = Math.floor(spectrum.length / 2);
  let numBars = upperLimit - lowerLimit;
  let radius = 70;
  let angleStep = 360 / numBars;
  let maxBarHeight = height / 1.8;


  image(vis, 0, 0, width, height);

  push();
  translate(width / 2, height / 2);

  for (let j = 0; j < 4; j++) {
    push();
    rotate(j * 90);
    for (let i = lowerLimit; i < upperLimit; i++) {
      let angle = (i - lowerLimit) * angleStep;
      let barHeight = map(spectrum[i], 0, 500, 15, maxBarHeight);
      let xEnd = cos(angle) * (radius + barHeight);
      let yEnd = sin(angle) * (radius + barHeight);
      stroke('#ffc700');
      line(0, 0, xEnd, yEnd);
    }
    pop();
  }

  pop();
}

// === CHECK SERIAL INPUT === //
function checkButtonPress(sensorValue) {
  if (state === "visualizer") {
    if (sensorValue === 1) {
      playAirhorn();
    } else if (sensorValue === 2) {
      toggleSpeed();
    } else if (sensorValue === 3) {
      shuffleNextSong();
    }
  }
}

// === SHUFFLE SONG === //
function shuffleNextSong() {
  let nextSong = random(partySongs);
  if (song && song.isPlaying()) song.stop();
  selectedSong = nextSong;
  isFastSpeed = false;
  loadSong(selectedSong);
}

// === TOGGLE SPEED === //
function toggleSpeed() {
  if (song) {
    isFastSpeed = !isFastSpeed;
    song.rate(isFastSpeed ? 1.5 : 1.0);
  }
}

// === PLAY AIRHORN === //
function playAirhorn() {
  if (airhornSound.isLoaded()) {
    airhornSound.play();
  }
}

 

Reflection/Difficulties

The project was not without its difficulties. One of my primary challenges involved oversensitive buttons. The issue arose because the buttons were triggering multiple actions from a single press, causing  these unintended rapid cycling of effects. To address this, I implemented a debounce mechanism and a cooldown timer, which made sure that each button press only activated an action once and prevented continuous cycling. This solution helped smooth out the interaction, making the experience more intuitive for the user.

Navigating the FFT (Fast Fourier Transform) in this project was also a challenge, as it involves converting an audio signal into its frequency components, which then drive the visual effects. The concept of analyzing an audio signal in terms of its frequency spectrum was at first a bit tricky to grasp. The FFT function takes the sound data, decomposes it into various frequency bands, and produces an array of amplitude values that represent the strength of each frequency. The biggest hurdle was understanding how to properly interpret and map these frequency values to create my visuals. For instance, the fft.analyze() function returns an array of amplitudes across a range of frequencies, but to effectively use this data, I needed to determine which frequency bands would be most useful for creating the visualizer’s elements. After some experimentation, I decided to focus on the lower and mid-range frequencies, which seemed to correspond best with the types of beats and musical elements I wanted to visualize.

Another significant issue was the motor’s weakness, which required a jump start for it to function properly. This created confusion for users, as they struggled to understand why the motor wasn’t working correctly.

Overall, I am very happy with how my project turned out, as it encompassed most if not all the things I wanted it to do. If I were to improve it however, I would maybe make the neopixels visualize the music as well, get the data from the FFT and send it to arduino to visaulize the changes in music. Maybe also add more sound effects, and additional interactive physical elements.

Week 13 – User Testing

My user testing for the DIY Disco Set revealed that the project was generally intuitive and easy to navigate, largely due to the  instructions page I added and the exploratory nature of the buttons. Users were able to understand the mapping between controls and the resulting effects without much guidance, which speaks to the project’s accessible design. However, I think there was noticeable confusion around the vinyl mechanism. The instructions I wrote mentioned that they should spin the vinyl to start dj-ing, on a mechanical perspective this was supposed to just jump start the motor and then it would start spinnning on its own, but users continued to manually spin and touch it even after the motor had taken over, unintentionally disrupting its movement. This suggests that while the concept was understood, the transition from manual to motorized spinning was not entirely clear. To address this, I think the instructions could be refined to emphasize that spinning is only required at the beginning, and clearer visual feedback, so such as an indicator light or animation, could help users recognize when the motor is engaged.

Week 12 – Finalized Concept

For my final project, I decided to shift away from my original idea which was a traditional music box, because I realized it wasn’t interactive enough. Instead, I’ve reworked the concept into a DIY DJ set. So, the new version still keeps the essence of the music box, especially the spinning element, but now users will be spinning a vinyl or maybe a disco ball.

The project will allow users to shuffle through songs, change the playback speed, and add sound effects to simulate dj-ing. I’m also thinking of  incorporating NeoPixels to either synchronize with the music or enhance the overall atmosphere visually.  For this project, the Arduino would handle the  physical input, so when users press buttons, specific actions are triggered (e.g shuffle, speed, sound effect). On the p5.js side, I would build a music visualizer that will deconstruct the audio and represent it visually by showing changes in loudness and frequency over time.

 

Week 11 – Final Project Proposal

Concept
My project will be inspired those old music boxes with spinning ballerinas. Ideally, my project would feature a spinning ballerina or just a figurine, LED’s that would synchronize with the music, and an aesthetic, coded background that adjusts to the mood and rhythm of the music. It would have multiple song options, and based on the song picked the ballerina, LED’s, and screen would change and be synchronized to the tempo and mood of the song.

Implementation

The p5.js component of the project will generate a visual background on a screen that reacts to the mood and rhythm of the music. As the music plays, the background will shift in real-time, creating patterns or abstract shapes that match the mood of the track. For example, more upbeat, energetic songs could produce fast-moving, bright, dynamic visuals, while softer or slower melodies might evoke slower, more tranquil visuals. p5 will also be in charge of the user interaction, the user will be able to  interact with the system through buttons on the screen, where they can select which songs to play.

Arduino on the other hand will serve as the main controller for the mechanical and physical elements of the installation. It will control the motor for the ballerina’s spin and manage the LED lights, ensuring that both respond appropriately to the music.

Week 11 – Reading Response

Design Meets Disability

This reading made me reconsider how I think about design in the context of disability. The leg splint designed by Charles and Ray Eames specifically really stood out to me, because it was initially created to help injured soldiers, but ended up influencing furniture that became iconic. Its actually fascinating how something made for a medical purpose could also be seen as beautiful and timeless. This really challenges the idea that medical or assistive products don’t need to look good, and that their purpose somehow makes good design less important. This made me think about how we often overlook the innovation that can come from designing for smaller, more specific needs, and how that innovation can influence much broader areas. What stayed with me most was the idea of tension between problem-solving and creative exploration in design. The author describes how the Eameses worked within two different approaches, so one that focused on solving practical issues, and another that played with form and possibility. That mix led to some of their most important work. It made me wonder why design for disability today is still so dominated by clinical or technical thinking. Where is the space for imagination and experimentation? This feels like a missed opportunity, and I think if more designers brought in artistic and playful approaches, we might see tools and products that are not only more effective but also more meaningful and engaging for the people using them.

Week 11 – Assignment

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

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

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

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

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

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

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);
    }
  }
}

Week 10 – Reading Response

A Brief Rant on the Future of Interaction Design

I feel like this piece hit hard, not because it presented this radical concept I’d never considered, but because it made me realize how “numb” we’ve become to bad interaction design. The author’s rant about Pictures Under Glass being a transitional technology really stuck with me, as I really hadn’t consciously thought about how much feedback we’re missing when we interact with screens, but now I can’t unsee it. It’s like we’ve accepted a more flattened version of reality just because it looks sleek and futuristic. The author’s praise for the hands also reminded me of how intuitive and rich real world interactions are, so like the simple example of turning the pages of a book or making a sandwich made it feel so obvious. Our fingers do a million things, completely automatically, and we’ve built an entire tech world that barely acknowledges that. The ending made me feel slightly  hopeful, though, I love the idea that the future is a choice, and that inspired people, like us students can try to help push interaction design in new directions.

As for the second reading which was a response to this first reading, what I took away is that if we don’t push for more sensory-rich interaction design, we might risk narrowing our creative and cognitive possibilities. I feel there was a subtle warning which tells designers and everyone else to not let convenience trap us in shallow interaction as we deserve better tools that can really challenge and extend the full capabilities of our bodies and minds.

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.

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 9 – Sensors

Concept: 

When thinking about an idea for this project, I kept coming back to the lights in my room and how they always stayed the same, no matter what the light outside was like, they were either always to bright or too dim. That simple thought inspired me to create a system where they could adjust automatically, so if it’s bright during the day, the lights would stay dim or even stay off, but as the sun sets and it gets darker, they would gradually get brighter and adjust to the environment. 

Implementation/ Setup: 

To implement this, I used the LDR and a button. The button would serve as a simple on/off switch for the LEDs, which would allow me to activate or deactivate the system manually. Then, once the LEDs are turned on, the LDR would take over by detecting the light levels in the room. This means as it gets darker outside, the LDR would read lower light values, which causes the LEDs to become brighter. On the other hand, if it’s bright out, the LEDs automatically dim or even turn off, since additional lighting isn’t really needed. Below is the set up and demo: 

Code/Challenges: 

One of the initial challenges I faced was figuring out how to use both the button and the LDR together in a way that made sense. I wanted the button to first be used to switch the system on, and then only after that should the LDR take over and adjust the LED brightness based on the surrounding light levels. To solve this, I used a boolean variable ledOn, which was inititally set as false, that toggles the LED system on or off when the button is pressed . Once the system is turned on (ledOn true), the LDR starts reading the  light and adjusts the LED brightness accordingly, so darker surroundings make the LEDs brighter, and brighter conditions dim the LEDs. This setup ensured that the button controlled the system’s activation, while the LDR only adjusted the lighting when the system was switched on.

// Detect button press (transition from LOW to HIGH)
 if (buttonState == HIGH && lastButtonState == LOW) {
   delay(50);  // Debounce
   ledOn = !ledOn;  // flips value of ledOn, toggles between on/off
 }


 lastButtonState = buttonState;  // Save current state for next loop


 if (ledOn) {
   // Read light level and map it to brightness
   int lightLevel = analogRead(ldrPin);  // 0 (dark) to 1023 (bright)
   int brightness = map(lightLevel, 0, 1023, 255, 0);  // Invert to make LED brighter in dark


  
   analogWrite(ledPin1, brightness);
   analogWrite(ledPin2, brightness);
 } else {
   // Turn LEDs off
   digitalWrite(ledPin1, LOW);
   digitalWrite(ledPin2, LOW);
 }
}

Reflection/Improvements: 

Reflecting on this project, one key area for improvement would be expanding the functionality of the button to maybe control more features. So, currently, the button toggles the LED system on or off, but I think it could also be used to switch between different modes or lighting patterns for more dynamic control. For example, I could program the button to cycle through several lighting modes. The first press could turn the system on with brightness adjusted by the LDR as it currently works. A second press could set the LEDs to a fixed brightness level, independent of the light conditions outside, giving the user more control over the lighting. A third press could switch the LEDs to a blinking pattern.

Week 8 – Reading Response

Attractive Design

I  found the example of the three teapots particularly interesting because it shows how different designs serve different purposes.  The author describes one of the teapots as being deliberately unusable, the other more aesthetically pleasing, and the last one being practical and well thought out. Yet, he uses all of them at different times, proving that design is situational. This made me reflect on my own experiences, there are times when I  am most liekly to prioritize efficiency, but other times I would like to appreciate beauty or novelty in design. Essentially,  it’s not about a single “best” design but rather the right design for the right moment.

Her Code Got Humans on the Moon

Reading about Margaret Hamilton’s contributions really gave me a new perspective on software design and reliability. I had never thought about how early software engineering had to account for human error in such high-stakes environments. This made me think about how much of today’s UI/UX design is centered around the same kind of principles and ideas Hamilton had. In interactive systems, we rely on clear, intuitive design to prevent mistakes, just like the Apollo software had to ensure astronauts couldn’t accidentally erase critical data. Her work really highlighted the idea that good software isn’t just about writing code but also  about anticipating how users will interact with it and designing systems that are both strong and intuitive to use.