Final Project Documentation.

Background

My final project was a, “DJ Mixer” where the user could use his/her own hands to control and mix two songs together to play at the same time. For example, Ed Sheerans’s shape of you background music could be played with Justin Bieber singing love yourself.

 

Timeline

  1. Used the Minim Processing library to simply play 2 songs at the same time, one kareoke and one acapella, and was working on a volume controller which looked like this initially and worked with the cursor on the computer:

2. Integrated it to the kinect using  a library by Dan Shiffman and added pause buttons.

3. Added many more songs, removed pause buttons, instead the currently playing song could be hovered on to pause.

 

4. I then made many improvements to the previous version:

  • I improved the design a bit.
  • Cropped the mp3 files of the songs to go directly to the music rather than a pause or an intro as many of the youtube videos had them.
  • Made the webcam visible in a small area at the bottom.
  • I also made the volume be always visible to the user as it wasn’t clear to them that that functionality was possible.

5. The final update was mainly a huge UI and Design bump which I will go over in the next section. I also added a, “DJ Training” stage in order to train users on how to move the hand around the screen to select stuff. (In the video below I use the computer cursor as the pointer for demo purposes)

Technical Aspect

The base of my project was not hard to make. I am glad I went with the object oriented approach as I’m sure it saved me TONS of time as I started making many upgrades to it. I created a Song class that included the functionality of instantiating the song with its song file, displaying the song on the screen, having on hover functionality for that song, playing and pausing functions. I then made an array of Song objects in a songs array and instantiated them all. This made it trivial to add new songs. Using an object oriented approach allowed me to implement the volume controller class and add in many design improvements such as font and on hover shading easily.

 

The controlling of the cursor was perhaps the most important part technically as it is what gives the user the ability to interact with my project so it had to be as seamless as possible which is why I focused on it a lot. The kinect integration was simple as I got the code from Dan Shiffmans website. However, the detection of the hand was VERY buggy initially and I had to make many changes to optimize it specifically for my requirement:

  • The first important thing I did was make the cursor return to a specific spot if there is no depth greater than the threshold value. This reduced some unintentional clicking.
  • The second is that I optimized Dan Shiffman’s Kinect class to suit my needs by adding 2 counters: one totalArea counter which increments in the for loop and one handArea counter which also increments in the for loop but only when the depth is greater than the threshold value. I then only allowed the movement of the cursor if handArea/totalArea=0.065 as after some testing found that the hand is less than 6.5% of the total kinect screen.
  • I made everything hover based, any song, pause button and volume control has its own counter which I use to make it function only when the cursor hovers over that part for some time. This dramatically reduced unintentional clicks.

Design and Interactivity

As you have seem there was a bump in the design from the 90% ready project and the 100% ready project. Let me take you through some of my decisions.

  • The adding of icons was important as professor suggested, as it gave the users a clear idea of what would happen when clicked which is why I used icons for pause, and a sound icon near the volume controllers. This worked well in practice as I didn’t have to explain much to the users, they simply knew what to do.

 

 

 

 

  • I added a hover shade effect when a user hovers over a song name and a color change to the pause buttons when hovered. I did this so that first time users would know that the song is indeed clickable (or pause button). It almost gives the, ‘hyperlink’ feel to the user which is familiar to them and hence serves my project better in terms of interaction.
  • I worked hard on the volume controller, which is why it has its own class. I wanted to design it just like a volume controller in a dj rack where the user could move a knob along a slider in order to control the volume. I did this so the user could relate what they see to a volume knob in a sound mixer in order to know what the volume controller does.  The background of the volume controller initially were horizontal, equally spaced lines but I decided to make it random constantly moving lines to give it a live feel so that the user knows that something can be done by using it.

 

 

  • I needed a good font as the one I selected initially wasn’t appealing to many users. I wanted a font that would be:
    • Easy to read
    • Had some type of old-school tech feel to it (like how text is displayed on a small LED display)
    • Seemed intriguing to catch some passer-by’s attention

After some considerable research, I found a font called “underwood”, which seemed like a good choice as it fit my criteria.

  • The background image of a DJ rack that I found on google images was a nice addition as it fit my project well. The two racks seemed cohesive with the two sides (kareoke and acapella) that the user could choose songs from. Many users actually started hovering over the DJ racks themselves thinking it would do something.

The code

Main code:

