Week 14 – Final Project

Project ‘Pakistani Moseeqi’ (translates to Pakistani music)

Description: A Sound Visualizing Musical Instrument

Concept and corresponding elements

A sound visualization instrument that allows you to:

    1. mix and play sounds to make your own unique music and 
    2. visualize it on the p5js screen

An ode to Pakistani music

This project pays tribute to Pakistani music by incorporating sounds from instruments that are unique to Pakistani music such as the tabla, bansuri, sitar, and veena among others. This way, I wanted to expose the NYUAD community, who will use my project, to Pakistani music while also paying tribute to my culture. 

Accessibility

With buttons, this instrument gives one the opportunity to make their own music. To make it accessible to people who are blind/visually impaired, I used a simple design with an easy-to-use interface that does not require many instructions to read. This way, someone who cannot see can still enjoy it and make their music. 

Additionally, to make it accessible to people who cannot hear, I added the sound visualization element so that they can see a unique visualization of each sound and hence, enjoy the instrument in a different way – as a visualization tool. 

Interaction design, feedback, and implementation

    • 4 momentary LED-Buttons to play 4 different sounds when pressed
    • Each button has an LED light that turns off when the button is pressed to indicate to the user that it is working
    • Each button plays a different sound when pressed. 
    • When pressed, button sends a signal to the p5js to display the animation/visualization associated with that button and sound
    • More than one button can be pressed simultaneously.
    • Turned on/off indicator led light
    • The blue ‘power’ LED on the bottom left is an indicator. When the instrument is in use, it is turned on. When not, it is off. This way, it provides indication to the user when the instrument is working and when it is not. 
    • Arduino/physical elements – contained within a cardboard box
    • 5th non momentary button/switch – this button is not momentary – it, when pressed, plays a background music. The user can then add more sound effects on top of this music. When pressed again, the button stops the music. And this can be repeated. 

P5js elements:

    • Start page – this page displays the cover and brief instructions to keep everything simple and easy to use
    • The play button leads to the music creation and sound visualization page – here, whenever a physical button is pressed, a visualization associated with the sound appears while the button is pressed
    • A slider to control volume of the sound effects from the momentary buttons (not the background music – so that the user controls the volume of the sound they “add” to the background music.
    • Inspiration: https://patatap.com/ 

Arduino Code

For the Arduino end, as mentioned, buttons and LEDs are arranged in a box. 

Inputs and Outputs:

    • From LED buttons to Arduino, which process and send information to p5 

From P5: 

    • Indication if instrument turned on/off: power LED on/off
    • LEDs to indicate if instrument is on or if any button is pressed (feedback)
const int redButtonPin = A2;
const int greenButtonPin = A1; 
const int yellowButtonPin = A3;
const int blueButtonPin = A4;
const int ledPin = 8;
const int songButtonPin = 7; 

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
  //pinMode(blueLedPin, OUTPUT);
  pinMode(redButtonPin, INPUT);
  pinMode(greenButtonPin,INPUT);
  pinMode(yellowButtonPin,INPUT);
  pinMode(blueButtonPin,INPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(songButtonPin, INPUT);

   // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW); 
    delay(50);
    digitalWrite(ledPin, LOW);
  }
}

void loop() 
{
  // put your main code here, to run repeatedly:
  while (Serial.available())
  {
    digitalWrite(LED_BUILTIN, HIGH);
    //if red button pushed
    
    int redButton = digitalRead(redButtonPin);
    int greenButton = digitalRead(greenButtonPin);
    int yellowButton = digitalRead(yellowButtonPin);
    int blueButton = digitalRead(blueButtonPin);
    int songButton = digitalRead(songButtonPin);
    Serial.print(redButton);
    Serial.print(",");
    Serial.print(greenButton);
    Serial.print(",");
    Serial.print(yellowButton);
    Serial.print(",");
    Serial.print(blueButton);
    Serial.print(",");
    Serial.println(songButton);
    //delay(100);


    int light = Serial.parseInt();
    if (light == 1)
    {
      digitalWrite(ledPin, HIGH);
    }
    if (light == 0)
    {
      digitalWrite(ledPin, LOW);
    }

  }
}

 

P5 Sketch and Code

The P5 screen will first present very brief instructions on how to use the instrument. Once the experience begins, the screen will show a visualization of the music created using input from the sensors. 

Inputs and outputs:

    • Use inputs from buttons that Arduino sends to draw shapes/patterns on the screen
    • Display details such as volume/start instruction on the screen

 

let incrementCircle = true;
let greenB=true;
let blueB=true;
let yellowB=true;
let playBg1 = true;
let playStart = true;
let start = true;
let numOfCircles = 0;
let circleX = [];
let circleY = [];
let circleWidth = []
let s = 0;
let alt = true;
let mode = 'pause';
let drum, flute, sitar, veena; 
let volumeSlider; 
let vol;
function setup() {
  startSound = loadSound('audio/bubbles.mp3');
  starting = loadSound('audio/start.mp3');
  volumeSlider = createSlider(0,100);
  volumeSlider.position(650, 20);
  drum = loadSound('audio/tabla.mp3');
  flute = loadSound('audio/bansuri.mp3');
  sitar = loadSound('audio/strike.mp3');
  veena = loadSound('audio/spiral.mp3');
  bgMusic = loadSound('audio/calmBg.mp3');
  //drumA=createAudio('audio/drum.wav');
  startImg = loadImage('images/pak music.png');
  displayImg = loadImage('images/display.png');
  startMusic = loadSound('audio/clay.mp3');
  createCanvas(800, 800);
  
  angleMode(DEGREES);
  rectMode(CENTER);
  
  
} 

function draw() {

  vol = volumeSlider.value();
  drum.setVolume(vol);
  flute.setVolume(vol);
  veena.setVolume(vol);
  sitar.setVolume(vol);
  
  if (mode == 'pause')
  {
    startPage();
    
  }
  
  
  if (mode == 'play')
    {
      background(0);
      for (i = 0; i < 10; i++)
      {
      fill('#F44336A5');
      //CAN DRAW PERMANENT CIRCLES
      noStroke();
      ellipse(width/2, height/2, s+15*i, s+15*i);
      fill('rgb(126,9,9)');
      ellipse(width/2, height/2, s-i*10, s-i*10);
      }
      s = s+10;
      if (playStart == true){
           startSound.play();
        playStart = false;
      }
   
    }
    if (mode == 'drums')
    { //if button pressed, random circles 
      if (incrementCircle == false)
      { 
        playDrums();
      }
      else 
      {
        drawCircles();
      }
    }
      
      
    if (mode == 'sitar')
      {
         if (blueB == false )
          {
            background(0);
          }
        else if (blueB == true)
          {
            playSitar();
          }
      }
  
      if (mode == 'flute')
       {
         console.log(mode);
         console.log(greenB);
        if (greenB == false)
          {
            background(0);
            playFlute();

          }
        else if (greenB == true)
          {
            console.log('calling playFlute')
            playFlute();
          }
      }
      if (mode == 'veena')
      {
        if (yellowB == false)
        {
          background(0);
        }
        else if (yellowB==true)
        {
          playVeena();
        }
      }
      
  textSize(12);
      if (!serialActive) 
      {
        text("Press Space Bar to select Serial Port", 20, 30);
      } else 
      {
        text("Connected", 20, 30);
        let volumeLabel = "Volume: " + vol;
        text(volumeLabel, 600, 20 );
        //everything else goes here

      }
    
}

