Final Project: Horror Journal

It’s the final project! My project, named “Horror Journal”, is a personal diary simulator of sorts. This project is an interactive version of the novel “The Horla”. The diary has 39 entries in total, spanning from May to September.  Find out more about how it works below!

View post on imgur.com

Moving Through the Entries: 

Right in front of the user are two sheets of paper labeled “Previous Entry” and “Next Entry”. Below the hand icons printed on each paper is a Force Sensing Resistor (FSR) wired to the Arduino. Without user interaction, Arduino communicates a “0” to Processing. When the user pushes on the sheet, the FSR generates higher values. Once a specified threshold is passed, Arduino communicates a “1” to Processing. Processing then detects the “1” and switches to the next or previous diary entry depending on the user interaction.

Summoning the Demon: 

In the novel being played in the project, the character writing the diary entries is experiencing some sort of mysterious illness. By the end of the story, we learn that the character is being possessed by some sort of demon who he names “The Horla”. Since this is the biggest plot point in the story, I thought it would only make sense to incorporate it in the user interaction. For this interaction, I used a webcam, a Big Demon Glove (also known as a beanie), and a servo. In Processing, I read frames from the webcam and applied frame differencing on them to detect motion within the area captured by the webcam. This area is outlined by the big red sheet seen on the table. The servo I’m using is taped on the Big Demon Glove. On the servo, a small paper strip which reads “The Horla” is taped. When the user places the Big Demon Glove on the red sheet area, the camera detects the motion of servo, or the paper strip more accurately. This triggers a red strip on the screen to grow. Once the red strip reaches the bottom of the screen, the interaction is triggered: the screen turns red and the voice of the narrator turns deeper, imitating a stereotypical demonic voice. Pictures of this interaction can be seen below:

View post on imgur.com

View post on imgur.com

The Interface: 

Aside from the interactions, my project consists of the Processing screen, which displays the text of the diary entry being played, and audio readings of the current entry. The screen consists of yellow, red, and black colors. Originally, I wanted to create a more elaborate screen interface that simulates a personal room environment. I quickly realized that going with a simpler interface would make more sense with the interactions I planned. Additionally, the simpler interface looked generally cleaner and easier to follow. I was happy to hear that other people agreed with me.

Overall Thoughts: 

I’m glad I went with this project. I have received variety of reactions at the showcase ranging from “This is a very specific project” to “Wow! [the demonic voice] is creepy”. I noticed that a video game type project might have been more approachable in the showcase, but I still had many people come over to take a look at my project. One limitation might have been that no one would actually be able to sit through the entire story being narrated in my project since it is so lengthy. I did think about this possibility in my design phase. I thought about several solutions such as cutting down on the content of the story, but keeping everything in and allowing the user to navigate through the content on their own made more sense to me. I also wish I had booked a speaker early on. When I tested my project in the lobby, it sounded fine, but I did not anticipate how loud it would actually be during the showcase. Either way, I was able to present my project to everyone who came by, so I’m glad it worked out.

Credits: 

Audio Reading:

Text:

http://www.eastoftheweb.com/short-stories/UBooks/Horl.shtml

Processing Code: 

import processing.sound.*;
import processing.video.*;
import processing.serial.*;

Serial myPort;
int prev_left=0;
int prev_right=1;
int left=0;
int right=0;
boolean onOff=false;
boolean onOff2=false;

Capture video;
int vidX, vidY;
PImage prevFrame;
float motion_threshold=20;

SoundFile s_bg, s_clock, s_breathing, s_drinkwater;
SoundFile[] entries_audio;
PImage paper;
PFont font;
String[] entries;
int entries_cnt = 39;
int paper_offset_y = 0;
int page_start_i = 0;

int letter_i = 0;

int red_level = 0;

int vid_width = 640;
int vid_height = 480;

color bg_yellow = color(229, 229, 144);
color bg_red = color(183, 0, 0);
color bg_color = bg_yellow;
color text_color = color(0);
color bar_color = bg_red;
float bg_phase = 0;
float bar_phase = 0;
float text_phase = 0;
float audio_phase = 0.8;

int entry_switch_offset = 0;
int entry_switch_threshold = 10;

int prev_entry = 0;
int curr_entry = 0;

int text_speed = 2;

float avgX = 0;
float avgY = 0;

String may = "8, 12, 16, 18, 25";
String june = "2, 3";
String july = "2, 3, 4, 5, 6, 10,\n12, 14, 16, 19, 21, 30";
String august = "2, 4, 6, 7, 8, 9,\n10, 11, 12, 13, 14, 15,\n16, 17, 18, 19, 20, 21,\n22";
String september = "10";

void setup() {
  fullScreen();
  background(bg_color);

  printArray(Serial.list());
  String portname=Serial.list()[0];
  //println(portname);
  myPort = new Serial(this, portname, 9600);
  myPort.clear();
  myPort.bufferUntil('\n');

  String[] cameras = Capture.list();
  video = new Capture(this, width, height, cameras[1]);
  video.start();
  prevFrame=createImage(width, height, RGB);

  paper = loadImage("paper.jpg");

  font = createFont("SyneMono-Regular.ttf", 25);
  textFont(font);

  entries_audio = new SoundFile[entries_cnt];
  for (int i=0; i<entries_cnt; i++) {
    entries_audio[i] = new SoundFile(this, "entries_audio/"+i+".mp3");
  }
  s_bg = new SoundFile(this, "sound/Ambience/night-crickets-ambience-on-rural-property.wav");
  s_clock = new SoundFile(this, "sound/Ambience/loopable-ticking-clock.wav");
  s_breathing = new SoundFile(this, "sound/Human/breath-male.wav");
  s_drinkwater = new SoundFile(this, "sound/Human/drink-sip-and-swallow.wav");
  s_bg.loop(1, 0.5);
  s_clock.loop(1, 0.1);

  entries = new String[entries_cnt];
  for (int i=0; i<entries_cnt; i++) {
    entries[i] = readFile("entries/"+i+".txt");
  }
}

void draw() {
  background(bg_color);
  entry_switch_offset++;
  textSpeed();
  drawPaper();
  entrySwitch();
  detectMotion();
  playEntry(entries_audio[prev_entry], entries_audio[curr_entry]);
  displayCalendar();
  prev_left = left;
  prev_right = right;
}