import ddf.minim.*;
import ddf.minim.signals.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import org.openkinect.freenect.*;
import org.openkinect.processing.*;
import processing.video.*;
KinectTracker tracker;
Kinect kinect;
Minim minim;
Movie myMovie;
AudioPlayer splatterSound;
PVector v1, v2;
float vol2, vol3;
int CurRad=30;
boolean lPaused=false;
int lCounter=0;
boolean rPaused=false;
int rCounter=0;
float pX, pY;
float maxX=0; 
float maxY=0;
int kS=99;
int aS=99;
PFont sFont, bFont;
boolean debugMode=false;
PImage hand,splatter,bg,slider,djBg,bHand;
int rVolCounter=0;
int lVolCounter=0;
int rSongCounter=0;
int r2SongCounter=0;
boolean randomSongMode=false;
int stage=0;
boolean djTestPassed=false;
int djTestMarks=0;
Song[] songs = new Song[14];
float[] djHandPosX = new float[3];
float[] djHandPosY = new float[3];
float[] djCounter = new float[3];
boolean[] djCircleClicked = new boolean[3];
boolean randomHover=false;
int djTrainingFinishedCounter=0;
boolean djTrainingReached=false;
VolumeController lVolController,rVolController;
void setup() {
  //size(640, 480);
  fullScreen();
  background(0);
  kinect = new Kinect(this);
  tracker = new KinectTracker();
  minim = new Minim(this);
  splatterSound = minim.loadFile("splatter.mp3", 1024);
  sFont =  createFont("underwood.ttf", 24); //cool.ttf
  bFont =  createFont("capture.ttf", 30);
  hand=loadImage("hand.png");
  bHand=loadImage("bHand.png");
  splatter=loadImage("splatter.png");
  slider=loadImage("slider.png");
  slider.resize(50,60);
  bg=loadImage("bg.jpg");
  djBg=loadImage("djBg.jpg");
    bg.resize(width,height);
  djBg.resize(width,height);
  songs[0] = new Song("k_billy.mp3", false, "billy jean", width/5, 100, 0);           songs[2] = new Song("a_mnm.mp3", true, "lose yourself", width*0.55, 100, 2);
  songs[1] = new Song("k_shape.mp3", false, "shape of you", width/5, 160, 1);         songs[3] = new Song("a_rolling.mp3", true, "roll in the deep", width*0.55, 160, 3);
  songs[5] = new Song("k_strange.mp3", false, "stranger things", width/5, 220, 5);    songs[4] = new Song("a_baby.mp3", true, "baby", width*0.55, 220, 4);
  songs[6] = new Song("k_beat.mp3", false, "beat", width/5, 280, 6);                  songs[7] = new Song("a_yodel.mp3", true, "yodel", width*0.55, 280, 7);
  songs[8] = new Song("k_human.mp3", false, "human", width/5, 340, 8);                songs[9] = new Song("a_love.mp3", true, "love yourself", width*0.55, 340, 9);
  songs[10]= new Song("k_fancy.mp3", false, "fancy", width/5, 400, 10);               songs[11]= new Song("a_closer.mp3", true, "closer", width*0.55, 400, 11);
  songs[12]= new Song("k_fur_elise.mp3", false, "fur elise", width/5, 480, 10);       songs[13]= new Song("a_logic.mp3", true, "logic", width*0.55, 480, 11);
  kS=0;
  aS=3;
  songs[kS].play();
  songs[aS].play();
  smooth(10);
  djHandPosX[0]=width/random(1,1.5); djHandPosX[1]=width/2; djHandPosX[2]=width/6;
  djHandPosY[0]=height/1.5; djHandPosY[1]=height/5; djHandPosY[2]=height/1.8;
    djCircleClicked[0]=false; djCircleClicked[1]=false; djCircleClicked[2]=false;
    djCounter[0]=0; djCounter[1]=0; djCounter[2]=0;
    lVolController= new VolumeController(width/7-50,250,50,250);
    rVolController= new VolumeController(width-width/7,250,50,250);
    tracker.setThreshold(435);
}      

void draw() {
  if(djTestPassed) {
     background(bg);
  tracker.track();
  tracker.display();
  v1 = tracker.getPos();
  v2 = tracker.getLerpedPos();
  pX=mouseX;pY=mouseY;
  //pX=v2.x;pY=v2.y;
  //pX=map(v2.x, 0, 640, 0, width); pY=map(v2.y, 0, 480, 0, height);
  //pX=map(v1.x, 0, 640, 0, width); pY=map(v1.y, 0, 480, 0, height);
  if (pX>maxX) {
    maxX=pX;
  }
  if (pY>maxX) {
    maxY=pY;
  }
  int t = tracker.getThreshold();
  
  noStroke();
  lVolController.draw(songs[kS]);
  rVolController.draw(songs[aS]);
  float radius;
  for (Song ss : songs) {
    ss.draw();
  }
  //HOVER CLICKING CODE
  if ((kS!=99)&&(songs[kS].acapella==acapella)&&songClicked) {
    songs[kS].pause();
  } 
  if ((kS!=99)&&(songs[aS].acapella==acapella)&&songClicked) {
    songs[aS].pause();
  }
  int indexer=0;
  for (Song ss : songs) {
    if ((!ss.paused)&&(!ss.clicked())) {
      if (ss.acapella) {
        aS=indexer;
      } else {
        kS=indexer;
      }
    } 
    if (ss.clicked()) {
      songClicked=true;
    }
    indexer++;
  }
  int indexer2=0;
  for (Song ss : songs) {
    if ((ss.clicked())&&songClicked) {
      ss.click();
    }
  }
  indexer2=0;
  songClicked=false;
  //HOVER CLICKING CODE END
  if (CurRad<30) {
    CurRad=30;
  }
  if (pX<width/2) {
    if (lPaused) {
      radius = CurRad + 30 * sin( frameCount * 0.05f );
    } else {
      radius=CurRad;
    }

    fill(155, 18, 13);
  } else {
    if (rPaused) {
      radius = CurRad + 30 * sin( frameCount * 0.05f );
    } else {
      radius=CurRad;
    }
    fill(16, 46, 117);
  }
  if((randomSongMode)||(randomHover)) {fill(200);} else {fill(255);}
  rect(width/2-100,height/1.35,200,35);
  textFont(sFont);
  textAlign(CENTER);
  fill(0);
  text("RANDOM",width/2-100,height/1.35+8,200,35);
   textFont(sFont);
  imageMode(CENTER);
  image(hand,pX,pY,50,50);
  randomSong();
  randomSongMode();
} else {
  background(djBg);
  tracker.track();
  tracker.display();
  v1 = tracker.getPos();
  v2 = tracker.getLerpedPos();
  pX=mouseX;pY=mouseY;
  //pX=v2.x;pY=v2.y;
  //pX=map(v2.x, 0, 640, 0, width); pY=map(v2.y, 0, 480, 0, height);
  //pX=map(v1.x, 0, 640, 0, width); pY=map(v1.y, 0, 480, 0, height);
  if (pX>maxX) {
    maxX=pX;
  }
  if (pY>maxX) {
    maxY=pY;
  }
  int t = tracker.getThreshold();
  tracker.setThreshold(630);
  textSize(55);
  fill(0);
  textAlign(CENTER);
  if(!djTrainingReached) {
  text("Place your hand on the circles for DJ Training",width/18,100,width,100);
  }
  djTraining();
  imageMode(CENTER);
  if(djTestPassed) {
  image(hand,pX,pY,50,50);
  } else {
  image(bHand,pX,pY,50,50);
  }
}
}
void keyPressed() {
  int t = tracker.getThreshold();
  if (key == CODED) {
    if (keyCode == UP) {
      t+=5;
      tracker.setThreshold(t);
    } else if (keyCode == DOWN) {
      t-=5;
      tracker.setThreshold(t);
      
      
    }
    fill(255);
      text(t,width/2,height-100);
    println(t);
  }//END KINECT
  if (key == 'x'||key == 'X') {
  debugMode=!debugMode;
  }
    if (key == 'R'||key == 'r') {
  randomSongMode=!randomSongMode;
  }
      if (key == 'd'||key == 'D') {
        djTestMarks=0;
        djCircleClicked[0]=false;
        djCircleClicked[1]=false;
        djCircleClicked[2]=false;
        djTrainingReached=false;
        djTrainingFinishedCounter=0;
        djTestPassed=!djTestPassed;
        debugMode=true;
  }
}
boolean acapella;
boolean songClicked=false;