function readSerial(data) {
    ////////////////////////////////////
    //READ FROM ARDUINO HERE
    ////////////////////////////////////
     if (data != null) 
     {
      //console.log("data:");
       console.log(data);
       // make sure there is actually a message
       let fromArduino = split(trim(data), ",");
       // if the right length, then proceed
       if (fromArduino.length == 5) 
       {
         let redButton = int(fromArduino[0]);
         if (redButton == 1)
         {
           mode = 'drums';
           if (incrementCircle == true)
           {
             numOfCircles = numOfCircles + 1;
             circleX[numOfCircles] = random(width);
             circleY[numOfCircles] = random(height);
             circleWidth[numOfCircles] = random(100);
             incrementCircle = false;
             drum.play();
           }
         }
         else if (redButton == 0)
           {
             incrementCircle = true;
           }
         
         let greenButton = int(fromArduino[1]);
         
         if (greenButton == 1)
         {
           mode = 'flute';
           if(greenB==true)
           {
             console.log("greenb set to false");
             flute.play();
             greenB = false;
           }
         }
        else if (greenButton == 0)
        {
            greenB = true;
            console.log("greenb set to true");
        }
         
         let yellowButton = int(fromArduino[2]);
          if (yellowButton == 1)
         {
           mode = 'sitar';
           if (yellowB == true)
           {
             sitar.play();
             yellowB=false;
           }
         }
         else if (yellowButton == 0)
           {
             yellowB = true;
           }
        
         let blueButton = int(fromArduino[3]);
           if (blueButton == 1)
         {
           mode = 'veena';
           if (blueB == true)
           {
             veena.play();
             blueB=false;
           }
         }
         else if (blueButton == 0)
           {
             blueB = true;
           }
         
         let songButton = int(fromArduino[4]);
         if (songButton ==1)
           {
             console.log('here');
             if (playBg1 == true && mode == 'play')
               {
                 console.log('here1');
                 bgMusic.play();
                 playBg1 = false;
               }
           }
          else if (bgMusic.isPlaying() == true && mode == 'play' && songButton == 0)
               {
                 console.log('here2');
                 bgMusic.stop();
                 playBg1 = true;
               }
      
         
    if (greenButton == 0 && blueButton ==0 && yellowButton == 0 && redButton == 0 && mode != 'pause')
           
      {
        mode = "play";
      }         
         
  // if (playBg1==true && mode == 'play')
  //   {
  //     startMusic.play();
  //     playBg1 = false;
  //   }
  // else {
  //   //calmBg.stop();
  // }         
         /////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
       }
          //////////////////////////////////
          //SEND TO ARDUINO HERE (handshake)
          //////////////////////////////////
       let light; 
      if (mode != 'pause')
      { 
        light = 1;
      }
       else if (mode =='pause')
         {
           light =0;
         }
        let sendToArduino = light + "\n";
        writeSerial(sendToArduino);
     }
}

function startPage()
{
  
  //background('white');
  image(startImg, 0, 0, width, height);
  //fill(255);
  // textSize(64);
  // text("موسیقی",60,120);
  // textSize(20);
  // fill(0);
  // rect(width/3-10,height-50,200,60);
  
  fill(255);
  let x = mouseX;
  let y = mouseY;
  let t = x+','+y;
  text(t, mouseX, mouseY);
  //text("Press 's' to play your own music", width/3-230, height-10); 
  textSize(14);
      textStyle(NORMAL);
      text("Play, mix, and visualize Pakistani music by pressing the buttons", width/4, 3*height/4+50);
  if (mouseX >= 242 && mouseX <=558 && mouseY >=679 && mouseY <= 764)
    {
      noStroke();
      fill('#503418')
      ellipse(390,720,120,75);
      fill(255);
      textSize(38);
      //fontWeight(200);
      textStyle(BOLD);
      text("PLAY",335,734);
      
      if (mouseIsPressed)
        {
          mode='play';
        }
    }
  
}

function keyPressed() 
{
  if (key == " ") 
  {
    // important to have in order to start the serial connection!!
    setUpSerial();
    
    if (playStart==true && mode == "play")
    {
      startMusic.play();
      playStart = false;
    }
    
    startMusic.play();
  }   
  if (key == 's')
  {
    mode = "play";
  }
//   if (key =='d')
//     {
//       mode = 'drums'
//       incrementCircle = false;
//       drum.play();
//     }
// if (key == 'f')
//     {
//       mode = 'flute';
//       flute.play();
//     }
//   if (key == 'g')
//     {
//       mode = 'sitar';
//       sitar.play();
//     }
//   if (key == 'v')
//     {
//       mode = 'veena';
//       veena.play();
//     }
  
  
}

function drawCircles()
{
  noStroke();
  //fill(random(255));
  fill(240);
  for (i =0; i < numOfCircles; i++)
    {
      circle(circleX[i], circleY[i], circleWidth[i]);
    }
}

function playDrums()
{ 
  if (alt== true)
    {alt = false}
  else
  {alt =true;}
 
   background('black');
  for (i = 23; i > 0; i--)
    {
      noStroke();
      if (alt == true)
      {
        if(i%2 == 0)
        { 
          fill('white');
        }
        else 
        {
          fill('black');
        }
      }
      else 
        {
          if(i%2 == 0)
        { 
          fill('black');
        }
        else 
        {
          fill('white');
        }
        }
      ellipse(width/2, height/2, random(50*i));
    }
   fill(0);
  //ellipse(width/2,height/2,40)
}

function playFlute()
{
  console.log("here ");
  background(0);
  noFill();
  push();
  translate(width/2, height/2);
  for (let i =0; i < 200; i++)
  {
    push();
    rotate(sin(frameCount+i)*100);
    
      let r = map(sin(frameCount), -1,1,50,255);
      let g = map(cos(frameCount/2),-1,1,50,255);
      let b = map(sin(frameCount/4),-1,1,50,255);
    noFill();
    stroke(r,g,b);

    rect(0,0,800-i*2,300-i);
    
    pop(); 
  }
  pop();
}

function playSitar()
{
  noStroke();
  for (i =30; i>0; i--)
 {
   if (i%2==0)
   {
     fill(0);
   }
   else
    {
     fill(255,150,0);
    }
   square(width/2, height/2, random(50*i));
 }
}
let direction = 1;

function playVeena()
{
  background(0);
  noStroke();
  let x=random(width);
  let y=0;
  direction = random(-1,1);
    //random(0,width);
  
  //y=random(0,height);
  for (i =0; i < 3*width;i++)
    {
      x=x+direction;
      y=y+1;
      fill(random(x,x+30),random(y-20,y+10),random(y-30,y+20));
      circle(x,y,30);
    }
}

function displayScreen()
{
  image(displayImg, 0,0, width, height);
}

User testing

For the most part, the design was simple to understand for the users. The simple instruction to use buttons to play music for users to understand how to begin. Then, as they pressed each, they easily saw its connection to sound and the visualization on the p5 and used this information to improvise and process the information.  

Things that can be improved: 

The slider for volume is not as visible or clear so we can replace it with clearer labels in the future.

More buttons can be added to create an array of different sounds.

More western music can ALSO be integrated to add a cultural diversity angle to the project.

Bigger box can be used so wires and Arduino can easily be places inside

The 5th red button was, in a way, ignored by the user due to its small size and because it looked small and unimportant alongside all the led lights and buttons. This button can be replaced with something better. 

What I am proud of:

I am particularly proud of my sound visualizations. They add a dynamism and motion to the project that makes listening to each sound even more fun and interesting. The visualizations are very appealing to the eye. I am also proud that I attempted to make the instrument more accessible as this reinforced the importance of keeping accessibility in mind when designing things. 

Week 12 – User Testing

Progress so far
So far, I have developed the basis of my program in  Arduino and P5. I have connected a single button. When pressed, a sound associated with that button is played in p5. An animation, associated with that specific sound is also displayed in p5 and then the canvas when the button is released has a randomly placed circle every time the button is pressed. This is to create a sound and p5 canvas that the user can design and control.

Next, I will add at least three more buttons in the same way. each will have a unique sound and animation attached to it and will allow the user to draw different things on the canvas. I will also add a flex sensor to control a background sound from a buzzer or from p5.

The project concept is based on traditional South Asian music so sound effects will be from instruments such as sitar, rubab, drums, and flute. Also, the background music will also complement these sounds. The project aims to promote accessibility therefore it visualizes sound for those who cannot hear so they can use it in their own unique way to design the canvas.

User Testing

So far, the project is running well. Due to several deadlines and a heavy workload, I could not do a lot of the work for this project but the basis of the idea is implemented and it will be easier to implement from here on.

The user was able to easily understand the interface and the circuit. However, I will add more elements to the interface such as instructions on how to use it for better understanding.

I am particularly proud that my project is accessible and will pay tribute to music from my home country Pakistan. That is why Im excited to further develop it to completion.

Here are details of the Arduino and P5 ends of the project:

Arduino