void serialEvent(Serial myPort) {
  String s=myPort.readStringUntil('\n');
  s=trim(s);
  if (s!=null) {
    int values[]=int(split(s, ','));
    if (values.length==2) {
      left=(int)values[0];
      right=(int)values[1];
    }
  }
  myPort.write(int(onOff)+","+int(onOff2)+"\n");
}


void drawPaper() {
  imageMode(CENTER);
  writeText(entries[curr_entry]);
}

void writeText(String text) {
  int x = (width/2) - (paper.width/6) + 70;
  int y = (height/2) - (paper.height/6) + 70;
  int char_width = 0;
  int char_row = 0;
  String date = "";
  int c = 0;
  while (text.charAt(c) != '.') {
    date = date + text.charAt(c);
    c++;
  }
  if (page_start_i == 0) {
    page_start_i = c+2;
  }

  pushMatrix();
  textSize(40);
  text(date, x, 80);
  popMatrix();

  pushMatrix();
  textSize(25);
  translate(x, y + paper_offset_y);
  fill(text_color);

  if (entry_switch_offset > entry_switch_threshold) {
    if (frameCount%text_speed == 0 && letter_i < text.length() && text.charAt(letter_i) != ' ') {
      letter_i++;
    } else if (letter_i < text.length() && text.charAt(letter_i) == ' ') {
      letter_i++;
    }
    for (int i=page_start_i; i < letter_i; i++) {
      char_width += textWidth(text.charAt(i));
      text(text.charAt(i), char_width, char_row*30);


      if (x + char_width >= (width/2) + (paper.width/6) - 160 && text.charAt(i) == ' ') {
        char_row++;
        char_width = 0;
      }
      if (text.charAt(i) == '\n') {
        char_row++;
        char_width = 0;
      }
    }

    if (char_row > 19) {
      page_start_i = letter_i;
    }
  }
  popMatrix();
}

String readFile(String path) {
  String s = "";
  String[] arr = loadStrings(path);
  for (int i=0; i<arr.length; i++) {
    s = s + '\n' + arr[i];
  }
  return s;
}

void detectMotion() {
  if (video.available()) {
    prevFrame.copy(video, 0, 0, width, height, 0, 0, width, height);
    prevFrame.updatePixels();
    video.read();
  }
  video.loadPixels();
  prevFrame.loadPixels();
  loadPixels();
  float totalMotion=0;
  for (int y=0; y<height; y++) {
    for (int x=0; x<width; x++) {
      int loc = (video.width-x-1)+(y*width);
      color pix=video.pixels[loc];
      color prevPix=prevFrame.pixels[loc];
      float r1=red(pix);
      float g1=green(pix);
      float b1=blue(pix);
      float r2=red(prevPix);
      float g2=green(prevPix);
      float b2=blue(prevPix);
      float diff=dist(r1, g1, b1, r2, g2, b2);
      totalMotion+=diff;
      if (diff>motion_threshold) {
        avgX += x;
        avgY += y;
      }
    }
  }
  float avgMotion=totalMotion/pixels.length;
  avgX = avgX/pixels.length;
  avgY = avgY/pixels.length;
  fill(0);
  text(avgX + ' ' + avgY, 200, 800);
  if (avgMotion>motion_threshold && frameCount%1 == 0 && red_level <= height+10) {
    red_level += 10;
  } else if (frameCount%2 == 0 && red_level >= -5) {
    red_level -= 3;
  }
  video.updatePixels();
  prevFrame.updatePixels();
  updatePixels();
  if (avgMotion>motion_threshold) {
    fill(0);
    ellipse(width/2, 100, 30, 30);
  }
  pushMatrix();
  barSwitch();
  fill(bar_color);
  noStroke();
  rect(400, 0, 15, red_level);
  popMatrix();
  bgSwitch();
}

void bgSwitch() {
  if (red_level >= height) {
    bg_color = bg_red;
    bg_phase = 0;
    text_color = color(255);
  } else if (bg_color != bg_yellow && bg_phase<1) {
    bg_phase = phaseFade(bg_phase);
    text_phase = phaseFade(text_phase);
    bg_color = colorFade(bg_color, bg_red, bg_yellow, bg_phase);
    text_color = colorFade(text_color, color(255), color(0), text_phase);
  } else {
    bg_color = bg_yellow;
    text_color = color(0);
    bg_phase = 0;
    text_phase = 0;
  }
}

void barSwitch() {
  if (red_level >= height) {
    bar_color = bg_yellow;
    bar_phase = 0;
  } else if (bar_color != bg_red && bar_phase<1) {
    bar_color = colorFade(bar_color, bg_yellow, bg_red, bar_phase);
    bar_phase = phaseFade(bar_phase);
  } else {
    bar_color = bg_red;
    bar_phase = 0;
  }
}

color colorFade(color curr, color from, color to, float phase) {
  if (frameCount%10 == 0) {
    return lerpColor(from, to, phase);
  }
  return curr;
}

float phaseFade(float phase) {
  if (frameCount%10 == 0) {
    return phase + 0.01;
  }
  return phase;
}

void playEntry(SoundFile preventry, SoundFile currentry) {
  audioSpeed();
  if (!currentry.isPlaying()) {
    if (preventry.isPlaying()) {
      preventry.stop();
    }
    currentry.play();
  }
}

void audioSpeed() {
  if (red_level >= height && bg_color == bg_red) {
    audio_phase = 0.8;
  } else if ( bg_color != bg_yellow && frameCount%10 == 0) {
    audio_phase += 0.002;
  } else if (bg_color == bg_yellow) {
    audio_phase = 1;
  }
  entries_audio[curr_entry].rate(audio_phase);
}

void entrySwitch() {
  if (left == 1 & prev_left == 0  && curr_entry > 0) {
    prev_entry = curr_entry;
    curr_entry--;
    page_start_i = 0;
    letter_i = 0;
  }
  if (right == 1 && prev_right == 0  && curr_entry < entries_cnt-1) {
    prev_entry = curr_entry;
    curr_entry++;
    page_start_i = 0;
    letter_i = 0;
  }
}