Song class:

class Song {
  Minim minimThing;
  AudioPlayer song;
  boolean paused=true;
  boolean acapella;
  float x, y;
  String title;
  int index;
  boolean clicked=false;
  int clickCounter=0;
  float aX=width/2;
  float aY=20;
  float kX=width/4;
  float kY=20;
  float curX=x;
  float curY=y;
  float prevX;
  float prevY;
  float defaultX;
  float defaultY;
  float Lx, Ly;
  boolean hover=false;
  Song(String fileName, boolean songType, String titleVal, float xPos, float yPos, int indexer) {
    minimThing = new Minim(this);
    song = minim.loadFile(fileName, 1024);
    acapella=songType;
    x=xPos;
    y=yPos;
    title=titleVal;
    index=indexer;
    prevX=x;
    prevY=y;
    defaultX=x;
    defaultY=y;
  }  
  void draw() { //boolean songType=true means acapella else kareoke

    textSize(24);
    textAlign(CENTER);
    
    if (paused) {
      clicked();
      if(!hover) {
      fill(255); } else {
      fill(100+clickCounter);
      }
      textSize(24);
      textFont(sFont);
      Lx = lerp(prevX, x, 0.05);
      Ly = lerp(prevY, y, 0.05);
      text(title, Lx, Ly, 300, 50);

      curX=x;
      curY=y;
    } else if (acapella) {
      //textFont(bFont);
      fill(0);
      Lx = lerp(prevX, aX, 0.05);
      Ly = lerp(prevY, aY, 0.05);
      fill(255);
      rect(aX, aY, 300, 50);
      fill(0);
      if(!hover) {fill(0);} else {fill(0,0,200);}
      rect(aX+10,aY+10,10,30);
      rect(aX+23,aY+10,10,30);
      fill(0);
      textAlign(CENTER);
      text(title, Lx+15, Ly+13, 300, 50);
      curX=aX;
      curY=aY;
    } else {
      //textFont(bFont);
      fill(0);
      
      Lx = lerp(prevX, kX, 0.05);
      Ly = lerp(prevY, kY, 0.05);
      fill(255);
      rect(kX, kY, 300, 50);
      if(!hover) {fill(0);} else {fill(0,0,200);}
      rect(kX+10,kY+10,10,30);
      rect(kX+23,kY+10,10,30);
      fill(0);
      textAlign(CENTER);
      text(title, Lx+40, Ly+13, 200, 50);
      curX=kX;
      curY=kY;
    }
    prevX=curX;
    prevY=curY;
  }
  void play() {
    //print(title);
    
    if (acapella) {
      if (clicked()) {
        songs[aS].pause();
      }
      aS=index;
    } else {
      if (clicked()) {
        songs[kS].pause();
      }
      kS=index;
    }
    song.loop();
    paused=false;
  }
  void pause() {
    song.pause();
    paused=true;
  }
  void togglePlay() {
    if (paused) {
      play();
    } else {
      pause();
    }
  }
  void setGain(float vol) {
    song.setGain(vol);
  }
  boolean clicked() {
    if ((pX>curX)&&(pX<curX+200)&&(pY>curY)&&(pY<curY+50)) {
      hover=true;
      
      if ((clickCounter==70)) {

        CurRad=30;

        boolean clicked=true;

        return true;
      } else {

        clickCounter++;

        CurRad=clickCounter;

        boolean clicked=false;
      }
    } else {
      hover=false;
      CurRad=30;

      clickCounter=0;
      clicked=false;
      return false;
    }
    clicked=false;
    return false;
  }
  void click() {

    togglePlay();
    //background(255);
  }
}

void randomSong() {
  //rect(width/2-100,height/1.35,200,35);
  if((pX>(width/2-100))&&(pX<(width/2+100))&&(pY>(height/1.35))&&(pY<((height/1.35))+35)) {
    randomHover=true;
    if((rSongCounter==25)||(((rSongCounter%125)==0)&&(rSongCounter!=0))) {
int first= int(random(0,songs.length));
int second=int(random(0,songs.length));
while((songs[first].acapella==songs[second].acapella)) {
first= int(random(0,songs.length));
second=int(random(0,songs.length));
}
songs[aS].pause();
songs[kS].pause();
songs[first].play();
songs[second].play();
    }
rSongCounter++;
  } else {
    randomHover=false;
  rSongCounter=0;
  
  }
}

void randomSongMode() {
  if(!randomSongMode) {r2SongCounter=0;} else {
    if((((r2SongCounter%400)==0)&&(r2SongCounter!=0))) {
int first= int(random(0,songs.length));
int second=int(random(0,songs.length));
while((songs[first].acapella==songs[second].acapella)) {
first= int(random(0,songs.length));
second=int(random(0,songs.length));
}
songs[aS].pause();
songs[kS].pause();
songs[first].play();
songs[second].play();
    }
r2SongCounter++;
  }
}

Upgraded Kinect Class from Dan Schiffman:

// Daniel Shiffman
// Tracking the average location beyond a given depth threshold
// Thanks to Dan O'Sullivan

// https://github.com/shiffman/OpenKinect-for-Processing
// http://shiffman.net/p5/kinect/

class KinectTracker {

  // Depth threshold
  int threshold = 435;

  // Raw location
  PVector loc;

  // Interpolated location
  PVector lerpedLoc;

  // Depth data
  int[] depth;
  
  // What we'll show the user
  PImage display;
   