For the Arduino end, as mentioned, sensors and buttons will be arranged in a box. I might integrate a motor as well that moves something, for example, a music record, according to the sensor values.

Inputs and Outputs:

  • From light sensor – will send it to p5
  • From distance sensor/potentiometer – will send it to p5/use to control the volume of the buzzer
  • From buttons – will light up corresponding LEDs and send to p5
  • From P5 – indication if instrument turned on/off from p5 screen – use to turn corresponding power LED on/off
  • LEDs to indicate if the instrument is on or if any button is pressed (feedback)
  • Buzzer (to play background sound/button sounds)

P5

The P5 screen will first present instructions on how to use the instrument. Once the experience begins, the screen will show a visualization of the music created using input from the sensors. This way, every user will, in a way, create an art piece that is theirs. So, not only can the user change their motion/touch to change the output sound but also to add something to their sketch. Also, P5 will have some buttons that, when pressed, will send output to Arduino – for example, LEDs light up when the Start button is pressed or if the sensor value exceeds a threshold or maybe different tone/sound/volume options available on the screen that will be used to change the output melody.

Inputs and outputs

  • Use light sensor value from Arduino to change tone type and screen background color
  • Use inputs from buttons that Arduino sends to draw shapes/patterns on the screen

 

Week 11 – Final Project Proposal

Sound Canvas – a sound visualizing musical instrument 

Concept

A sound visualization instrument that allows you to both make music and visualize it on the p5 screen. Someone who cannot hear can see the visualization of a sound, and hence will enjoy the instrument in a different way. Similarly, someone who cannot see can hear the sounds, and hence enjoy it differently too. Overall, the Sound Canvas concept presents a unique and accessible approach to music creation and enjoyment, integrating sound visualization, interactivity, and artistic elements.

  • 3 different tone types (fast/medium/slow) – change according to brightness of light in the room – dimmer the light, calmer the tone
      • Light sensor to specify ranges of brightness in which each tone plays so that if brightness within a certain range, a certain type of music will be played in the background 
      • The tone might be played on the buzzer or the computer (TBD)
      • The background color of the p5 screen will represent the tone type, for example, fast will be represented by a specific color such as red
  • 3-6 Buttons to play additional sounds/notes when pressed
      • Each button will have a specific sound associated to it
      • When pressed, the associated sound will be played (added on top of the background music)
      • The sound will either be played on the computer or the buzzer (TBD)
      • Whenever a button is pressed, an LED will light up as feedback
  • Tone volume/pitch can be changed using either a distance sensor or a potentiometer (TBD)
      • volume/pitch displayed on the p5 screen
  • Sound Visualization on the p5 screen with shapes/patterns/colors
      • Inspiration: https://patatap.com/ 
      • Every button when pressed will also display a pattern/color/shape associated to the button on the p5 screen

Arduino

For the Arduino end, as mentioned, sensors and buttons will be arranged in a box. I might integrate a motor as well that moves something, for example a music record, according to the sensor values.

Inputs and Outputs:

    • From light sensor – will send it to p5
    • From distance sensor/potentiometer – will send it to p5/use to control volume of buzzer
    • From buttons – will light up corresponding LEDs and send to p5 
    • From P5 – indication if instrument turned on/off from p5 screen – use to turn corresponding power LED on/off
    • LEDs to indicate if instrument is on or if any button is pressed (feedback)
    • Buzzer (to play background sound/button sounds)

P5

The P5 screen will first present instructions on how to use the instrument. Once the experience begins, the screen will show a visualization of the music created using input from the sensors. This way, every user will, in a way, create an art piece that is theirs. So, not only can the user change their motion/touch to change the output sound but to also add something to their sketch. Also, P5 will have some buttons that, when pressed, will send output to Arduino – for example LEDs light up when Start button is pressed or if sensor value exceeds a threshold or maybe different tone/sound/volume options available on screen that will be used to change the output melody.

Inputs and outputs

    • Use light sensor value from Arduino to change tone type and screen background color
    • Use inputs from buttons that Arduino sends to draw shapes/patterns on the screen
    • Display details such as volume/tone type in the corner of the screen

Week 11 – Arduino and P5 + Final Project Concept

Exercises

Exercise 1

For exercise 1, we used a light sensor. As brightness of light in the surrounding was changed, the ball moved along the horizontal axis in p5. Higher the brightness, the farther right the ball moved.

The values from the light sensor roughly ranged between 0 and 500,000 so we mapped them between 0 and width of the screen and used the mapped value as x-coordinate of the ellipse

//Exercise 1 P5js Code

let ellipseX; // Variable to store ellipse's x-coordinate

function setup() {
  createCanvas(400, 400); // Create a canvas of 800x400 pixels
  ellipseX = width / 2; // Set initial x-coordinate of ellipse to middle of the screen
  noStroke(); // No stroke for the ellipse
}

function draw() {
  
  background(220); // Refresh background on each frame
  fill(255, 0, 0); // Set fill color to red
  ellipse(ellipseX, height / 2, 50, 50); // Draw ellipse at current x-coordinate and middle of the screen
  
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    text("Connected", 20, 30);}
}


function keyPressed() {
  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }   
}

// this callback function
function readSerial(data) {
  ////////////////////////////////////
  //READ FROM ARDUINO HERE
  ////////////////////////////////////

  if (data != null) {
    // make sure there is actually a message
    // split the message
    console.log(data);
    console.log("fromArduino");
   
    let fromArduino = split(trim(data), ",");
    // if the right length, then proceed
    if (fromArduino.length == 1) {
      // only store values here
      // do everything with those values in the main draw loop
      console.log("working");
//values from light sensor roughly ranged between 0 and 500,000 so map them between 0 and width of the screen
//use the mapped value as x-coordinate of the ellipse
      ellipseX = map(data,0,500000, 0,width);
    }

    //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
   let sendToArduino = fromArduino + "\n";
   writeSerial(sendToArduino);
  }
}
//Exercise 1 Arduino Code 

void setup() {
  Serial.begin(9600); // Start serial communication at 9600 bps

  pinMode(LED_BUILTIN, OUTPUT);
// start the handshake
  while (Serial.available() <= 0) {
    //digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    //digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}
void loop() {

  // wait for data from p5 before doing something
    while (Serial.available()) {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data

  // Read sensor value
  int sensorValue = analogRead(A0);
   Serial.print(sensorValue);
  // Map sensor value to screen width (e.g. 0 to 800 pixels)
  int screenValue = map(sensorValue, 0, 1023, 0, 800);

  // Send mapped value to p5.js
  Serial.println(screenValue);

  delay(50); //    for stability
}
digitalWrite(LED_BUILTIN, LOW);
}

Exercise 2

For this exercise, we used a slider in p5 to control brightness of the LED. Value of slider ranged between 0 and 255 and these values were sent to Arduino and used as brightness for the LED.

//Exercise 2 P5js Code

let slider;
function setup() {
   createCanvas(400, 400); // Create a canvas of 800x400 pixels
  slider = createSlider(0, 255, 0);
  slider.position(160,height/2); // Set the position of the slider
  slider.style('width', '80px'); // Set the width of the slider
 
  
  noStroke(); // No stroke for the ellipse
}

function draw() {
  
  background(255); // Refresh background on each frame
  
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    text("Connected", 20, 30);}
}


function keyPressed() {
  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }   
}

// this callback function
function readSerial(data) {
    ////////////////////////////////////
    //READ FROM ARDUINO HERE
    ////////////////////////////////////

    //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
    console.log(slider.value());
    let sendToArduino = slider.value() + "\n";
    writeSerial(sendToArduino);
  
}
//Exercise 2 Arduino Code

const int ledPin =3;
int brightness = 0;
void setup() {
  Serial.begin(9600); // Start serial communication at 9600 bps

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(ledPin, OUTPUT);
// start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
  }
}
void loop() 
{
  // wait for data from p5 before doing something
    while (Serial.available()) 
    {
    digitalWrite(LED_BUILTIN, HIGH); // led on while receiving data
    int brightness = Serial.parseInt(); //get slider value from p5
    Serial.println(brightness); //just to view the value
    
     if (Serial.read() == '\n') {
       analogWrite(ledPin, brightness); //set brightness of LED
     
      }else
      {
        digitalWrite(LED_BUILTIN, HIGH);
      }
    }
}