void textSpeed() {
  if (red_level >= height) {
    text_speed = 4;
  } else {
    text_speed = 3;
  }
}

void displayCalendar() {
  int offset = 40;
  text("May", 50, 150);
  text("June", 50, 250);
  text("July", 50, 350);
  text("August", 50, 500);
  text("September", 50, 720);
  fill(text_color);
  text(may, 50, 150+offset);
  text(june, 50, 250+offset);
  text(july, 50, 350+offset);
  text(august, 50, 500+offset);
  text(september, 50, 720+offset);
  
  fill(bar_color);
}

Arduino Code:

#include <Servo.h>

#define right_fsr_pin A1
#define left_fsr_pin A0
#define threshold 900

int right_fsr_val;
int left_fsr_val;

int left = 0;
int right = 0;

Servo myServo;
const int servoPin = 3;

long currmillis = 0;
int angle = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("0,0");
  myServo.attach(servoPin);
}

void loop() {
  while (Serial.available()) {
    right = Serial.parseInt();
    left = Serial.parseInt();
    if (Serial.read() == '\n') {
      left_fsr_val = analogRead(left_fsr_pin);
      delay(1);
      right_fsr_val = analogRead(right_fsr_pin);
      delay(1);

      if (left_fsr_val >= threshold) {
        left_fsr_val = 1;
      } else {
        left_fsr_val = 0;
      }
      if (right_fsr_val >= threshold) {
        right_fsr_val = 1;
      } else {
        right_fsr_val = 0;
      }

      Serial.print(left_fsr_val);
      Serial.print(',');
      Serial.println(right_fsr_val);
    }
  }

    if (currmillis + 1000 <= millis()) {
      if (angle <= 10) {
        angle = 180;
      } else {
        angle = 0;
      }
      myServo.write(angle);
      currmillis = millis();
    }
}

 

Final Project Update

For the final project, I have been working on creating a “personal diary simulator”. I chose the fictional horror short story “The Horla” to build my project around. I used a text and audio reading of the story that I found online.

When the user runs the file, the sketch will run on full screen. The background is a yellow-ish color. The date of the current diary entry will be displayed on the upper part of the screen, and the text of the entry will start appearing on the middle of the screen. The audio reading of the entry will also start playing.

I have been working on three types of interactions in the project:

Camera-based interaction: 

I used frame differencing for this interaction. The camera will be set up downwards, facing a table. The interaction will be triggered when the user places their hands over the table and/or move it. When that happens, a red strip (rectangle) will appear on the left side and start expanding in height as long as the user has their hand on top of the table. If the user removes their hands, the strip will start shrinking. If the strip grows all the way to the bottom of the screen, the background’s color will turn red. When the user removes their hand, the screen will slowly transition from the red color to the original yellow color.

To implement: When the red screen is triggered, the audio reading of the entry will change to a deeper/demonic voice and will start transitioning back to the normal voice as the screen’s color turns yellow.

Selecting Diary entry:

In this interaction, the user will be able to move through the different entries of the diary. I’m still thinking of the best way to implement this interaction. I don’t want this interaction to unintentionally trigger the camera-based, so I want to make it separate from the table.

To implement: I have implemented the transition between texts of different entries, but I’m having trouble implementing the audio transition. For some reason, the function for playing the audio does not work. I still have to fix this.

Vibration Interaction (maybe?): 

For this interaction, I want to play an audio file of a gasp or heavy breathing whenever there is some vibration detected on the table. The text/sketch screen would also shake for a brief moment.

To implement: I have not started working on this interaction. I have thought of using the camera captured video to implement this interaction, but I think it would interfere too much with the other camera-based interaction. I’m not sure if there is a type of sensor that can help with implementing this interaction. I will think of different ways to do it.

User Testing:

Code (Mid-debugging Sorry!):

import processing.sound.*;
import processing.video.*;

Capture video;
int vidX, vidY;
PImage prevFrame;
float motion_threshold=38;

SoundFile s_bg, s_clock, s_breathing, s_drinkwater;
SoundFile[] entries_audio;
PImage paper;
PFont font;
String[] entries;
int entries_cnt = 39;
int paper_offset_y = 0;
int page_start_i = 0;

int letter_i = 0;

int red_level = 0;

int vid_width = 640;
int vid_height = 480;

color bg_yellow = color(229, 229, 144);
color bg_red = color(183, 0, 0);
color bg_color = bg_yellow;
color text_color = color(0);
color bar_color = bg_red;
float bg_phase = 0;
float bar_phase = 0;
float text_phase = 0;

int entry_switch_offset = 0;
int entry_switch_threshold = 10;

int prev_entry = 0;
int curr_entry = 0;

void setup() {
  //size(1000,600);
  fullScreen();
  background(bg_color);

  String[] cameras = Capture.list();
  video = new Capture(this, cameras[1]);
  video.start();
  prevFrame=createImage(width, height, RGB);

  paper = loadImage("paper.jpg");

  font = createFont("SyneMono-Regular.ttf", 25);
  textFont(font);

  entries_audio = new SoundFile[18];
  for (int i=0; i<18; i++) {
    entries_audio[i] = new SoundFile(this, "entries_audio/"+i+".mp3");
  }
  s_bg = new SoundFile(this, "sound/Ambience/night-crickets-ambience-on-rural-property.wav");
  s_clock = new SoundFile(this, "sound/Ambience/loopable-ticking-clock.wav");
  s_breathing = new SoundFile(this, "sound/Human/breath-male.wav");
  s_drinkwater = new SoundFile(this, "sound/Human/drink-sip-and-swallow.wav");
  entries_audio[curr_entry].play();
  //s_bg.loop(1, 0.5);
  //s_clock.loop(1, 0.1);
  //s_breathing.loop();
  //s_drinkwater.loop(0.7);

  entries = new String[entries_cnt];
  for (int i=0; i<entries_cnt; i++) {
    entries[i] = readFile("entries/"+i+".txt");
  }
}