  KinectTracker() {
    // This is an awkard use of a global variable here
    // But doing it this way for simplicity
    kinect.initDepth();
    kinect.initVideo();
    kinect.enableMirror(true);
    
    // Make a blank image
    display = createImage(kinect.width, kinect.height, RGB);
    // Set up the vectors
    loc = new PVector(0, 0);
    lerpedLoc = new PVector(0, 0);
  }

  void track() {
    // Get the raw depth as array of integers
    depth = kinect.getRawDepth();
    float totalArea=0;
    float handArea=0;
    // Being overly cautious here
    if (depth == null) return;

    float sumX = 0;
    float sumY = 0;
    float count = 0;

    for (int x = 0; x < kinect.width; x++) {
      for (int y = 0; y < kinect.height; y++) {
        totalArea++;
        int offset =  x + y*kinect.width;
        // Grabbing the raw depth
        int rawDepth = depth[offset];

        // Testing against threshold
        if (rawDepth < threshold) {
          handArea++;
          sumX += x;
          sumY += y;
          count++;
          
        } else {
        loc=new PVector(kinect.width/2,kinect.height/1.2);
        }
      }
    }
    // As long as we found something
    //println(sumY);
    //println((handArea/totalArea));
   if ((count != 0)&&((handArea/totalArea)<0.065)) { //if ((count != 0)&&(sumX<8000000)&&(sumX>150000)&&(sumY<6000000)&&(sumY>1000)) {
      loc = new PVector(sumX/count, sumY/count);
    }

    // Interpolating the location, doing it arbitrarily for now
    lerpedLoc.x = PApplet.lerp(lerpedLoc.x, loc.x, 0.3f);
    lerpedLoc.y = PApplet.lerp(lerpedLoc.y, loc.y, 0.3f);
  }

  PVector getLerpedPos() {
    return lerpedLoc;
  }

  PVector getPos() {
    return loc;
  }

  void display() {
    PImage img = kinect.getDepthImage();
    PImage webcam = kinect.getVideoImage();
    // Being overly cautious here
    if (depth == null || img == null) return;

    // Going to rewrite the depth image to show which pixels are in threshold
    // A lot of this is redundant, but this is just for demonstration purposes
    display.loadPixels();
    for (int x = 0; x < kinect.width; x++) {
      for (int y = 0; y < kinect.height; y++) {

        int offset = x + y * kinect.width;
        // Raw depth
        int rawDepth = depth[offset];
        int pix = x + y * display.width;
        if (rawDepth < threshold) {
          // A red color instead
          display.pixels[pix] = color(255, 10, 10);
        } else {
          //display.pixels[pix] = color(255);
          display.pixels[pix] = img.pixels[offset];
        }
      }
    }
    display.updatePixels();

    // Draw the image
    if(debugMode) {
    image(display,width/2, height*0.75,width/4,height/4); //display/webcam
    }
  }

  int getThreshold() {
    return threshold;
  }

  void setThreshold(int t) {
    threshold =  t;
  }
}

Volume Controller Class

class VolumeController {
  int x,y;
  int lVolCounter=0;
  int w;
  int h;
  int padding=50;
  float volume=0;
  PImage audioImage;
  float defaultVolume=0;
  VolumeController(int xPosition, int yPosition, int dWidth,int dHeight) {
    x=xPosition;
    y=yPosition;
    w=dWidth;
    h=dHeight;
    audioImage=loadImage("audio.png");
    audioImage.resize(25,25);
  }
  
void draw(Song curSong) {
      float displayVol=map(volume,-30,0,0,h);
      float pos=(y+h)-displayVol;
  if(((pX>x-padding)&&(pX<x+w+padding)&&(pY>y-padding)&&(pY<y+h+padding))) {
   // if(((pX>x-padding)&&(pX<x+w+padding)&&(pY>pos-20)&&(pY<pos+20))) {
  if(lVolCounter>15) {
    volume = map((y+h)-pY, 0, h, -30, 0);
    displayController(curSong);
    curSong.setGain(volume);
    } else {
    displayController(curSong);
    }
    lVolCounter++;
  } else {
    lVolCounter=0;
    displayController(curSong);
  }
}

void displayController(Song curSong) {
  image(audioImage,x+w/2,y-25);
  stroke(0);
  noFill();
  rect(x,y,w,h);
  noStroke();
        for(int yp=0;yp<h;yp++){
          if(curSong.paused) {
          if(yp%15==0) {
        fill(random(180,200));
      }
          }
          
      else if(yp%2==0) {
        fill(random(230,255));
      } else  if(yp%10==0) {
        fill(random(200,220));
      } else if(yp%15==0) {
        fill(random(180,200));
      }
     rect(x+5,y+yp,w-5,1);
     }
     
         fill(0);
    rect(x+w/2,y,5,h);
    fill(0,0,128);
    noStroke();
    //rect(5, height*0.65-volDis2, 30, volDis2);
    float displayVol=map(volume,-30,0,0,h);
float pos=(y+h)-displayVol;
if(pos>(y+h)-20) {pos=y+h-20;} else if(pos<(y)){pos=y;}
    rect(x,pos, w, 20);
}
}

DJ Training function:

void djTraining() {
  boolean playSound=false;
for(int x=0;x<(djHandPosX.length);x++) {
  //ellipse(djHandPosX[x],djHandPosY[x],40,40);
  if((pX>djHandPosX[x]-25)&&(pX<djHandPosX[x]+25)&&(pY>djHandPosY[x]-25)&&(pY<djHandPosY[x]+25)) {
  fill(255,0,0);
  //djHandPosX[x]=9000;
  //djHandPosY[x]=9000;
  djCounter[x]++;
  if(djCounter[x]==40) {playSound=true;}
  if(djCounter[x]==55) {
  djCircleClicked[x]=true;
  djTestMarks++;
  
  } 
  } else {
  fill(50);
  djCounter[x]=0
;  }

  if(!djCircleClicked[x]) {
    noStroke();
  ellipse(djHandPosX[x],djHandPosY[x],50+djCounter[x],50+djCounter[x]);
  } else {
      if (!splatterSound.isPlaying()) { splatterSound.rewind();
 }

  image(splatter,djHandPosX[x],djHandPosY[x],300,300);
  }
       if(playSound) {
   splatterSound.play();
   }} 
if(djTestMarks==djHandPosX.length) {
djTrainingFinishedCounter++;
djTrainingReached=true;
debugMode=false;
fill(0);
text("Get your DJ Belts on.",width/2,height/2);
if(djTrainingFinishedCounter>120) {
djTestPassed=true;
}
}
}

 

 

 

 

 