Exercise 3

For this exercise, we used a potentiometer to control the wind. The potentiometer’s values ranged between 0 and 1023. So, for any value less than 500, wind moved the ball forward/wind blew to right. We added this layer that if value was between 500 and 600, wind stopped so the ball stopped. Finally, if value was above 600, wind blew in opposite direction moving the ball in the opposite direction.

For LED part, we used a flag called heightOfBall. Whenever ball touched the ground, this flag was set to zero. Otherwise, it was set to one. This was sent to Arduino to check whenever ball bounced. When this happened, LED would be turned on. Otherwise it would be turned off.

//Exercise 3 P5js Code

let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let heightOfBall = 0;

function setup() {
  createCanvas(640, 360); // Create a canvas of 800x400 pixels
 
  noFill();
  position = createVector(width/2, 0);
  velocity = createVector(0,0);
  acceleration = createVector(0,0);
  gravity = createVector(0, 0.5*mass);
  wind = createVector(0,0); 
}

function draw() {
  background(215);
  fill(0);
  
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else 
  {
    text("Connected. Press s to jump.", 20, 30);
  
  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  ellipse(position.x,position.y,mass,mass);
  //heightOfBall set to zero whenever ball is touching the ground. Otherwise it is set to 1.
  if (position.y > height-mass/2) {
      velocity.y *= -0.9;  // A little dampening when hitting the bottom
      position.y = height-mass/2;
    heightOfBall = 0;
    } else {
      heightOfBall = 1;
    }
  }
}
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 == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }   
  else if (key=='s'){
    mass=random(15,80);
    position.y=-mass;
    velocity.mult(0);
  }
}

// this callback function
function readSerial(data) {
    ////////////////////////////////////
    //READ FROM ARDUINO HERE
    ////////////////////////////////////
  
     if (data != null) {
    // make sure there is actually a message
    
    let fromArduino = split(trim(data), ",");
    
       // if the right length, then proceed
    if (fromArduino.length == 1) {
//sensor value is the input from potentiometer
      let sensorVal = int(fromArduino[0]);
      console.log("Sensor value: ")
      console.log(sensorVal);

//sensor values range between 0 and 1023
//for any sensor value less than 500, make wind move ball forward/wind blows to right
      if (sensorVal <500){
        wind.x=1
      }
//if sensorVal between 500 and 600, wind stops so ball stops
      else if(sensorVal >=500 && sensorVal < 600){
        wind.x = 0
      }
//if sensorVal over 600, wind blows in opposite direction moving ball to left
      else {
        wind.x =-1
      }
          //////////////////////////////////
          //SEND TO ARDUINO HERE (handshake)
          //////////////////////////////////
    }

//send heightOfBall to arduino to indicate if ball is on the floor or not
    let sendToArduino = heightOfBall  + "\n";
    writeSerial(sendToArduino);
  }
}

Arduino

//Exercise 3 Arduino code

const int poten_pin = A0;
const int ledPin =3;

void setup() {

  Serial.begin(9600); // Start serial communication at 9600 bps
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(poten_pin, INPUT);

  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW); 
    delay(50);
  }
}
void loop() 
{
  // wait for data from p5 before doing something
    while (Serial.available()) 
    {
      digitalWrite(LED_BUILTIN, HIGH);
      digitalWrite(ledPin, LOW);
//read the position of ball from p5
      int position = Serial.parseInt();
    
      if (Serial.read() == '\n') {
        // Read potentiometer value
      int sensorValue = analogRead(poten_pin);
      //send value to p5
      Serial.println(sensorValue);
      }
//if ball is touching the ground i.e. height is zero, turn LED on
      if (position == 0)
      {
        digitalWrite(ledPin, HIGH);
      }
      else{
        digitalWrite(ledPin, LOW);
      }
    }
      digitalWrite(LED_BUILTIN, LOW); 
    }


Video for Exercise 3:

Teammate: Mohid Raza

Final Project Proposal

For the final project, I am planning to make a musical instrument. While I do not have the exact image in mind, I want to use input from different sensors, for example touch and distance sensors, to allow user to control the music.

Elements:

  1. Live sketching/sound visualization on P5 screen using sensor input
  2. Sound control using sensor input
  3. Motor-controlled moving music record controlled by sensor input

other possible:

  • drawing options on p5 screen
  • sound options
  • buttons/LEDs

P5

The P5 screen will first present instructions on how to use the instrument. Once the experience begins, the screen will show a visualization of the music created using input from the sensors. This way, every user will, in a way, create an art piece that is theirs. So, not only can the user change their motion/touch to change the output sound but to also add something to their sketch. Also, P5 will have some buttons that, when pressed, will send output to Arduino – for example LEDs light up when Start button is pressed or if sensor value exceeds a threshold or maybe different tone/sound/volume options available on screen that will be used to change the output melody.

Arduino

For the Arduino end, as mentioned, sensors and buttons will be arranged in a box. I might integrate a motor as well that moves something, for example a music record, according to the sensor values.

Inspiration

Inspiration for the visualization for sound came from this really creative website: https://patatap.com/.

One instrument that inspired me to want to use distance sensors is a Theremin that is a non-contact instrument. I find the instrument both fascinating and beautiful and I would love to create something close to it.

Ennio Morricone - The Ecstasy of Gold - Theremin & Voice - YouTube

Week 10 – Musical Instrument

Concept and Implementation

For this assignment, we wanted to make a musical instrument that was controlled by a user’s motion. So, we came up with the idea of changing the output tone based on the input from a flex sensor. For this, we attached a flex sensor to a glove so that when a user wears the glove and bends their hand, the input changes, and depending on how much the user bends their hand, the tone changes. To implement this, we saved a number of notes in an array. The input from the flex sensor is mapped to values between 0 and the size of the array so that for different measurements of the ‘bend’ of the hand, a different number is obtained after mapping and a note is picked from the array at that number. Moreover, we used a switch to play another tone, so that when the switch is turned on, a different tone made up of a set of notes is played. When the switch is turned off, the user can control the output melody using their hand. We played with different durations and frequencies until we found the one we liked.

Schematic

Video

Code

#include "pitches.h"

const int Buzzer1=3;
const int flexPin = A0;
int value; 

//array of notes
int melody[] = {
  NOTE_E5, NOTE_D5, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_E5, NOTE_E5,
  NOTE_D5, NOTE_D5, NOTE_D5, NOTE_E5, NOTE_G5, NOTE_G4, NOTE_E5,
  NOTE_D5, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_E5, NOTE_E5, NOTE_E5,
  NOTE_D5, NOTE_D5, NOTE_E5, NOTE_D5, NOTE_C5
};

void setup()
{
  Serial.begin(9600);
  pinMode(Buzzer1, OUTPUT);
  pinMode(A2, INPUT);
}

void loop()
{
  int button = digitalRead(A2);
  //when button is pressed, play a predefined tone (happy brithday music)
  if (button == HIGH)
  {
    Serial.print("SWITCH ON\n");
    //tone(Buzzer1, 200, 1200);
    // play a melody using tone()
  tone(Buzzer1, NOTE_C4, 250); delay(250);
  tone(Buzzer1, NOTE_C4, 250); delay(250);
  tone(Buzzer1, NOTE_D4, 500); delay(500);
  tone(Buzzer1, NOTE_C4, 500); delay(500);
  tone(Buzzer1, NOTE_F4, 500); delay(500);
  tone(Buzzer1, NOTE_E4, 1000); delay(1000);
 
  tone(Buzzer1, NOTE_C4, 250); delay(250);
  tone(Buzzer1, NOTE_C4, 250); delay(250);
  tone(Buzzer1, NOTE_D4, 500); delay(500);
  tone(Buzzer1, NOTE_C4, 500); delay(500);
  tone(Buzzer1, NOTE_G4, 500); delay(500);
  tone(Buzzer1, NOTE_F4, 1000); delay(1000);
  }
  //if button is not pressed, read value from flex sensor
  //map the value to a number that is within 0 and the max index of array melody
  //use this mapped value as an index and play the note from that particular index 
  else if (button == LOW)
  {
    Serial.print("OFF\n");
  
  value= analogRead(flexPin);
  // Serial.print("\nvalue is: ");
  // Serial.print(value);
  int size = sizeof(melody)/sizeof(melody[0]);
  value = map(value, 850, 1050, 0, size);
  
  tone(Buzzer1,melody[value],1200);
  //Serial.print("\n Note is: " );
  //Serial.print(melody[value]);
  delay(300);
  }
}