void draw() {
  background(bg_color);
  entry_switch_offset++;
  drawPaper();
  detectMotion();
  updateEntry();
  //playEntry(entries_audio[prev_entry], entries_audio[curr_entry]);
  //if (entries_audio[prev_entry].isPlaying()) {
  //  entries_audio[prev_entry].stop();
  //}
  //if (entry_switch_offset == entry_switch_threshold) {
  //  //entry_switch_offset = 0;
  //  entries_audio[curr_entry].play();
  //  println("in if");
  //}
  //entries_audio[curr_entry].play();
  //println("offset" + " " + entry_switch_offset);
  //println("threshold" + " " + entry_switch_threshold);
}

void drawPaper() {
  imageMode(CENTER);
  //image(paper, width/2, height/2, paper.width/3, paper.height/3);
  writeText(entries[curr_entry]);
}

void writeText(String text) {
  int x = (width/2) - (paper.width/6) + 70;
  int y = (height/2) - (paper.height/6) + 70;
  int char_width = 0;
  int char_row = 0;
  String date = "";
  int c = 0;
  while (text.charAt(c) != '.') {
    date = date + text.charAt(c);
    c++;
  }
  if (page_start_i == 0) {
    page_start_i = c+2;
  }

  pushMatrix();
  textSize(40);
  text(date, x, 80);
  popMatrix();

  pushMatrix();
  textSize(25);
  translate(x, y + paper_offset_y);
  fill(text_color);

  if (entry_switch_offset > entry_switch_threshold) {
    if (frameCount%2 == 0 && letter_i < text.length()) {
      letter_i++;
    }
    for (int i=page_start_i; i < letter_i; i++) {
      char_width += textWidth(text.charAt(i));
      text(text.charAt(i), char_width, char_row*30);

      if (x + char_width >= (width/2) + (paper.width/6) - 160 && text.charAt(i) == ' ') {
        char_row++;
        char_width = 0;
      }
      if (text.charAt(i) == '\n') {
        char_row++;
        char_width = 0;
      }
    }

    if (char_row > 10) {
      page_start_i = letter_i;
    }
  }
  popMatrix();
}

String readFile(String path) {
  String s = "";
  String[] arr = loadStrings(path);
  for (int i=0; i<arr.length; i++) {
    s = s + '\n' + arr[i];
  }
  return s;
}

void detectMotion() {
  if (video.available()) {
    prevFrame.copy(video, 0, 0, width, height, 0, 0, width, height);
    prevFrame.updatePixels();
    video.read();
  }
  video.loadPixels();
  prevFrame.loadPixels();
  loadPixels();
  float totalMotion=0;
  for (int y=0; y<vid_height; y++) {
    for (int x=0; x<vid_width; x++) {
      int loc = (video.width-x-1)+(y*vid_width);
      //println(video.width);
      color pix=video.pixels[loc];
      color prevPix=prevFrame.pixels[loc];
      float r1=red(pix);
      float g1=green(pix);
      float b1=blue(pix);
      float r2=red(prevPix);
      float g2=green(prevPix);
      float b2=blue(prevPix);
      float diff=dist(r1, g1, b1, r2, g2, b2);
      totalMotion+=diff;
    }
  }
  float avgMotion=totalMotion/(vid_width*vid_height);
  if (avgMotion>motion_threshold && frameCount%2 == 0 && red_level <= height+10) {
    red_level += 8;
  } else if (frameCount%2 == 0 && red_level >= -5) {
    red_level -= 3;
  }
  //println(avgMotion);
  video.updatePixels();
  prevFrame.updatePixels();
  updatePixels();
  pushMatrix();
  barSwitch();
  fill(bar_color);
  noStroke();
  rect(300, 0, 15, red_level);
  //text(avgMotion, 200, 200);
  popMatrix();
  bgSwitch();
}

void bgSwitch() {
  if (red_level >= height) {
    bg_color = bg_red;
    text_color = color(255);
  } else if (bg_color != bg_yellow && bg_phase<1) {
    bg_color = colorFade(bg_color, bg_red, bg_yellow, bg_phase);
    text_color = colorFade(text_color, color(255), color(0), text_phase);
    bg_phase = phaseFade(bg_phase);
    text_phase = phaseFade(text_phase);
  } else {
    bg_color = bg_yellow;
    text_color = color(0);
    bg_phase = 0;
    text_phase = 0;
  }
}

void barSwitch() {
  if (red_level >= height) {
    bar_color = bg_yellow;
  } else if (bar_color != bg_red && bar_phase<1) {
    bar_color = colorFade(bar_color, bg_yellow, bg_red, bar_phase);
    bar_phase = phaseFade(bar_phase);
  } else {
    bar_color = bg_red;
    bar_phase = 0;
  }
}

color colorFade(color curr, color from, color to, float phase) {
  if (frameCount%10 == 0) {
    return lerpColor(from, to, phase);
  }
  return curr;
}

float phaseFade(float phase) {
  if (frameCount%10 == 0) {
    return phase + 0.01;
  }
  return phase;
}

void updateEntry() {
  if (keyPressed && keyCode == LEFT && curr_entry > 0) {
    prev_entry = curr_entry;
    curr_entry--;
    //println(curr_entry);
    page_start_i = 0;
    letter_i = 0;
  }
  if (keyPressed && keyCode == RIGHT && curr_entry < 18) {
    prev_entry = curr_entry;
    curr_entry++;
    //println(curr_entry);
    page_start_i = 0;
    letter_i = 0;
  }
}

void playEntry(SoundFile preventry, SoundFile currentry) {
  if (preventry.isPlaying()) {
    preventry.stop();
  }
  if (entry_switch_offset == entry_switch_threshold) {
    currentry.play();
  }
}

Final Project Progress

Idea:

My project will be a first-person POV diary storytelling simulator-ish. I want to select a theme around which the stories in the diary will revolve. I first thought about WW2 diaries, but I found it difficult to find excerpts from those diaries on the internet. I thought about using fictional diaries, but I will keep this as a choice to consider if I fail to find sufficient diary excerpts for my game. I will also consider themes other than WW2 as I develop my project as I think the theme will not affect the technical part too much.

Materials Needed:

My project’s visual element will run completely on Processing, so I don’t anticipate needing special material for that part. For the interactive part of my project, I would like to have switches/controllers that work similarly to the arduino kit’s potentiometer and digital switches, but are larger and don’t need to be connected directly to the breadboard. For the potentiometer style controller, I’m thinking of a controller with the shape of the sketch below.

View post on imgur.com