Hand Music Mashup – User Testing

For context, here’s my 75% complete project:

and here’s my 90% complete project:

So as I was developing my project, I was constantly trying to user test my own project before I user test it on others as I would know best how to break my own project which resulted in many enhancements in the UI, design and how it functions overall:

    • I realized early on that there would be no ‘buttons’ to be clicked, rather places that could be hovered on for some time and some specific task will take place (If I refer to buttons in my post I mean places you can hover click)
    • Volume control of either side initially was controlled by simply hovering anywhere vertically in the two halves of the screen which has proven to be clunky. I now limited volume control to be 1/8th of the screen on the left corner for the karaoke and 1/8th of the screen on the right corner for the acapella.
    • I needed to make the cursor easier for the user to follow. I found out how to leave a trail of an object in processing from http://www.science.smith.edu/dftwiki/index.php/Creating_a_trail_of_moving_object_in_Processing as I couldn’t figure out how to do it in the draw function because of the background that kept resetting. I learned that for this to work there shouldn’t be a background reset, rather a rectangle that’s the size and width of the screen with an opacity.
  • Initially the volume control was simply assumed to be understood by scroll hovering on either side but I added a visualizer for the volume as I felt like it wasn’t clear to the user of how exactly the volume would increase and reduce and the current volume the sound is at. 
  • Integrating the kinect into the project was troublesome as it was very buggy. Initially, I put made the cursor move with the kinectTracker class that Dan Schiffman made but the cursor would move all around when I went away from the kinect, so I changed the kinectTracker class to return the PVector to the center of the screen if it doesn’t find any rawDepth that is lower than the threshold, that way there .
    • I made some optimizations to the kinect tracking class as it didn’t work well with my project. One thing I did is set up two integer variables called handArea and totalArea that were incrementally increased in the for loop that detects the depth using the threshold. The handArea would only increase when the raw depth is less than the threshold and the totalArea int would increase with the for loop always. I then made the cursor be able to move only if handArea/totalArea was less than 0.065 (tested to get that value) because the hand would only be less than 6.5% of the total kinect visiblity.
    • I also made the cursor move back to a specific point away from all buttons when there is no depth less than the threshold. By doing this random clicking of buttons would be reduced.
    • I made the hover time for every function about 0.5-2 seconds in order to reduce unintentional clicking.

User Testing 1

Response to Computer Vision for Artists and Designers

I found this reading very interesting as I find the field of computer vision very exciting and I see it as a rising industry in many forms such as VR or AR. What I find particularly amazing is the algorithms that find patterns in a set of almost abstract pixels and find some sort of meaning to it.

The “ELEMENTARY COMPUTER VISION TECHNIQUES” section was important to me as my final IM Project includes the detection of motion as I am using a kinect to detect hand movements.

I also found the “LimboTime” game interesting as it would have to take into account both depth and motion.

I tried some of the .pde files linked to in the reading and was fun to play around with (some didn’t work probably due to my processing version)

My Final Project plan

My plan as I mentioned in class is to make a system that allows users to mash up different songs together based on their own choice.

Firstly, the screen would be divided into two vertical halves(not visibly) and the controls would differ depending on which hand the user uses that act on the ‘karaoke’ side or the ‘acapella’ side.

There are a few controls I would like to give the user:

  • Volume control by moving vertically along the left side for kareoke and right side for acapella.
  • On the top right there will be a pause/play as well as the left, if the user hovers his or her hand over the respective text for more than 2 seconds, that side(kareoke or acapella) would only be acted on.
  • There will be lists of songs on either side, where the user can hover for more than 2 seconds to play.

Things I need

  • Kinect, because I feel I can only get the seamless control of the music using someones free hands in the air to control volume, pausing/playing each side and selecting a song.
  • A projector.

The list of songs I have made for now

 

Karaoke Acapella
Earned it – The Weeknd https://www.youtube.com/watch?v=o-gH2KedX60
Somebody I Used To Know https://www.youtube.com/watch?v=XvVmZmMLojc
Lady GaGa – Paparazzi https://www.youtube.com/watch?v=Hz_9FNImfMs
Lady Gaga – Poker Face https://www.youtube.com/watch?v=9smUUbtgxSM
Ed Sheeran – Thinking Out Loud https://www.youtube.com/watch?v=byXb3KT1-3A
Michael Jackson – Billie Jean   https://www.youtube.com/watch?v=eXqBhDAlVCc
Clean Bandit – Rockabye https://www.youtube.com/watch?v=Gl78zFKbQbE
Twenty One Pilots – Ride https://www.youtube.com/watch?v=muSbrUYiqrI
Cheerleader https://www.youtube.com/watch?v=ivJBs_wbOyY
Adele Rolling in the deep https://www.youtube.com/watch?v=WppvmpLKS-Y
Humble – Kendrick Lamar https://www.youtube.com/watch?v=AnQESyZisU0
Thriftshop https://www.youtube.com/watch?v=qx668eVJKeo
Yodeling Kid https://www.youtube.com/watch?v=bOZT-UpRA2Y
Eminem – Lose yourself https://www.youtube.com/watch?v=7_QK8yGjhH0
Logic’s type beat https://www.youtube.com/watch?v=hGAjM4qcWcg
Aggressive trap beat https://www.youtube.com/watch?v=i4zbFSbN_BY
Stranger things soundtrack https://www.youtube.com/watch?v=a3wGYbq6_Mc
Drake – Blessings https://www.youtube.com/watch?v=gqvFEHlwqVs
Ed Sheeran Shape Of You https://www.youtube.com/watch?v=o71_MatpYV0

 