Future Improvements

For later, we can improve by first attaching the flex sensor inside the glove more securely so that the instrument produces more consistent sounds. Also, we can add an additional buzzer with the switch to play a sound continuously in the back while the user uses the flex sensor to add more sounds on top of it.

Groupmate: Mohid Raza

Week 9 – LED Lights with Switches

Concept

For this assignment, I used two LEDs controlled by two switches. The idea is that when one switch is pressed, one of the lights turns on and the other one turns off. When the other button is pressed, the light that is on turns off, and the one controlled by that switch is turned on. When both the buttons are pressed at the same time, both lights blink.

Code

void setup() {
  pinMode(8, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);

digitalWrite(13, LOW);
digitalWrite(8, LOW);

}

void loop() {

  int switchPosition = digitalRead(A2);
  int switchTwoPosition = digitalRead(A3);

  if (switchPosition == HIGH && switchTwoPosition == HIGH)
  {
    digitalWrite(13, HIGH); 
    digitalWrite(8, HIGH);
    delay(100);
    digitalWrite(13, LOW); 
    digitalWrite(8, LOW);
    delay(100);

  }
  else
  {
    if (switchPosition == HIGH) {
    digitalWrite(13, HIGH); 
    digitalWrite(8, LOW);

  }

  if (switchTwoPosition==HIGH)
  {  
    digitalWrite(8, HIGH); // turn the LED on (HIGH is the voltage level)
    digitalWrite(13, LOW);
  }

  }
 
}

Video

Future Improvements

Later, I can add more layers to the lights when each button is separately pressed, for example, change the brightness of the lights or make them fade.

Week 8 – Unusual Switch

Concept and Method

For this project, I wanted to make a fun switch, like the mustache one. So, I made two stick/wire figures that held aluminum foil in their hands and when they high five/fist bump, the bulb lights up. The figures are attached to my shoes so I move them using my feet. While this is not a practical switch, I really enjoyed being creative with it!

Future Improvements

Another thing that can be tried later is that instead of making their hands meet, I can attach the wires directly to my shoes so whenever the shoes touch, the bulb will light up.

Midterm Project – ‘Save the Ocean’ Game

Concept

‘Save the Ocean’ is an adaptation of the famous snakes game but with a new concept. It aims to raise awareness of the plastic pollution in our oceans around the world and with this intent in mind, I developed this game where the player controls a diver (instead of a snake) and uses arrow keys on the keyboard to move around the ocean – the canvas. The diver’s mission is to collect as much plastic waste as possible while avoiding obstacles – the monsters.

The Game

Fullscreen view here: https://editor.p5js.org/ramshabilal/full/uDXF9FUDA

Key elements of the game

Start Page

The game has a start page that shows the cover of the game along with a start button, which, when pressed moves onto the instructions page. Game music also begins playing on this page. An image I designed for the game is used as a background while the button is added using shapes and text.

Instructions Page

With an online image as a background, this page highlights the key information and instructions needed to understand how the game is to be played.  A button ‘Go’ when pressed takes the player to the main game page where the game is to be played.

Main Game Page

The main game page has:

  • The diver: using a segment class I made, the diver object can be moved around using keyboard navigations.
  • Monsters: Monsters are generated at random positions (coordinates saved in arrays) on the canvas and if the diver collides with them, 10 points are deducted from the player’s “Life” – game stops when Life gets to zero.
  • Plastic Waste: These are the green plastic bottles that the user has to collect to save the ocean. Whenever plastic is collected, 10 points are added to the user’s score. At the end of the game, the high score is updated – if the user exceeds the previous high score, they break their own record. This layer is added to compel users to play again.
  • Informational text: Score, high score, and life are displayed for the user to see.

Game over page

This is the page displayed when the game ends i.e. life = zero. Here, the score and high score with the corresponding message are displayed. If the user breaks their high score record, they are informed here as well. Music also changes on this page. The user can also press a button to go back to the start – the game is reset using a reset game function and the user can start again (without rerunning the code).

Difficulty

The difficulty of the game increases with time – as time passes, the game speeds up and the number of monsters in the ocean also increases.

Some of my design considerations

A color scheme was designed for the game – blue and purple were the dominant colors in this theme to create a coherent and complete look and experience for the user and to fit the “ocean” theme of the game.

Initially, this was my cover for the game:

However, to reveal more about what the game will be and to match the theme and colors, I changed it to this design I made:

Buttons changed color when the mouse hovered over them to indicate they need to be pressed.

The main music played in the game was chosen to match the ocean theme. Moreover, pop and coin sound effects were added which corresponded to the collisions with monster and plastic waste respectively.

A simple yet fun font was used uniformly throughout the game to create a coherent and fun look while keeping everything minimal and easy to follow.

Code I am proud of

As  I developed the game, unique challenges popped up that required creative solutions. Developing these solutions was the part I was most proud of as it required problem-solving.

One example is when I wanted to create more than one monster at a time as opposed to the one monster-at-a-time thing I had initially. For this, I created arrays to store random coordinates and loops to create the monsters.

function createMonsters(){
//for every monster, update its speed and if it collides with the walls, reverse their direction of motion
  for (let i = 0; i < numOfMonsters; i++){
    image(monsterImg, monsterXs[i], monsterYs[i], monsterWidth, monsterHeight);
    
    monsterXs[i] += monsterSpeeds[i];
  if (monsterXs[i] < 0 || monsterXs[i]+monsterWidth > width)
    {
      monsterSpeeds[i] = -monsterSpeeds[i];
    }
  }
}

Another challenge I faced was that when the user would play again, the conditions would not reset. For this, firstly i introduced game states to smoothly switch between different stages of the game and also created a reset page function to refresh the game to start conditions.

function stopPage(){
  background('rgb(100,229,255)');
  image(gameOverImg, 0,0,width,height);
  fill('white');
  textAlign(CENTER);
  textFont(scoreFont);
  textSize(24);
  if (score > highscore)
  {
    print('score is ' + score + ' highscore is ' + highscore);
    
    text('Congratulations. You helped free the ocean of harmful plastic and you just beat your previous highscore!', 0, height/2, width);
    fill('gold')
    text('New Highscore =  ' + score, 10, height*2/3, width);
  }
    else 
    { 
      fill('white');
       text('Congratulations. You helped free the ocean of harmful plastic!',10, height/2, width);
      
       text('Score =  ' + score + '\n\nHighscore = '+ highscore, 10, height*2/3, width);
    }
  fill('red')
  ellipse(width/2, 530,200,80);
  fill('white')
  text('Play Again', 295, 538);
  if (mouseX > width/2-100  && mouseX < width/2+100 && mouseY > 530-40 && mouseY < 530+40){
    fill('gold');
    ellipse(width/2, 530,200,80);
    fill('white')
    text('Play Again', 295, 538);
    if (mouseIsPressed){
      reset = true;
      gameOverSong.stop();
      gameState='start';
    }
  }
}

function resetGame(){
  gameOverSong.stop();
  song.loop();
  life =100;
  if (score > highscore){
    highscore = score;
  }
  score = 0;
  startPlaying=false;
  x1=0;
  x2=width;
  direction='start';
  newSeg.setX(width/2);
  newSeg.setY(height/2);
  
  for (i = numOfMonsters; i > 2;i--)
  {
    monsterSpeeds.splice(i, 1);
    monsterXs.splice(i, 1);
    monsterYs.splice(i, 1);
  }
  numOfMonsters = 3;
  step =3;
  
  for (i =0; i<numOfMonsters; i++)
    {
      monsterSpeeds[i] = random(-2,2);
      monsterXs[i] = random(0,width);
      monsterYs[i] = random(0,height);
    }
}

Also, while resetting, to go back to the original number of monsters, I splice their arrays keeping only some monsters and reset their speeds to the initial range (while keeping it random).