The red material will be an insulator. The blue material will be a conductor. To interact with the controller, the player will change the angle of the external insulator part. Each blue pin attached to the insulator will be associated with an angle degree that will be communicated to processing to manipulate the game parameters. For the digital switch type controller, I think I will a box with a squishy material that separate the button from the bottom of the box. On the bottom of the box, there will be a pressure sensor. The player will push the button and the sensor will then communicate numerical values to Processing which will be used to manipulate the parameters of the game.

Difficult Parts:

I think the visual part of the project will be difficult. At first, I thought of changing the speed of writing or change the text being written depending on how the player interacts with the game, but now, I think I will keep the text and speed constant and add different elements into the game such as audio files of the text being read. Additionally, since the game visual is meant to be in a first person POV, I think adding some type of motion to that affects all objects in the game would be more engaging. This would simulate head movements or such. I think it would be strange to have the environment completely still in a first-person POV.

Final Project Proposal

For the final project, I would like to make some interactive processing sketch that emulates a personal diary and is controlled by arduino.

Idea: 

The interface will be in first person. The player will see diary notes being written automatically in slow speed similar to the writing speed of an actual person. This will simulate the act of a person writing their own diary. The text of the diary will be loaded and defined in the code itself. There will be various parameters changing that will affect the first person display the user will be viewing. These parameters could include level of consciousness and hunger. The goal of the user is to keep the levels of those parameters in check. If those parameters pass a certain threshold, the text being written will start turning into gibberish. Finally, if the levels of the parameters max out, the writer passes out and the screen blacks out.

My goal is to make this a story-driven experience, where the player needs to ensure the game does not end in order to complete reading the diary notes. I plan to source the diary notes from actual diaries that could be considered interesting for some reason.

Arduino: 

I plan to use digital switches and analog switches (such as the potentiometer) to give the player a way to control the changing parameters in the game. Depending on my final implementation, I could have some elements on the display which receive feedback from the switches and change somehow.

Processing: 

One way to implement alterations in the display environment is to simulate a desk on which the diary notes and other objects lie. The user’s interaction with different switches could alter different objects on the desk in some way.

[Badr & Ziyad] Week 10: Music Instrument

For this week’s assignment, Badr and I tried to build a simple musical instrument using a servo to emulate a simple drum and a buzzer to play a melody.

Drum: 

We defined three different drum modes (beats) that can be looped over using a pushbutton. Every time the button is clicked, the drum moves to the next mode. Originally, we planned to use two servos (drums), but it turned out noisier than we thought, so we decided to use only one. To move the servos, we have used servo.write().

Millis():

To avoid using delay() which will affect the interaction, we have used Millis to set breaks. For that, we had to create an array to store the duration of the breaks, variables to store the elapsed time, and if conditions. 

unsigned long curr = millis(); // store the elapsed time
bool played[] = {1, 0, 0, 0}; // boolean variables to control the modes
int period[]= {200, 150, 150, 200}; // Tempo of the servo (breaks)
int curr_prd=0; // keep track of the current break

Beats (Modes): 

To alternate between the modes, we have used if/else conditions, boolean variables, digitalRead() to read the state of the push button and a prevButton variable to store the previous state.

// switch between the modes
  // default mode
  if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==0){
    level2=1;
    period[0] = 200; period[1] = 150; period[2] = 150; period[3] = 200;
  }
  // mode 3
  else if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==1){
    level3=0;
    period[0] = 300; period[1] = 300; period[2] = 300; period[3] = 300;
  }
  // mode 2
  else if (!prevbutton && digitalRead(3)==HIGH && level2==1 && level3==0){
    level3=1; level2=0;
    period[0] = 60; period[1] = 60; period[2] = 60; period[3] = 60;
  }

Moving the Servo:

// set the tempo for level 3
  if (curr-prev>=period[curr_prd] && level2==0 && level3==1){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // set the tempo for level 2
  if (curr-prev>=period[curr_prd] && level2==1 && level3==0){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // set the tempo for the default level
  if (curr-prev>=period[curr_prd] && level2==0 && level3==0){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // Move the servo
  if (played[0]){servo_r.write(90);}
  if (played[1]){servo_r.write(120);}
  if (played[2]){servo_r.write(100);}
  if (played[3]){servo_r.write(120);}

Buzzer: 

We chose to play the tetris theme on the buzzer, and used the ultrasonic sensor to change the speed at which the melody is being played. The larger the values read by the sensor are, the slower the music will play. 

View post on imgur.com

Melody:

To play the melody, we stored the convenient notes in an array (we got them online), then proceeded to calculate how long the note is using sizeof() and the tempo which is 160.

int tempo = 160; // Tempo of the song // Melody notes 
int melody[] = { NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_A4, NOTE_GS4, NOTE_B4, REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_E5, NOTE_A5, NOTE_GS5 };

// Calculate the duration of each note
int notes= sizeof(melody) / sizeof(melody[0]) / 2; 
int note = (60000 * 4) / tempo;
int divider = 0, duration = 0;

// Calculate the duration of each note
int notes= sizeof(melody) / sizeof(melody[0]) / 2; 
int note = (60000 * 4) / tempo;
int divider = 0, duration = 0;

Ultrasonic Sensor:

Depending on the distance measured by the sensor, the speed of the song is changing. For that, we used map() to modify it within a certain range. We also had to convert microseconds to cm.

// get the distance using the ultrasonic sensor
  long duration, cm;
  digitalWrite(9, LOW);
  delayMicroseconds(2);
  digitalWrite(9, HIGH);
  delayMicroseconds(10);
  digitalWrite(9, LOW);
  duration = pulseIn(8, HIGH);
  // convert microseconds to cm
  cm = duration/29/2;
  if (cm>20) {
   cm = 20;
  }
  // map the distance form 80 to 150 to fit 
  cm = map(cm,0,20,80,150);

Challenges: 

The biggest challenge we faced was getting the drum to work without using delay(). Using delay() would have affected the interaction with the button and sensor, so we used millis() instead. When we tried to implement the millis() solution, the drum’s playing started to become irregular and sometimes stopped working after some time. To fix this, we used arrays to keep track of the angles and timings of each angle change of the servo. The code started working properly after we introduced this fix and restructured the logic we used in the loop.

We also faced some issues with taping of our servo and stick coming loose occasionally while working on the project.

Code: 

#include <Servo.h>
#include "notes.h"

Servo servo_r;
unsigned long prev=0; // variable to store the time
bool played[] = {1, 0, 0, 0}; // boolean variables to control the modes
int period[]= {200, 150, 150, 200}; // Tempo of the servo (breaks)
int curr_prd=0; // keep track of the current break
bool level2=0; // Mode 2
bool level3=0; // Mode 3
bool prevbutton=0; // store the previous state of the pushbutton
int prevnote = 0; // store the prev note
int currnote = 0; // store the current note
int tempo = 160; // Tempo of the song
// Melody notes
int melody[] = {
  NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4,
  NOTE_A4, NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5,
  NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4,
  NOTE_A4, NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5,
  NOTE_A5, NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5,
  NOTE_D5, NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5,
  NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, 
  NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4,
  NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4,
  NOTE_C5, NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4,
  NOTE_A4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_F5, NOTE_A5,
  NOTE_G5, NOTE_F5, NOTE_E5, NOTE_C5, NOTE_E5, NOTE_D5,
  NOTE_C5, NOTE_B4, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_E5,
  NOTE_C5, NOTE_A4, NOTE_A4, REST, NOTE_E5, NOTE_C5,
  NOTE_D5, NOTE_B4, NOTE_C5, NOTE_A4, NOTE_GS4, NOTE_B4,
  REST, NOTE_E5, NOTE_C5, NOTE_D5, NOTE_B4, NOTE_C5, NOTE_E5,
  NOTE_A5, NOTE_GS5
};
// Calculate the duration of each note
int notes= sizeof(melody) / sizeof(melody[0]) / 2; 
int note = (60000 * 4) / tempo;
int divider = 0, duration = 0;


void setup() {
  Serial.begin(9600);
  servo_r.attach(6); // Set the servo
  pinMode(3, INPUT); // Set the button pinmode
  pinMode(9, OUTPUT); // set the echo pin for the sensor
  pinMode(8, INPUT); // set the pin for the sensor
}

void loop() {
  // switch between the modes
  // default mode
  if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==0){
    level2=1;
    period[0] = 200; period[1] = 150; period[2] = 150; period[3] = 200;
  }
  // mode 3
  else if (!prevbutton && digitalRead(3)==HIGH && level2==0 && level3==1){
    level3=0;
    period[0] = 300; period[1] = 300; period[2] = 300; period[3] = 300;
  }
  // mode 2
  else if (!prevbutton && digitalRead(3)==HIGH && level2==1 && level3==0){
    level3=1; level2=0;
    period[0] = 60; period[1] = 60; period[2] = 60; period[3] = 60;
  }

  unsigned long curr = millis(); // store the elapsed time

  // set the tempo for level 3
  if (curr-prev>=period[curr_prd] && level2==0 && level3==1){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // set the tempo for level 2
  if (curr-prev>=period[curr_prd] && level2==1 && level3==0){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // set the tempo for the default level
  if (curr-prev>=period[curr_prd] && level2==0 && level3==0){
    prev=curr;
    // change the array parameters
    if (played[0]==1){played[0]=0; played[1]=1;}
    else if (played[1]==1){played[1]=0; played[2]=1;}
    else if (played[2]==1){played[2]=0; played[3]=1;}
    else if (played[3]==1){played[3]=0; played[0]=1;}
    curr_prd=(curr_prd+1)%4;
  }
  // Move the servo
  if (played[0]){servo_r.write(90);}
  if (played[1]){servo_r.write(120);}
  if (played[2]){servo_r.write(100);}
  if (played[3]){servo_r.write(120);}

  // read the state of the button
  prevbutton = digitalRead(3);

  // get the distance using the ultrasonic senor
  long duration, cm;
  digitalWrite(9, LOW);
  delayMicroseconds(2);
  digitalWrite(9, HIGH);
  delayMicroseconds(10);
  digitalWrite(9, LOW);
  duration = pulseIn(8, HIGH);
  // convert microseconds to cm
  cm = duration/29/2;
  if (cm>20) {
   cm = 20;
  }
  // map the distance form 80 to 150 to fit 
  cm = map(cm,0,20,80,150);

  if (curr-prevnote >= cm) {
    prevnote = curr;
    // play the note
    tone(11, melody[currnote], duration*0.9);
    // loop over the array
    currnote = (currnote + 1) % 100;
  }
}

Week 9: Rain Mayhem

For this week’s task, I used several LEDs, a photoresistor, and a button to simulate a rare event in Abu Dhabi: rain.

LEDs:

I used four colors of LEDs:

  1. blue to represent rain
  2. red and yellow to represent the social chaos caused by the rain;
  3. green to represent the calm state of no rain

The blue and green LEDs are connected individually in parallel, while the other six red and yellow LEDs are connected in pairs of series, which are then connected in parallel.

Photoresistor: 

For this task, I thought that using the values read directly from the photoresistor would not make sense because I’m only considering two states: rain and no rain. For this reason, I decided to set threshold variables. If the photoresistor value exceeds the variable, then it is considered not raining, and if the photoresistor value is lower than the threshold then it is considered raining. This can also be abstracted as the presence or lack of rain clouds.

Button:

The button in this task represents some control over the chaos (yellow and red LEDs) present while it’s raining. If it is raining and the button is pressed, then the chaos will stop. This can also be abstracted as some kind of enforcement of social distancing regulations which might be broken in the rain mayhem.

Limitations(?):

I had to change my threshold variable multiple times while working on the project because the lighting in my surroundings changed. I think for this project to work correctly, the threshold value has to be updated every time it is run.

Below are the visuals and code of the task:

View post on imgur.com

const int knob = A0;
const int calmLed = 3;
bool raining = 0;
int threshold = 85
0;
int activeBlue = 0;
int buttonPin = 13;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  for (int i = 3; i <= 9; i++) {
    pinMode(i, OUTPUT);
  }
  pinMode(buttonPin, INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  int buttonState = digitalRead(buttonPin);
  int knobVal = analogRead(knob);
  int mappedVal = map(knobVal, 420, 820, 0, 255);
  Serial.print("Knob Val: ");
  Serial.println(knobVal);
  Serial.print("Mapped Val: ");
  Serial.println(mappedVal);

  if (knobVal > threshold) {
    raining = 0;
  } else {
    raining = 1;
  }

  //control green light when not raining
  if (!raining) {
    //turn off all red, yello, and blue leds when not raining
    for (int i = 4; i <= 9; i++) {
      digitalWrite(i, LOW);
    }
    digitalWrite(3, HIGH);
    delay(500);
    digitalWrite(3, LOW);
    delay(500);
  } else {

    //control red and yellow leds when raining
    if (buttonState == LOW) {
      int x = 0;
      for (int i = 4; i <= 6; i++) {
        x = (int) random(0, 10);
        if (x > 7) {
          digitalWrite(i, LOW);
        } else {
          digitalWrite(i, HIGH);
        }
      }
    } else {
      for (int i = 4; i <= 6; i++) {
        digitalWrite(i, LOW);
      }
    }

    //control blue leds when raining
    int iterateBlue = (activeBlue % 3) + 7;
    digitalWrite(iterateBlue, HIGH);
    delay(200);
    digitalWrite(iterateBlue, LOW);
    activeBlue += 1;
  }
}

 

Week 8: Unusual Switch

For this week’s task, I created a switch which can be activated by touching a piece of wire taped on top of my foot to an aluminum foil sheet taped on the bottom side of a chair, emulating a self-defense move. When the switch is activated, a servo starts moving two aluminum foil pieces back and forth towards an aluminum foil figure , emulating slapping a stickman.

Process:

I started with designing the figure and securely taping everything to the cardboard. That was the most difficult part. After figuring out that part, arranging the circuit was straightforward. Below are a few pictures detailing the process and a video demo of the switch:

Unusual Switch

My Arduino code is very short. It controls the motion of the servo, allowing for a short pause in between.

#include <Servo.h>

Servo servo;
const int servoPin = 2;
int currmillis = 0;
bool dir = 0;

void setup() {
  servo.attach(servoPin);
}

void loop() {

  if ((int)millis() - currmillis >= 1000) {
    currmillis = millis();
    dir = !dir;
  }

  if (dir) {
    servo.write(180);
  } else {
    servo.write(0);
  }

}

 

Staying in Orbit: Midterm Video Game Project

My midterm project turned out closely similar to what I imagined it would be when I started. I used two images and one audio. The character sprite and the audio file were both downloaded from https://www.nyan.cat/

Game Premise: 

The game is a one player game with the goal of keeping the character within the screen (in orbit). If the character moves out of screen (falls out of orbit), the game ends. The player interacts with the game using the Space key. Whenever the Space key is pressed, the character accelerates horizontally and vertically. If the key is not pressed, the character loses horizontal speed and falls to the bottom of the screen.

Challenges:

The audio file used in the game is too big (about 13 mb), so the game takes a while to load. However, the game runs smoothly after loading. Another issue I faced is adjusting the game difficulty. I adjusted the timing of releasing the objects using the random function. This makes the difficulty of the game somewhat inconsistent, but I think I have adjusted it sufficiently.

Short Clip:

The code and assets are available in this Drive folder (accessible only within the NYU network).

Midterm Progress

Inspiration:

The game is inspired by themes from “Nyan Cat: Lost in Space”, and the game style/mechanics are similar to Chrome’s dinosaur game. The main similarities with the dinosaur game is that it is controlled by one key and there is no win condition. The game continues to run until the player hits one of the death obstacles or moves off screen.

Google Chrome's Dinosaur game is presumably called so due to the fact of a  dinosaur being the protagonist.: shittygamedetails

Midterm progress:

So far, I have collected the main images and sounds which I will be using. I also have a plan for the game mechanics, which I have yet to implement most of. The game consists of one character which can be controlled by the space bar. The character is always moving along the x values, gravitating towards the bottom of the screen, and bouncing between the two “walls” at each side of the screen. The goal of the player is to keep the character on screen else the game ends. Obstacles will include objects falling from the top and bubbles rising from the bottom. If a falling object hits the character, the character will gravitate faster to the bottom, and if a rising bubble collides with the character, the character will be paralyzed for a set amount of time and will continue to rise to the top of the screen. The score will be increasing as long as the game has not ended, and bonus object might appear to help the player increase their score.

Below is a visual of the game and a portion of the code.

View post on imgur.com

PImage jazz_sheet;
PImage[] jazz;
Star[] stars;
int starcnt;
int step = 0;
int x;
int y;
float speed = 3;
float gravity = 0.5;
int wall;

void setup() {
  fullScreen();

  wall = 80;
  
  starcnt = 80;
  stars = new Star[starcnt];
  
  for (int i=0; i<starcnt; i++){
    stars[i] = new Star();
  }

  jazz_sheet = loadImage("jazz.png");
  jazz = new PImage[12];

  int w = jazz_sheet.width/12;

  for (int x=0; x<12; x++) {
    jazz[x] = jazz_sheet.get(x*w, 0, w, jazz_sheet.height);
  }

  x = width/2;
  y = height/2;

  imageMode(CENTER);
}

void draw() {
  background(0);

  if (keyPressed && keyCode==UP) {
    gravity -= 0.1;
    speed += 0.1;
  } else {
    gravity += 0.1;
    speed -= 0.1;
  }

  if (frameCount%6==0) {
    step = (step+1) % 12;
  }
  
  for (int i=0; i<starcnt; i++) {
    stars[i].display();
  }

  pushMatrix();
  if (speed>0) {
    image(jazz[step], x, y);
  } else {
    //scale(-1, );
    image(jazz[step], x, y);
  }
  popMatrix();

  pushMatrix();
  rectMode(CORNERS);
  fill(52, 232, 221);
  rect(0, 0, wall, height);
  rect(width, 0, width-wall, height);
  popMatrix();

  x+=speed;
  y+=gravity;

  if (x+100 >= width-wall) {
    speed *= -1;
  }
}

//void keyPressed() {
//  if (keyCode == 49) {
//    gravity -= 0.5;
//    speed += 1;
//  } else {
//    gravity += 0.5;
//    speed -= 1;
//  }
//}
class Star {
  float x, y;
  float y_min, y_max;
  float diameter;
  float ty;
  float speed;
  Star() {
    diameter = 3;
    x = -diameter/2;
    x = random(0,width);
    y_min = 0;
    y_max = height;
    y = random(0,height);
    ty=random(0, 10000);
    //ty = 0.1;
    speed = random(0.1, 0.5);
  }
  
  void motion() {
    y += map(noise(ty), 0, 1, -1, 1);
    //y += noise(ty);
    x += speed;
    ty += 0.02;
  }
  
  void reposition() {
    if (x >= width+diameter/2) {
      x = -diameter/2;
    }
  }
  
  void display() {
    fill(255);
    motion();
    noStroke();
    ellipse(x, y, diameter, diameter);
    reposition();
  }
}

 

Week 4: Data Visualization

For this week’s task, I reused some modified code from the previous week’s task. I tried to make some sort of data visualization that compares the popularity of google searches on some celebrities’ names before their death and on the month of their death. I originally planned to take the mean of all months prior to their death, but I realized that would result in very low values consistently. I decided to consider only the highest month prior to their death and the month of their death. This resulted in some variety in the data. Below is a video of the visualization:

Whenever the mouse cursor hovers over one of the sections, that section displays the data on the month of death. Otherwise, the data prior to death is always displayed.

Challenges:

My PC randomly shut down twice while working on my code. I forgot to save my code on both times. Saving code periodically is a good habit.

Wait, is Wade Boggs dead?

No, interestingly though, the frequency of google searches on his name was highest the month this episode was released.

Code:

PFont font;
color bgcolor1;
color bgcolor2;
color bblclr;
int sections, bblcnt;
Thing bbls[][][];
Table table;
float diameter[][];

void setup() {
  size(800, 402);
  
  font = createFont("Franklin Gothic Heavy", 80);
  
  bgcolor1 = color(1, 148, 154);
  bgcolor2 = color(219,31,72);
  bblclr = color(209,202,185);
  
  sections = 3;
  bblcnt = 20;
  bbls = new Thing[sections][2][bblcnt];
  diameter = new float[sections][2];
  
  diameter[0] = loadData("alextrebek.csv", "2020-11");
  diameter[1] = loadData("wadeboggs.csv", "2015-01");
  diameter[2] = loadData("reckful.csv", "2020-07");
  
  for (int i=0; i<sections; i++) {
    for (int j=0; j<2; j++) {
      diameter[i][j] = map(diameter[i][j],1,100,1,80);
    }
  }
  
  for (int i=0; i<sections; i++) {
    for (int j=0; j<bblcnt; j++) {
      float ymin = i*(height/sections);
      float ymax = (i+1)*(height/sections);
      //float d = random(20,40); //temporary, replace with mean
      bbls[i][0][j] = new Thing(ymin,ymax,diameter[i][0],bblclr,false);
      bbls[i][1][j] = new Thing(ymin,ymax,diameter[i][1],bblclr,true);
    }
  }
  
}

void draw() {
  background(bgcolor1);
  
  fill(bgcolor2);
  noStroke();
  rect(0, height/3, width, height/3);
  
  for (int i=0; i<sections; i++) {
    for (int j=0; j<bblcnt; j++) {
        bbls[i][0][j].display();
        bbls[i][1][j].display();
    }
  }
  
  textAlign(CENTER, TOP);
  textSize(70);
  textFont(font);
  fill(bgcolor1);
  text("Alex Trebek", width/2, 20);
  
  textAlign(CENTER, TOP);
  textSize(70);
  textFont(font);
  fill(bgcolor2);
  text("Wade Boggs", width/2, 20+(height/3));
  
  textAlign(CENTER, TOP);
  textSize(70);
  textFont(font);
  fill(bgcolor1);
  text("Reckful", width/2, 20+(2*height/3));
}

float[] loadData(String file, String last) {
  table = loadTable(file, "header");
  float result[] = {0,0};
  for (int i=0; i<table.getRowCount(); i++) {
    TableRow row = table.getRow(i);
    
    String month = row.getString("month");
    float frequency = row.getFloat("frequency");
    
    if (month.equals(last)) {
      result[1] = frequency;
      break;
    }
    
    if (result[0] < frequency) {
      result[0] = frequency;
    }
  }
  return result;
}
class Thing {
  float x, y;
  float y_min, y_max;
  float diameter;
  float ty;
  float speed;
  color bc;
  float alpha;
  boolean active;
  Thing(float ymin, float ymax, float d, color bubbleColor, boolean state) {
    diameter = d;
    x = -diameter/2;
    y = random(ymin+(diameter/2), ymax-(diameter/2));
    ty=random(0, 10000);
    speed = random(1, 3);
    y_min = ymin;
    y_max = ymax;
    bc = bubbleColor;
    alpha = 0;
    active = state;
  }
  
  void motion() {
    y = map(noise(ty), 0, 1, y_min+(diameter/2), y_max-(diameter/2));
    x += speed;
    ty += 0.02;
  }
  
  void reposition() {
    if (x >= width+diameter/2) {
      x = -diameter/2;
    }
  }
  
  //float getAlpha() {
  //  return alpha;
  //}
  
  //void setAlpha(float a) {
  //  if (a >=0 && a <= 255) {
  //    alpha = a;
  //  } else if (a < 0) {
  //    a = 0;
  //  } else if (a > 255) {
  //    alpha = 255;
  //  }
  //}
  
  void resetAlpha() {
    if (!active && (mouseY <= y_max && mouseY >= y_min)) {
      if (alpha >=10) {
      alpha -= 10;
      }  else {
        alpha = 0;
      }
    } else if (!active && (mouseY > y_max || mouseY < y_min)) {
      if (alpha < 170) {
        alpha += 10;
      }
    }
    
    if (active && (mouseY <= y_max && mouseY >= y_min)) {
      if (alpha < 170) {
        alpha += 10;
      }
    } else if (active && (mouseY > y_max || mouseY < y_min)) {
      if (alpha >=10) {
      alpha -= 10;
      }  else {
        alpha = 0;
      }
    }
  }
  
  void display() {
    fill(bc, alpha);
    motion();
    noStroke();
    ellipse(x, y, diameter, diameter);
    reposition();
    resetAlpha();
  }
}