Computing

I’ve been interested in computers for as long as I remember. However, I was stuck in the software side of things, I never actually worked with hardware at all. I did watch a lot of videos on DIY Raspberry Pi projects but I didn’t exactly do anything, I was entertained watching other people’s projects and felt like it wasn’t in my realm to do.

Now however, it is in my realm. It wasn’t until this class that I actually started tinkering with hardware. I had zero knowledge about hardware so there was a steep learning curve at first but then when I got the hang of it, it became really fun making projects that interest me.

I feel like I have a better view on computing now that I know the hardware side of things (to an extent) Knowing how to tinker and make cool physical projects that communicate with the computer just gives me and everyone in this class, power. The power to use our creativity to make useful things, or stupid pet tricks.

Air Music Player

I took my previous project where I visualized music through the microphone and reversed it. I wanted to in some form visualize the music with my hands and play it on the computer. I had to make some big changes to the code as I hadn’t generalized the classes enough in my previous project which is something I have to make sure I do next time. I used a library called Minim as the processing sound library didn’t allow pausing which was a vital feature in my project.

I selected Fur Elise from Beethoven and the Wonder Woman soundtrack for the two songs because they are so drastic in genre, tone and the way it sounds that it almost feels like a remix mashup in a way.

Arduino Code

int lightLevel;

void setup()
{
 Serial.begin(9600);
}
void loop()
{

  int knob = analogRead(A0);
  delay(1);
  lightLevel = analogRead(A1);
  delay(1);
  Serial.print(lightLevel);
  Serial.print(",");
  Serial.println(knob);
  

}

 

Processing Code

import processing.serial.*;
import processing.sound.*;
import ddf.minim.*;
Minim       minim;
Minim       minim2;
Minim       minim3;
AudioPlayer song2;
AudioPlayer song3;
AudioPlayer song4;
SoundFile song;
Amplitude amp2;
Serial myPort;
AudioIn in2;
CircleThing mainCircle;
Snow snowfall;
float ears= 0;
boolean movable=false;
PImage bg,nyu;
boolean logoDisplay=false;
float lightValue;
float knobValue;
float highLight=100;
float lowLight=0;
float knobScaling=1.5;
class CircleThing {
  public int circleX, circleY,defColor;
  public float prevRad=0;
  CircleThing(int cX, int cY,int defaultColor) {
    circleX=cX;
    circleY=cY;
    defColor=defaultColor;
  }
  void draw(float cX, float cY, float soundVal,int scaling,PImage nyuImage,boolean displayImage) {
  
    fill(map(soundVal,0,100,0,255)-100);
    strokeWeight(10);
    float radi=200+(100-soundVal)*knobScaling;
    //float radi =(200+soundVal*scaling);
    ellipse(cX, cY,radi, radi);
    //line(0,0,radi/2,radi/2);
    imageMode(CENTER);
    //rotate(45);
if(displayImage) {
    pushMatrix();
imageMode(CENTER);
translate(cX,cY);
rotate(-PI/8);
image(nyuImage,0,0);
popMatrix();
}

String s = "Made by Romeno Wenogk Fernando. wenogk@nyu.edu";
fill(255);
textSize(16);
text(s, cX+500, cY, 200, 150);
prevRad=200+soundVal*scaling;
  }
}

class Particle {

  float x;          // adds x position property
  float y;          // adds y position property
  float xVel;       // adds xvel property
  float yVel;       // adds yvel property
  float partsize;   // adds a size property


  //Constructor = function// float says where it is xpos/ypos
  Particle(float xpos, float ypos) {
    // assigning the values
    x = xpos = random (0,600);
    y = ypos;
    xVel = random (-2, 2);   // random,(the length of the random)
    yVel = random (0, 5);    // controls the speed that the snow falls
    partsize = random (5, 10);
  }
  
}

class Snow {
Particle[] particles = new Particle[0];
int maxParticles =100;

Snow() {
}
void setMax(int val) {
maxParticles=val;
}
void draw(float ears2) {
  float ears =100- ears2;
particles = (Particle[]) append(particles, new Particle(300, 0));
  if (particles.length>maxParticles) {
    particles = (Particle[]) subset(particles, 1);
  }
  for (int i=0; i<particles.length; i++) {
      particles[i].partsize *=0.975;  //make the snow stay reduce size
      particles[i].x += particles[i].xVel;
      particles[i].y += particles[i].yVel;
     if(ears>30) {
      particles[i].yVel +=0.2;   
     } else if(ears>50) {
       particles[i].yVel +=0.3;
     }
     else if(ears>70) {
       particles[i].yVel +=0.4;
     } else if(ears>90) {
       particles[i].yVel +=0.6;
     } else {
       particles[i].yVel-=0.1; //reverse gravity
     }
    ellipse(particles[i].x, particles[i].y, particles[i].partsize, particles[i].partsize);
  }
}
}

void setup() {
  size(640, 440);
  background(90);
  amp2 = new Amplitude(this);
  in2 = new AudioIn(this, 0);
  in2.start();
  amp2.input(in2);  
  mainCircle= new CircleThing(width/2,height/2,45);
  snowfall= new Snow();
  bg = loadImage("bg.jpg");
  bg.resize(width,height);
  nyu=loadImage("nyu.png");
  nyu.resize(int(nyu.width*0.5),int(nyu.height*0.5));
  printArray(Serial.list());
String portname=Serial.list()[6];
println(portname);
myPort = new Serial(this,portname,9600);
myPort.clear();
myPort.bufferUntil('\n');
 //song = new SoundFile(this, "ww.mp3");

  // Loop the sound forever
  // (well, at least until stop() is called)
  //song.loop();
  
  minim = new Minim(this);
  minim2 = new Minim(this);
  minim3 = new Minim(this);
  song2 = minim.loadFile("ww.mp3", 1024);
  song3 = minim2.loadFile("bee.mp3", 1024);
  song4 = minim2.loadFile("fetty.mp3", 1024);
  song2.play();
  song3.play();
  song4.play();
}      