I also used other boolean flags to control the flow of the game and as a whole, I am very proud of the entire code – functions, class, etc – that I wrote.

//seg is class for the diver and step is the speed of the diver
let newSeg, segWidth = 60, step = 3;
let direction = 'start', gameState = 'start';

//booleans to store various game states/reset etc. startPlaying checks if game level has started, reset checks if the game is started again to reset the variables, playgameover stores if the game over song should be played or not
let startPlaying = false, reset = true, playgameover = true;

//for images and songs 
let img, diverImg, instrImg, bgImg, plasticImg, monsterImg, gameOverImg;
let song, gameOverSong, coinSong,powSong;
let cnv; 

//for scrolling background 
let x1 = 0, x2, scrollSpeed = 1;

//for the green plastic bottles 
let plasticX = 100, plasticY = 200, plasticWidth =70, plasticHeight = 70;

//to save randomly chosen x and y coordinates for the monsters
let monsterXs = [], monsterYs = [];
let monsterWidth =60, monsterHeight = 60; 

//initially num of monsters and array to store their randomly chosen speeds (within a range)
let numOfMonsters = 3, monsterSpeeds =[];
let highscore = 0, score = 0, life = 100;

//preload required images, songs, and fonts
function preload() {
  img = loadImage('images/1.png');
  song = loadSound('music.mp3');
  gameOverSong = loadSound('music/gameover.mp3');
  coinSong = loadSound('music/coin.mp3');
  powSong = loadSound('music/pow.mp3');
  bgImg = loadImage("images/game_bg.png");
  plasticImg = loadImage("images/plastic.png");
  monsterImg = loadImage("images/monster.png");
  gameOverImg = loadImage("images/gameOverImg.png");
  instrImg = loadImage("images/instructions.jpg");
  diverImg = loadImage("images/diver-1.png");
  regularFont ='Georgia';
  scoreFont = loadFont("fonts/Sunrise.ttf");
}

//class to represent the segment/object for the diver 
class Segment {
  constructor(x, y){
    this.x = x;
    this.y = y;
    noStroke();
    fill(55);
    ellipse(this.x, this.y, segWidth);
    image(diverImg, this.x-55, this.y-segWidth, 100,80);
  }
  //methods to change direction of diver
   moveRight(){
    if (this.x > width){
       this.x = 0;
     }else {
    this.x += step;
     }
    noFill();
    ellipse(this.x, this.y,segWidth);
    image(diverImg, this.x-55, this.y-35,100,80);
     
  }
   moveLeft(){
     if (this.x < 0){
       this.x = width;
     }
     else
    {
      this.x -= step;
    }
     //flip the image when diver moves left
     noFill();
    ellipse(this.x, this.y,segWidth);
     push();
     //flipping along x-axis to create turning effect
     scale(-1,1);
    image(diverImg, -(this.x+55), this.y-35,100,80);
     pop();
  }
   moveUp(){
     if (this.y < 0){
       this.y = height;
     }
     else{
       this.y -= step;
     }
     //fill(55);
     noFill();
    ellipse(this.x, this.y,segWidth);
     image(diverImg, this.x-55, this.y-35,100,80);
  }
   moveDown(){
     if (this.y > height){
       this.y = 0;
     }
     else {
       this.y += step;
     }
     noFill();     
    ellipse(this.x, this.y,segWidth);
    image(diverImg, this.x-segWidth, this.y-35,100,80);
  }
  updatePosition()
  {
    if (direction === 'right')
    {
     this.moveRight();
    }
  if (direction === 'left')
    {
      this.moveLeft();    
    }
  if (direction === 'up')
    {
      this.moveUp();
    }
  if (direction === 'down')
    {
      this.moveDown(); 
    }
  }
  //get and set methods
  getX(){
    return this.x;
  }
  getY(){
    return this.y;
  }
  blink(){
    fill(random(200,255));
    ellipse(this.x,this.y, segWidth);
  }
  setX(x){
    this.x=x;
  }
  setY(y){
    this.y=y;
  }
}

//during set up, play song, initialize the x and y coordinates for monsters, and create diver segment 
function setup() {
  cnv = createCanvas(600, 600);
 // frameRate(20);
  song.loop();
  
  //for scrolling background
  x2=width;
  newSeg = new Segment(width/2, height/2);
  for (i =0; i<numOfMonsters; i++)
    {
      append(monsterXs,random(0,width));
      append(monsterYs,random(0,height));
      append(monsterSpeeds, random(-2,2));
    }
  
  //increase speed of monsters and diver and increment number of monsters every 20 seconds when game is playing
  setInterval(increaseSpeed, 20000); 
}

function draw() {
//draw according to the state of the game 
  
  //reset game whenever game is started again
  if (gameState === 'start'){
    if (reset === true){
      resetGame();
      reset = false;
    }
    playgameover=true; //flag to check if game over song should be played or not
    startPage(); //if game started, show start page
  }
  else if (gameState === 'play'){
    playGame(); 
  }
  //switch song when game/level is over and show the stop page
  else if (gameState === 'stop'){
    song.stop();
    if(playgameover === true){
      gameOverSong.loop();
      playgameover = false;
    }
    stopPage();
  } 
}

function startPage(){
   textAlign(LEFT, BASELINE);
  image(img, 0, 0,600,600);
  noFill();
  //when mouse enters the canvas - display the start button
  if (mouseX< 580 && mouseX > 20 && mouseY < 580 && mouseY > 20) 
     {
      fill('rgba(10,108,180,0.85)');
      ellipse(width*4/5, height*5/6, 150,100);
      fill('white');
      textFont(scoreFont);
      textSize(32);
      text('Start',425, 513);
     } 
  //when mouse hovers over start button, change its color
    if (mouseX < width*4/5+75 && mouseX > width*4/5-75 && mouseY < height*5/6 + 50 && mouseY > height*5/6-50)
    {
      fill('#6338B1');
      ellipse(width*4/5, height*5/6, 170,120);
      textFont(scoreFont);
      textSize(38);
      fill('white')
      text('Start',415, 513);
      //if start button is pressed, change game state
      if (mouseIsPressed)
          {
            gameState = 'play'
          }
    
    }
  fill(255, 60, 100);
}

function playGame(){
  smooth();
  background('rgb(100,229,255)');
  showInstructions();
if (startPlaying === true){
    startPlayingGame();
  }
}

function showInstructions(){
  background('rgb(100,117,255)')
  fill(255)
  square(25,25,550);
  image(instrImg, 30,30,540,540);
  textSize(20);
  textFont(scoreFont)
  fill('white');
   textAlign(CENTER);
  text('Instructions', width/2, 100);
  textSize(18);
  
   textAlign(LEFT,BASELINE);
  text ('Welcome!\n\nToday, you are a diver. Your mission is to \ncollect as much plastic waste from the \nocean as possible to protect marine life.\n\nMove around using the left, right, up, and \ndown arrow keys on your keyboard to \ncollect trash.\n\nEvery piece of trash collected = +10 points\n\nBeware! The ocean has monsters! Life = 100. \nEach encounter with a monster = -10 life.\nCome on! Let us free the ocean from plastic \nwaste!', 70, 130, width);
  ellipse(width/2,555, 80);
  fill(0);
  text('GO', width/2-12,560)
  if (mouseX > width/2-50  && mouseX < width/2+50 && mouseY > 540-50 && mouseY < 540+50)
    {
      fill('rgb(221,166,252)');
       ellipse(width/2,555, 80);
      fill(255);
      text('GO', width/2-12,560)
      if(mouseIsPressed)
     {
       startPlaying = true;
     }
    }
}

function startPlayingGame()
{
  //scrolling background
  background('rgb(100,229,255)');
  image(bgImg, x1, 0, width, height);
  image(bgImg, x2,width, height);
  
  //starting text
  text('Use arrow keys to start moving', width/4, height/3);
  fill(55);
  ellipse(width/2, height/2, segWidth-20);
  
  //if player starts moving, begin updating position - start game
  if (direction === 'up' || direction === 'down' || direction === 'left' || direction === 'right' )
 {
   background('rgb(100,229,255)');
   image(bgImg, x1, 0, width, height);
   image(bgImg, x2, 0, width, height);
   
  x1 -= scrollSpeed;
  x2 -= scrollSpeed;
  
  if (x1 < -width){
    x1 = width;
  }
  if (x2 < -width){
    x2 = width;
  }
   newSeg.updatePosition();
   updatePlastic();
   createMonsters();
   checkPlasticCollisions();
   checkMonsterCollisions();
   displayScoreandLife();
   checkLife();
 }
}

function keyPressed() {
  if (keyCode === RIGHT_ARROW) {
    direction = 'right';
    //print('right')
  } else if (keyCode === LEFT_ARROW) {
    direction = 'left';
    //print('left');
  } else if (keyCode === UP_ARROW) {
    direction = 'up';
    //print('up')
  }
  else if (keyCode === DOWN_ARROW) {
    direction = 'down';
    //print('down')
  }
}

function updatePlastic(){
  image(plasticImg, plasticX, plasticY, plasticWidth, plasticHeight);

}
//for every monster, update its speed and if it collides with the walls, reverse their direction of motion
function createMonsters(){
  for (let i = 0; i < numOfMonsters; i++){
    image(monsterImg, monsterXs[i], monsterYs[i], monsterWidth, monsterHeight);
    
    monsterXs[i] += monsterSpeeds[i];
  if (monsterXs[i] < 0 || monsterXs[i]+monsterWidth > width)
    {
      monsterSpeeds[i] = -monsterSpeeds[i];
    }
  }
}
//if plastic collected, generate a new one on the canvas at a random location and update score
function checkPlasticCollisions(){
  //print(newSeg.getX(), " and ", plasticX);
  if (newSeg.getX() > plasticX && newSeg.getX() < plasticX + plasticWidth && newSeg.getY() > plasticY && newSeg.getY() < plasticY + plasticHeight)
    {
      coinSong.play();
      score += 10;
      plasticX = random(0,width-plasticWidth);
      plasticY = random(0,height-plasticHeight);
      image(plasticImg, plasticX, plasticY, plasticWidth, plasticHeight);
    }
}
//if monster collides collected, generate a new one on the canvas at a random location and decrement life
function checkMonsterCollisions(){
  for (let i = 0; i < numOfMonsters; i++){
    if (newSeg.getX() > monsterXs[i] && newSeg.getX() < monsterXs[i] + monsterWidth && newSeg.getY() > monsterYs[i] && newSeg.getY() < monsterYs[i] + monsterHeight)
    {

      powSong.play();
      newSeg.blink();
      monsterXs[i] = random(0,width-monsterWidth);
      monsterYs[i] = random(0,height-monsterHeight);
      life -=10;
    }
  }
}

//when life reaches zero, end the game
function checkLife(){
  if (life === 0){
    gameState = 'stop';
  }
}

function displayScoreandLife(){
  fill('rgb(138,63,196)')
  textFont(scoreFont);
  textSize(18);
  text('Highscore: ' + highscore + '  Score: '+ score, 10,20);
  text('Life remaining: '+ life, 390,20);
}

function stopPage(){
  background('rgb(100,229,255)');
  image(gameOverImg, 0,0,width,height);
  fill('white');
  textAlign(CENTER);
  textFont(scoreFont);
  textSize(24);
  if (score > highscore)
  {
    print('score is ' + score + ' highscore is ' + highscore);
    
    text('Congratulations. You helped free the ocean of harmful plastic and you just beat your previous highscore!', 0, height/2, width);
    fill('gold')
    text('New Highscore =  ' + score, 10, height*2/3, width);
  }
    else 
    { 
      fill('white');
       text('Congratulations. You helped free the ocean of harmful plastic!',10, height/2, width);
      
       text('Score =  ' + score + '\n\nHighscore = '+ highscore, 10, height*2/3, width);
    }
  fill('red')
  ellipse(width/2, 530,200,80);
  fill('white')
  text('Play Again', 295, 538);
  if (mouseX > width/2-100  && mouseX < width/2+100 && mouseY > 530-40 && mouseY < 530+40){
    fill('gold');
    ellipse(width/2, 530,200,80);
    fill('white')
    text('Play Again', 295, 538);
    if (mouseIsPressed){
      reset = true;
      gameOverSong.stop();
      gameState='start';
    }
  }
}

function resetGame(){
  gameOverSong.stop();
  song.loop();
  life =100;
  if (score > highscore){
    highscore = score;
  }
  score = 0;
  startPlaying=false;
  x1=0;
  x2=width;
  direction='start';
  newSeg.setX(width/2);
  newSeg.setY(height/2);
  
  for (i = numOfMonsters; i > 2;i--)
  {
    monsterSpeeds.splice(i, 1);
    monsterXs.splice(i, 1);
    monsterYs.splice(i, 1);
  }
  numOfMonsters = 3;
  step =3;
  
  for (i =0; i<numOfMonsters; i++)
    {
      monsterSpeeds[i] = random(-2,2);
      monsterXs[i] = random(0,width);
      monsterYs[i] = random(0,height);
    }
}

function increaseSpeed(){
  if(startPlaying === true){
  step +=0.5;
  
  append(monsterXs,random(monsterWidth,width-monsterWidth));
  append(monsterYs,random(monsterHeight,height-monsterHeight));
  append(monsterSpeeds, random(-2,2));
  numOfMonsters+=1;
  for (i =0; i < numOfMonsters; i++ ){
    if (monsterSpeeds[i]<0){
      monsterSpeeds[i]-=0.5;
    }
    else{
      monsterSpeeds[i]+=0.5
      }
  }
  }
}

Problems/Future Improvements

Some problems I mentioned earlier. Additionally, I was not able to stop the ‘game over’ song and it kept playing repeatedly at the same time. I figure out that it was due to the draw function being repeatedly called so I set up a flag to check if the song was already playing.

For the future, I can add coins/life randomly on the canvas so the user can “gain” life as well. Also, I can also make the plastic move as well. Different difficulty levels can also be designed instead of one continuous level with increasing difficulty. Also, the diver’s collision with the monster or plastic is not the most accurate and can be made more accurate for a more visually appealing effect.

As a whole, I really enjoyed revamping the snakes game to make one that I enjoy and to also raise awareness on an issue I care about.

Week 5: Midterm Progress Report

Concept

I am creating a game that I have named “Save the Ocean”. This game is inspired by the snakes game that I grew up playing. While I wanted to recreate that experience, I also wanted to add my own concept and content to the game. Therefore, I am using this game to create awareness about plastic pollution and how it affects our oceans and its wildlife.

In the game, the player will move from the start screen to the main screen after reading some instructions. When playing, the user will use arrow keys to move a character that cleans up plastic waste (just like a snake collects coins in the snake game). The more you clean, the more points you get.

Save ocean poster. Eco vector illustration about global save underwater nature from pollution, hand draw turtle in net with plastic bottles, concept cleanliness isolated on white background

To make it challenging, I have these ideas:

  1. Time limit: user has to collect all the garbage within a given time.
  2. Obstacles: user has to avoid obstacles

Depending on how challenging the concept is to implement, I will add more layers as needed to the project.

Game

Code

let segWidth = 30;
let step = 3;
let direction = 'start'
let gameState = 'start';
let begin = 'stop'
let startPlaying = false;
let img;
let song;
let cnv; 
function preload() {
  img = loadImage('images/1.png');
  song = loadSound('music.mp3');
}
class Segment {
  constructor(x, y){
    this.x = x;
    this.y = y;
    noStroke();
    fill('pink')
    ellipse(this.x, this.y, segWidth)
  }
  
   moveRight(){
    if (this.x > width){
       this.x = 0;
     }else {
    this.x += step;
     }
    ellipse(this.x, this.y,segWidth)
  }
   moveLeft(){
     if (this.x < 0){
       this.x = width;
     }
     else
    {
      this.x -= step;
    }
    ellipse(this.x, this.y,segWidth)
  }
   moveUp(){
     if (this.y < 0){
       this.y = height;
     }
     else{
       this.y -= step;
     }
    ellipse(this.x, this.y,segWidth);
  }
   moveDown(){
     if (this.y > height){
       this.y = 0;
     }
     else {
       this.y += step;
     }
    ellipse(this.x, this.y,segWidth)
  }
  updatePosition()
  {
    if (direction === 'right')
    {
     this.moveRight();
    }
  if (direction === 'left')
    {
      this.moveLeft();    
    }
  if (direction === 'up')
    {
      this.moveUp();
    }
  if (direction === 'down')
    {
      this.moveDown();
    }
  }
}