void draw() {
  background(bg);
  //ears=amp2.analyze();
  ears=map(lightValue,lowLight,highLight,0,100);
  if((100-ears)>45) {
  song4.play();
  song2.pause();
  song3.pause();
  }
  else if((100-ears)>25) {
  song3.play();
  song2.pause();
  song4.pause();
  }  else if((100-ears)>10) {
  song3.pause();
  song2.play();
  song4.pause();
  } else {
  song2.pause();
  song3.pause();
  song4.pause();
  }
 
  println(100-ears);
   stroke(255); 
  strokeWeight(1);
  //println(ears);
  fill(255);
  snowfall.draw(ears);
  stroke(75); 
  float xpos,ypos;
  if(!movable) { 
    xpos=lerp(width/2,mouseX,0.01);
    ypos=lerp(height/2,mouseY,0.01);
  } else {
  //mainCircle.draw(mouseX,mouseY,ears,290,nyu,logoDisplay); 
   xpos=lerp(mouseX,width/2,0.01);
    ypos=lerp(mouseY,height/2,0.01);
  }
  mainCircle.draw(xpos,ypos,ears,290,nyu,logoDisplay);
 
highLight();
knobScaling=map(knobValue,0,255,0,1);
}

void mousePressed() {
movable=!movable;
}

void keyPressed() {
logoDisplay=!logoDisplay;

}

void serialEvent(Serial myPort){
String s=myPort.readStringUntil('\n');
s=trim(s);
//println(s);
if (s!=null){
int values[]=int(split(s,','));
lightValue=values[0];
knobValue=values[1];
}
}

void highLight() {
if(lightValue>highLight) {
highLight=lightValue;
}
if(lightValue<lowLight) {
lowLight=lightValue;
}
}

 

 

Life and Death Data Visualization Project

So for my project this week I wanted to do some data visualization that could be regarded, “live”. I got some interesting rates of from http://www.ecology.com/birth-death-rates/ where I found that the rate of birth worldwide is about 4 every second and the rate of death as 1.78 every second.

The idea was to basically show the user how many people have born and died since they opened the application and perhaps make them understand the importance of life by its fragility and beauty.

I used this data to implement a simple data visualization which you can see in the video below:

I randomly switched from the ‘lifeMode’ to the ‘deathMode’ with a 0.02 probability every 1/60th of a second and played a heart beat sound when it changes to give this visualization a more rushed feel.

I used the simple black and white color scheme to make it simple for the user to understand what is death and being born as it can be commonly derived that white represents life and black represents death.

The user can switch to and fro from death and life by clicking anywhere. And as some type of easter egg I implemented a system where it recognizes how long you stay in either the life or death by clicking and displays it in a pie chart if you press any key on your keyboard (as you can see towards the end of the youtube video). This basically is a type of personality test which shows the user whether they focus of life or death more.

 

float seconds,deaths,births;
float currentRadius=0;
int d = 0;
boolean lifeMode=true; //default lifeMode value is true which is the number of people born will be displayed first
boolean probability(float prob) { float value=random(0,1); if(prob>value) { return true; } else { return false; } } //probability function
boolean startControl=false;
boolean counting=false;
float lifePoints=1;float deathPoints=1;
boolean debug=false;
int[] angles={180,180};
void setup() {
  fullScreen();
//size(640,420);
  smooth();
  frameRate(60);
}

void draw() {
  //println((lifePoints/(lifePoints+deathPoints))*100 + " life percentage and" + (deathPoints/(lifePoints+deathPoints))*100);
  float lifePercent=((lifePoints)/(lifePoints+deathPoints))*360;
  float deathPercent=(deathPoints)/(lifePoints+deathPoints)*360;
  angles[0]=int(lifePercent);
  angles[1]=int(deathPercent);
  if(!debug){
  println(lifePercent + " --- " + deathPercent);
  if(lifeMode) { background(0);fill(255); if(counting) {lifePoints+=1;} } else { background(255);fill(0);if(counting) {deathPoints+=1;} } //switch bg color depending on mode
if(probability(0.01)) { changeLifeMode(true); } //switch mode randomly with a probability of 0.01
seconds = frameCount/60; 
deaths=(seconds*1.78); //1.78 deaths per second
births=(seconds)/0.25; //4 deaths per second
float rate=births-deaths;
if(rate>=currentRadius) {
currentRadius+=0.5; //to smoothe the radius increase rather than mapping the rate directly to radius
}
int scaler;
if(lifeMode) {scaler=4;} else {scaler=2;} 
noStroke();
ellipse(width/2,height/2,currentRadius*scaler,currentRadius*scaler);
if(lifeMode) { fill(0);textSize(16 + 16*currentRadius*0.02); } else { fill(255);textSize(16 + 16*currentRadius*0.01); }

textAlign(LEFT,CENTER);
String msg;
float textPlace;
if(lifeMode) { msg=int(births)+" born.";textPlace=(currentRadius*0.02*2+msg.length()*2); } else { msg=int(deaths)+" dead.";textPlace=(currentRadius*0.01*2+msg.length()*1.5); }
if(lifeMode) { text(msg , width/2-2.5*(textPlace), height/2);  } else { text(msg, width/2-2.5*(textPlace)-20, height/2); } //displaying text depending on mode
fill(255);
} else {
  background(200);
  float lifePercent2=((lifePoints)/(lifePoints+deathPoints))*100;
  float deathPercent2=(deathPoints)/(lifePoints+deathPoints)*100;
  pieChart(300, angles);
  fill(0);
  textSize(18);
  text("Life focus percent: " + lifePercent2 + "%" ,0.1*width,height*0.9);
  text("Death focus percent: " + deathPercent2 + "%" ,0.6*width,height*0.9);
}
}
void changeLifeMode(boolean auto) {
lifeMode=!lifeMode;
if(auto) {
counting=false;
}
}
void mousePressed() {
  counting=true;
startControl=true;
changeLifeMode(false);
}

void keyPressed() {
debug=!debug;
}