let segments = [];

function setup() {
  cnv = createCanvas(600, 600);
  song.play();
  append(segments,
  new Segment(width/2, height/2)
  )
}

function startPage(){
  image(img, 0, 0);
  noFill();
  if (mouseX< 580 && mouseX > 20 && mouseY < 580 && mouseY > 20) 
     {
      fill('lightblue');
      ellipse(width*4/5, height*5/6, 150,100);
      fill('rgb(246,237,237)');
      textFont('Helvetica');
      textSize(38);
      text('Start',440, 513);
      print("play option appears")
     } 
    if (mouseX < width*4/5+75 && mouseX > width*4/5-75 && mouseY < height*5/6 + 50 && mouseY > height*5/6-50)
    {
      fill('lightblue');
      ellipse(width*4/5, height*5/6, 150,100);
      if (mouseIsPressed)
          {
            gameState = 'play'
          }
    
    }
  fill(255, 60, 100);
  //text("(" + mouseX + ", " + mouseY + ")", mouseX, mouseY);
}

let currX = 300;
let currY = 300;

function draw() {
  
  if (gameState === 'start'){
    print('start');
    startPage();
  }
  else if (gameState === 'play'){
    print('play game')
    playGame();
  }

  
}

function keyPressed() {
  if (keyCode === RIGHT_ARROW) {
    direction = 'right';
    print('right')
  } else if (keyCode === LEFT_ARROW) {
    direction = 'left';
    print('left');
  } else if (keyCode === UP_ARROW) {
    direction = 'up';
    print('up')
  }
  else if (keyCode === DOWN_ARROW) {
    direction = 'down';
    print('down')
  }
}

function playGame(){
  smooth();
  background(220);
  showInstructions();
if (startPlaying === true){
    startPlayingGame();
  }

}
function showInstructions(){
  fill('lightblue')
  square(100,100,400);
  textSize(20);
  fill(0);
  text('This is where the instructions will go:', width/5, 200);
  text ('Up, down, left, and right arrows to move', width/5, 300);

  ellipse(width/2, height*2/3, 80);
  fill(255);
  text('GO', width/2-15,height*2/3+8)
  if (mouseIsPressed && mouseX > width/2-40  && mouseX < width/2+40)
    {
      print('start playing now')
      startPlaying = true;
    }
}

function startPlayingGame()
{
  background(220);
  text('Use arrow keys to start moving', width/4, height/3);
  fill('rgb(221,37,69)')
  ellipse(width/2, height/2, segWidth);
  if (direction === 'up' || direction === 'down' || direction === 'left' || direction === 'right' )
  {
   background(220);
   for (i = 0; i < segments.length; i++){
    segments[i].updatePosition(); 
  }
 }
}
function addSegment(){
  append(segments,new Segment(currX+step,currY+step))
}

I will be using object oriented programming structure and functions to design and organize the code. Text, shapes, and other elements used in class will be used.

Challenging Parts

What I find challenging at the moment is how to randomly generate obstacles and detect collisions. This is what I am figuring out now and then I will work on the design and aesthetics. Moreover, I want to check how to add video to the background or make the background move.

Week 4 – Text and Data Visualization

Concept

I wanted to combine both text and data visualization for this assignment so I decided to import a dataset containing countries with their respective latitude and longitudes. After importing these, I split each row of information by commas and saved the country name, longitude, and latitude in variables. I then used these variables to create objects for each country and saved their name along with their x and y positions. The initial x and y positions are the country’s longitude and latitude mapped onto the canvas and then these countries are made to move along the canvas while their original locations (represented by blinking points) stay static. The background is an image of the world map.

This piece represents the dynamism that exists between countries today. The world is super interlinked with countries staying connected, be it through trade, technology, or politics. Thus, this piece aims to represent our dynamic, ever-evolving, globalized world. Moreover, for interactivity, the mouse can be pressed to pause and play the motion of the countries.

Sketch and Code

For this sketch, I used the following as can be seen in my code:

  • Image
  • Data visualization (country dataset)
  • Text
  • OOP to create country objects
  • Mapping
  • Loops
  • Randomization
//declare global variables
let img;
let lat;
let lon;
let country;
let countries =[];
let minSpeedX =-1;
let maxSpeedX =1;
let minSpeedY =-2;
let maxSpeedY = 2;
let updateScreen =true;

//preload images and csv data to avoid errors/undefined values
function preload() {
  img = loadImage('image/3.png');//2
  // The text from the file is loaded into an array.
  strings = loadStrings("world.csv");
}

//set up a canvas
function setup() {
    createCanvas(600, 500);
    smooth();
    background(220);  
    image(img, 0, 0);
    //frameRate(50);
  // Top-left corner of the img is at (0, 0)
  // Width and height are the img's original width and height
  
  if (strings == null) {
    print("failed to load the file, stopping here");
    // this is an endless loop; it's a common way
    // to prevent a program from continuing when
    // something is so wrong that there is no sense
    // in continuing
    while (true) {}
  }
  print(
    "strings loaded from file successfully, read " + strings.length + " lines"
  );
  
  //use countries data from csv to find name, latitude, and longitude of each country and save each into country objects
  findcountryLocation();
 
}
function draw()
{
  //user can press mouse to pause and play the motion of the country names
   if(mouseIsPressed){
  if (updateScreen == true)
    {
      updateScreen = false;
    }
     else {
       if (updateScreen == false)
         {
           updateScreen = true;
         }
     }
}
  //whenever update screen is true, allow the countries to update/move
  if (updateScreen==true){
     image(img, 0, 0);
    for (let i = 0; i < countries.length; i++)
    {
      countries[i].update();
    
    }
  }
}
  //use countries data from csv to find name, latitude, and longitude of each country and save each into country objects
function findcountryLocation(){
  let row =[];
  // loop over each row in the file
  for (let csvRowNumber = 1; csvRowNumber < strings.length; csvRowNumber++) {
    // get a single row and split that row
    // into individual words
    row = split(strings[csvRowNumber], ",");
    lat = row[1];
    lon = row[2];
    country = row[3];
    //print(country, ' ', lat,' ', lon);
  
    //map the country's longitude and latitude within the limits of our canvas
    ypos = map(lat, -90, 90, 0, height);
    xpos = map(lon, -180,180, 0, width);
    
    //create country object to save name, x and y position in an array of countries
    newCountry = new my_country(country, xpos, ypos, random(minSpeedX, maxSpeedX), random(minSpeedY, maxSpeedY));
    countries.push(newCountry);
  } 
}
//class to save data of countries
class my_country {
  constructor(country, x, y, speedX, speedY){
    this.country = country;
    this.x = x;
    this.y = y; 
    this.speedX=speedX;
    this.speedY=speedY;
    this.pointX =x;
    this.pointY =y;
  }
  
  //function to move the country names
  update(){
    //move/translate the blinking points to center them on canvas
    push();
    translate(0,-80);
    fill(random(255));
    noStroke();
    ellipse(this.pointX,this.pointY,3,3);
    pop();
    
    //write the country name at x and y and update x and y to move the names
    //if the names touch the canvas boundary, change their direction so they bounce back
    fill('white');
    textSize(8)
    text(this.country, this.x, this.y);
    this.x+=this.speedX;
    this.y+=this.speedY;
    if (this.x > width || this.x < 0){
      this.speedX = -this.speedX
    }
    if (this.y > height || this.y < 0)
      {
        this.speedY = -this.speedY
      }
  }
}

Reflection and Further Improvements

I had a lot of fun working with both text and datasets as I loved how such a simple data import allowed for a random and intricate design to be created. I also enjoyed representing our world and its dynamism through this piece and something I realized that this type of art can help us do is convey a message or raise awareness. In future works, I would love to use other data sets to represent a problem in our world such as carbon emissions data or poverty rates, or convey a message/concept as I did for this piece. Something I can improve is the mapping of longitudes and latitudes as I felt the points did not perfectly map on the image and this is something I can further look into to improve.