void pieChart(float diameter, int[] data) {
  float lastAngle = 0;
  for (int i = 0; i < data.length; i++) { 
    if(i==0) { fill(255); } else if(i==1) { fill(0); }
    
    arc(width/2, height/2, diameter, diameter, lastAngle, lastAngle+radians(data[i]));
    lastAngle += radians(data[i]);
  }
}

 

Music Art Visualizer using Object Oriented Programming

So for my project this week I wanted to recreate some of the music videos for electronic music on youtube such as this one:

I was interested in doing this a few months back and when I searched for tutorials they all had to do with getting some video editing software(Sony Vegas Pro) and putting in some scripts. However, now with my knowledge of processing I felt I could do it from scratch with code only and no video editing software, which is what I attempted. It isn’t as seamless as the one above but the general concept is there and it can be improved.

I also added some small features such as click to move the circle, added some text on the very right hand side so you can only see it when you move the circle and like the trapNation video has a logo in the circle, I added a logo that can be triggered by clicking the space bar.

The Code

I created a, ‘circleThing’ class which is the circle in the middle that increases its size depending on the Amplitude (note:not frequency).

I got the snow effect code from a website online that had a ‘Particle’ class and the rest of that snow code was procedural code. I enhanced it by making a ‘Snow’ class that created ‘Particle’ objects and was generalized so it was customizable. The speed of the snow fall depended on the Amplitude and when it was low I made it reverse the ‘gravity’ with a negative value.

import processing.sound.*;

Amplitude amp2;
AudioIn in2;
CircleThing mainCircle;
Snow snowfall;
float ears= 0;
boolean movable=false;
PImage bg,nyu;
boolean logoDisplay=false;

class CircleThing {
  public int circleX, circleY,defColor;
  CircleThing(int cX, int cY,int defaultColor) {
    circleX=cX;
    circleY=cY;
    defColor=defaultColor;
  }

  void draw(int cX, int cY, float soundVal,int scaling,PImage nyuImage,boolean displayImage) {
    if(soundVal>0.025) {
    fill(random(255), random(255), random(255));
    } else {
      //fill(random(255), random(255), random(255));
    fill(defColor);
    }
    strokeWeight(10);
    ellipse(cX, cY,(200+ soundVal*scaling), (200+soundVal*scaling));
    imageMode(CENTER);
    //rotate(45);
if(displayImage) {
    pushMatrix();
imageMode(CENTER);
translate(cX,cY);
rotate(-PI/8);
image(nyuImage,0,0);
popMatrix();
}

String s = "Made by Romeno Wenogk Fernando. wenogk@nyu.edu";
fill(255);
textSize(16);
text(s, cX+500, cY, 200, 150);
  }
}

class Particle {

  float x;          // adds x position property
  float y;          // adds y position property
  float xVel;       // adds xvel property
  float yVel;       // adds yvel property
  float partsize;   // adds a size property


  //Constructor = function// float says where it is xpos/ypos
  Particle(float xpos, float ypos) {
    // assigning the values
    x = xpos = random (0,600);
    y = ypos;
    xVel = random (-2, 2);   // random,(the length of the random)
    yVel = random (0, 5);    // controls the speed that the snow falls
    partsize = random (5, 10);
  }
  
}

class Snow {
Particle[] particles = new Particle[0];
int maxParticles =100;

Snow() {
}
void setMax(int val) {
maxParticles=val;
}
void draw(float ears) {
particles = (Particle[]) append(particles, new Particle(300, 0));
  if (particles.length>maxParticles) {
    particles = (Particle[]) subset(particles, 1);
  }
  for (int i=0; i<particles.length; i++) {
      particles[i].partsize *=0.975;  //make the snow stay reduce size
      particles[i].x += particles[i].xVel;
      particles[i].y += particles[i].yVel;
     if(ears>0.03) {
      particles[i].yVel +=0.2;   
     } else if(ears>0.05) {
       particles[i].yVel +=0.4;
     }
     else if(ears>0.07) {
       particles[i].yVel +=0.6;
     } else if(ears>0.09) {
       particles[i].yVel +=0.8;
     } else {
       particles[i].yVel-=0.1; //reverse gravity
       
     }
    ellipse(particles[i].x, particles[i].y, particles[i].partsize, particles[i].partsize);
  }
}
}

void setup() {
  size(640, 440);
  background(90);
  amp2 = new Amplitude(this);
  in2 = new AudioIn(this, 0);
  in2.start();
  amp2.input(in2);  
  mainCircle= new CircleThing(width/2,height/2,45);
  snowfall= new Snow();
  //bg = loadImage("bg.jpg");
  //bg.resize(width,height);
  //nyu=loadImage("nyu.png");
  //nyu.resize(int(nyu.width*0.5),int(nyu.height*0.5));
}      

void draw() {
  background(0);
  ears=amp2.analyze();
   stroke(255);
  strokeWeight(1);
  println(ears);
  fill(255);
  snowfall.draw(ears);
  stroke(75); 
  if(!movable) {
    
  mainCircle.draw(width/2,height/2,ears,190,nyu,false);  
  } else {
  mainCircle.draw(mouseX,mouseY,ears,190,nyu,false); 
  }
 

}

void mousePressed() {
movable=!movable;
}

void keyPressed() {
logoDisplay=!logoDisplay;
}

 

Romeno’s response to Eyeo2012 – Casey Reas

The video was fun to watch as there were many interesting projects. I liked how he replicated the project where there were motorized toy vehicles moving based on light intensity, into a simulation and then found a pattern.

His work seems, ‘extraterrestrial’ in some ways to me as they resemble actual beings which I think is what makes his work special and gives it life.

Romeno’s response to the Lev Manovich Reading

I found the reading interesting as the author explains how the media is now, “programmable” which is a strong term. I believe that the author is right and in fact in my opinion it is more programmable than he writes as there is so much fake news and controversies created from thin air. I also like how the author regards human language as discrete, “A human language is discrete on most scales: we speak in sentences; a sentence is made from words; a word consists from morphemes, and so on“. I learned about the history of the world wide web with really ancient web browsers like, “Netomat” and how structural programming was used in applications such as Macromedia Director’s Lingo. Overall it was an interesting read and got a lot out of it and I do think there is a huge change in the forms of media.