ux-pic.jpg
ux-pic.jpg

UX/UI


SCROLL DOWN

UX/UI


chciha

Chicha is  a digital platform that helps people become food entrepreneurs by crowdfunding their food dish, finding and renting commercial kitchen space, and creating the marketplace to to sell and market their food dishes. 

The name Chicha is derived from a sweet Peruvian drink made with purple corn called chicha morada. We chose the color purple for the logo as an homage to the drink. 

Below are the User Personas created for the site, along with sketches, and some wireframes.

USER PERSONAS

WIREFRAME-SITEMAP

MOBILE DESIGN

 

 

I worked on creating a responsive infinite message list. Maintained a material design aesthetic as well as many of the Google Design patterns. 

Delved deep in the user experience of infinite lists.
     

      Pros:

  • Efficient way to browse ocean of information without pagination/reloading
  • It's a responsive experience

      Cons:

  • People are goal oriented. If it's never ending, creates frustration. That's why I believe, Google Search still has pagination. Results are framed, indexed, presented clearly and orderly.
  • Turns every browsing experience into a complicated one. Gives no reference point.
  • There is a cost to clicking on an item. You go back to square one when done.
  • Too much choice overwhelms

CODE


CODE


Small module created for the lab that tracks movement inside the Havas Lab. A kinect V2 above the lab is used for tracking. This is then projected against a large wall for people to see movement in the lab during a given day. It has the ability to track up to 6 people. The view is top down with the outline showing the footprint of the lab. This is part of a dashboard that is still in process. Code below

HavasLab
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
<!doctype html>
<html>
  <head>
    <title>Socket Test</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script>
    <script type="text/javascript" src="js/sb-1.4.1.js"></script>

    <script type="text/javascript" src="class.locationPerson.js"></script>

    <script type="text/javascript">

	// when page loads call spacebrew setup function 
	$(window).on("load", setupSpacebrew);

	// wher the jquery mobile is ready to initialize the UI call the setUI function 
	$(document).bind("pageinit", setupUI);

	// Spacebrew Object
	var sb
		, app_name = "Dashboard Client"
		, values = {} 
		;

	/**
	 * setupSpacebrew Function that creates and configures the connection to the Spacebrew server.
	 * 				  It is called when the page loads.
	 */
	function setupSpacebrew (){
		var random_id = "0000" + Math.floor(Math.random() * 10000);
 
		app_name = app_name + ' ' + random_id.substring(random_id.length-4);

		console.log("Setting up spacebrew connection");
		sb = new Spacebrew.Client();

		sb.name(app_name);
		sb.description("Dashboard Client.");

		//List subscribers
		sb.addSubscribe("location_pos", "string");

		// override Spacebrew events - this is how you catch events coming from Spacebrew
		sb.onStringMessage = onStringMessage;
		sb.onOpen = onOpen;

		// connect to spacbrew
		sb.connect();
	};

        /**
        * Function that is called when Spacebrew connection is established
        */
        function onOpen() {
                        var message = "Connected as <strong>" + sb.name() + "</strong>. ";
                        if (sb.name() === app_name) {
                                message += "<br>You can customize this app's name in the query string by adding <strong>name=your_app_name</strong>."
                        }
                        $("#name").html( message );

			canvasApp();
        }


        /**
        * setupUI Function that create the event listeners for the sliders. It creates an callback
        *                 function that sends a spacebrew message whenever an slide event is received.
        */
        function setupUI() {
                        console.log("Setting up the UI listeners");
                        // when the slider state changes it sends a message to spacebrew
                        $(".slider").bind( "change", function(event, ui) {
                                if (values[event.target.id] != event.target.value) {
                                        sb.send(event.target.id, "range", event.target.value);
                                        values[event.target.id] = event.target.value;
                                }
                     });
        }

        /**
        * onRangeMessage Function that is called whenever new spacebrew range messages are received.
        *                                It accepts two parameters:
        * @param  {String} name        Holds name of the subscription feed channel
        * @param  {Integer} value      Holds value received from the subscription feed
        */
        function onStringMessage(name, value){

        	console.log("Received new string ", value);
		arrVals = value.split(',');
		
		_i = parseInt(arrVals[0]);

		arrLocPerson[_i].set(arrVals[1], arrVals[2]);	
        };

    </script>

    <style>
       	.square {
         border-style: solid;
         border-width: 3px;
         border-color: #7f7f7f;
         width: 400px;
         height: 400px;
       }

	body {
		background-color: #f0f0f0;
		padding: 0px;
		margin: 0px;
		border: 0px none;	
	}

        #frame {
		position:relative;
		background-color: #ff0000;
		top:0px;
		left:0px;
	}

        .widget_small {
		width:460px;height:335px;background-color:#000000;position:absolute;
	}

	.widget_med {
		width:685px;height:460px;background-color:#000000;position:absolute;
	}

	.widget_med2 {
		width:1040px;height:460px;background-color:#000000;position:absolute;
	}

	.widget_lrg {
		width:930px;height:1040px;background-color:#000000;position:absolute;
	}

	.txtSmallWhite {
		padding: 20px;
		font-family: "Helvetica Neue";
		color: #ffffff;
		font-size: 16px;
	}

	.txtSmallGreen {
		font-family: "Helvetica Neue";
		color: #00ff00;	
		font-size: 16px;
	}

	.txtTitleGreen {
		font-family: "Helvetica Neue";
		color: #00ff00;	
		font-size: 46px;
		text-align: center;
	}

	#labmovement_canvas {
	
	}

    </style>
  </head>

  <body>
    <script type="text/javascript">


      function canvasApp()
      {
        console.log(window.innerWidth + " " + window.innerHeight);

	FRAME_SIZE_W = window.innerWidth;
	FRAME_SIZE_H = window.innerHeight;

        //var COLORS = [ '#ffff00', '#ff00ff', '#00ffff'];
        var COLORS = [ '#ffff00', '#ff00ff', '#00ffff', '#ff0000', '#00ff00', '#0000ff' ];
	var COLOR_NUM = COLORS.length;
	var _colorIndex = 0;


  	context = labmovement_canvas.getContext('2d');
	if (!context) {
   	 	return;
  	}

	context.strokeStyle = '#ffffff';
	context.lineWidth = 2;

        setInterval(run,intervalTime);

	//Test vals
	arrLocPerson[0] = new LocationPerson(150,150,COLORS[0],1);
	arrLocPerson[1] = new LocationPerson(150,150,COLORS[1],2);
	arrLocPerson[2] = new LocationPerson(150,150,COLORS[2],3);
	arrLocPerson[3] = new LocationPerson(150,150,COLORS[3],4);
	arrLocPerson[4] = new LocationPerson(150,150,COLORS[4],5);
	arrLocPerson[5] = new LocationPerson(150,150,COLORS[5],6);

        function run() {

	  context.clearRect(0, 0, labmovement_canvas.width, labmovement_canvas.height);

          render_lab();
	  render_lab_ppl();
        }

	function render_lab_ppl() {

	  for (var i=0; i<6; i++)
          {
	    //arrLocPerson[i].move();
	    arrLocPerson[i].drawLines();
	  }

	  for (var i=0; i<6; i++)
          {
	    arrLocPerson[i].render();
	  }
	}

	function render_lab() {
	
	  context.strokeStyle = "#ffffff";
	  context.lineWidth = 4;

          context.beginPath();
          context.moveTo(131,348);
          context.lineTo(375,6);
          context.lineTo(733,266);
          context.lineTo(675,351);
          context.lineTo(675,387);
          context.lineTo(797,387);
          context.lineTo(797,757);
          context.lineTo(730,757);
          context.lineTo(730,836);
          context.lineTo(187,836);
          context.lineTo(187,390);
          context.lineTo(131,348);
          context.stroke();
	}

      }

      const FRAME_RATE = 10;
      const SQUARE_SIZE = 400;

      var FRAME_SIZE_W;
      var FRAME_SIZE_H;

      var intervalTime= 1000 / FRAME_RATE;

      //light window
      var _lightY = 0;
      var _lastLightY = 0;

      var _lightX = 0;

      //Arr Loc Person
      var arrLocPerson = new Array();

    </script>

    <div id="frame">

 	<div class="widget_lrg" style="left:20px;top:20px" >

	<div class="txtTitleGreen" style="padding-top:350px">
	<div style="font-size:120px;padding-bottom:10px">[ ]</div>
        HAVAS<br>LAB
        </div>

	</div>

 	<div class="widget_lrg" style="left:970px;top:20px">

		<div class="txtSmallWhite">
		LAB<br>
		MOVEMENT<br>
		-
		</div>

		<canvas id="labmovement_canvas" width="920" height="1040">
		your browser does not support HTML 5 Canvas
		</canvas>

	</div>
    </div>

  </body>
</html>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
 /**********************
      * LOCATION PERSON
      **********************/
      function LocationPerson(_x, _y, _color, _index) {

	this.x = _x;
        this.y = _y;
	this.color = _color;
        this.arrPts = [];
	this.active = false;
	this.last_update = Date.now();
	this.index = _index;

        this._last_x;
	this._last_y;

    	this.kx = 0;
	this.kz = 0;

        this.move = function() {

	  this.x += Math.floor(Math.random() * 20 - 10);
	  this.y += Math.floor(Math.random() * 20 - 10);

          this.arrPts.push([this.x,this.y]);

        };

        this.render = function() {

	  if (this.active) {

            context.beginPath();
	    context.arc(this.x, this.y, 15, 0, 2 * Math.PI, false);
	    context.fillStyle = this.color;
	    context.fill();

	    if (Date.now() > this.last_update + 1000)
            {
		this.active = false;
            }

	  }

	  //Write location
          context.fillStyle = this.color;
          context.font = "35px Arial";
          context.fillText(this.kx+","+this.kz, 20, (this.index*35));

        };

        this.drawLines = function() {

          context.beginPath();
	 
	  context.strokeStyle = this.color;
	  context.lineWidth = 1;

          _length = this.arrPts.length;

	  if (_length>0) {
            context.moveTo(this.arrPts[0][0],this.arrPts[0][1]);

            if (_length > 300) {
              this.arrPts.shift(); 
            }
          }

	  for (var i=1; i<this.arrPts.length; i++) {

            context.lineTo(this.arrPts[i][0],this.arrPts[i][1]);
          }
          context.stroke();

        };

        this.set = function(_x, _z) {

          this.kx = _x;
    	  this.kz = _z;

	  this.active = true;
	  this.last_update = Date.now();

          //CENTIMETERS TO PIXELS
          var CENTIMETER_TO_PIXELS = .9;
          var CENTIMETER_TO_PIXELS_VERT = 1.2;
          var CENTER_POINT = 450; 

          //CONVERT CENTIMETERS TO PIXELS
	  this.x = CENTER_POINT - _x * CENTIMETER_TO_PIXELS;
	  this.y = 800 - _z * CENTIMETER_TO_PIXELS;

	  if (this.x != this._last_x || this.y != this._last_y)
	  {
            this.arrPts.push([this.x,this.y]);
          }

	  this._last_x = this.x;
	  this._last_y = this.y;

        }
      }

This sketch uses your webcam to create a polygonal representation of the image in real time

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//inspiration:
//http://robbietilton.com
//http://vormplus.be/blog/article/voronoi-paintings-with-toxiclibs

import processing.video.*;
import toxi.processing.*;
import toxi.geom.*;
import toxi.geom.mesh2d.*; 
import toxi.util.*;

Capture video;
float size = 11000;
Voronoi voronoi;
PImage imgFrame;

int pd = 70;
int cols, rows;
int x, y;
int numPix;
float colorThresh = 30;

void setup() {
  size(640,480);
  video = new Capture(this, width, height);
  video.start();
  //if you want to add a pic, uncomment this line and add the image
  //imgFrame = loadImage("test.png");
  imgFrame = createImage(640,480,RGB);
  smooth();

  cols = width/pd;
  rows = height/pd;
  numPix = imgFrame.width * imgFrame.height;

  voronoi = new Voronoi(size);

}

void addPoint() {
  imgFrame.copy(video,0,0,640,480,0,0,640,480);
  imgFrame.updatePixels();

  for (x=1; x<imgFrame.width; x+=cols) {
    for (y=1; y<imgFrame.height; y+=rows) { 
      int loc = x + y*imgFrame.width;
      color pix = imgFrame.pixels[loc];
      int leftLoc = (x-1) + (y-1)*imgFrame.width;
      color leftPix = imgFrame.pixels[leftLoc];
      // compare ver: color
      float red1 = red(pix);
      float green1 = green(pix);
      float blue1 = blue(pix);
      float red2 = red(leftPix);
      float green2 = green(leftPix);
      float blue2 = blue(leftPix);
      float diff = dist(red1,green1,blue1,red2,green2,blue2);

      if (diff > colorThresh) {
//        voronoi.addPoint(new Vec2D(x+random(-2, 2), y+random(-2, 2)));
        voronoi.addPoint(new Vec2D(x, y));
      }
    }
  }
}


void drawTriangle(){

  for (Triangle2D tri : voronoi.getTriangles()) {
    color pix;

    // use Centroid's color
    Vec2D locVec = tri.computeCentroid();
    int loc = (int)((locVec.x) + (locVec.y)*imgFrame.width);

    pix = imgFrame.pixels[constrain(loc,0,numPix-1)];
//    stroke(pix,100);
    fill(pix);
    noStroke();

    beginShape(TRIANGLES);
    stroke(pix);
    vertex(tri.a.x, tri.a.y);
    vertex(tri.b.x, tri.b.y);
    vertex(tri.c.x, tri.c.y);
    endShape();    
  }
}

void draw() {
  background(255);
  video.read();

  if(shoImage) {
    image(imgFrame, 0, 0);
  } else {
    addPoint(); 
    drawTriangle();
  }

  //fill(0);  
  //rect(15,5,30,20);

  // show frame rate
  //fill(255);
  //text(int(frameRate), 20, 20);

  updatePixels();

  if (frameCount%10 == 0)
    voronoi = new Voronoi(size);

}

boolean shoImage = false;
//boolean saveImg = false;

Drum sequencer made with P5, server.io, and tone.js. You can save your sequence and play it back later.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
var bpm = 90;
var current = 0;
// drums
var kick = new Tone.Sampler('audio/kick.mp3');
var snare = new Tone.Sampler('audio/snare.mp3');
var hh = new Tone.Sampler('audio/hh.mp3');
var hho =new Tone.Sampler('audio/agogoHigh.mp3');
var drumArray = [kick, snare, hh, hho];
for (var i in drumArray) {
  drumArray[i].toMaster();
}

var socket = io();

socket.on('initSequencer', function(data) {
  parseSeqObj(data);
  console.log(data);
});

// how many blocks fit horizontally and vertically:
var hDiv = drumArray.length;
var wDiv = 16;

// array of Blocks
var blocks = [];

// ===========================
// Drum Pattern Array for i/o
// ===========================
var kickArray = new Array(wDiv);
var snareArray = new Array(wDiv);
var hhArray = new Array(wDiv);
var hhoArray = new Array(wDiv);
var drumPatterns = [kickArray, snareArray, hhArray, hhoArray];

// set all the drums to 0 empty to start
function clearBlocks() {
  for (var i in drumPatterns) {
    for (var j = 0; j < wDiv; j++) {
      drumPatterns[i][j] = false;
    }
  }
  blocks = [];
}

function parseSeqObj(data) {
  console.log(data);
  clearBlocks();
  drumPatterns = data;
  for (var i in drumPatterns) {
    console.log('doin it');
    for (var j = 0; j < wDiv; j++) {
      if (drumPatterns[i][j] === true) {
        var bX = width/wDiv * j;
        var bY = height/hDiv * i; // reverse?
        blocks.push( new Block(bX, bY));
      };
    }
  }
}

function savePattern() {
  saveJSON(drumPatterns, 'myPattern.json');
}

function setup() {
  createCanvas(800, 400);

  // set up tone transport
  Tone.Transport.setInterval(function(time){
    increment(time);
  }, "16n");
  Tone.Transport.start();
  Tone.Transport.setBpm(bpm);

  // ============ 
  // UI w/ p5.dom
  // ============
  createP('');
  var volumeSlider = createSlider();
  var vol = createP('Volume');
  volumeSlider.mouseMoved( function() {
    Tone.Master.setVolume( map(volumeSlider.value(), 0, 100, -80, 1));
  });
  volumeSlider.value(10);
  Tone.Master.setVolume( map(volumeSlider.value(), 0, 100, -80, 1));


  var clear = createButton('clear');
  clear.mousePressed( function() {
    clearBlocks();
  });
  var save = createButton('save');
  save.mousePressed( function() {
    savePattern();
  });

  var tempoSlider = createSlider();
  tempoSlider.mouseMoved( function() {
    Tone.Transport.setBpm( map(tempoSlider.value(), 0, 100, 40, 200));
  });
  tempoSlider.value(map(bpm, 40, 200, 0, 100));


  // ==================
  // Import Saved Files
  // ==================
  var dropZone = createDiv('Drop your saved JSON file here');
  dropZone.id('drop_zone');
  // Add some events
  dropZone.elt.addEventListener('dragover', handleDragOver, false);
  dropZone.elt.addEventListener('drop', handleFileSelect, false);
  dropZone.elt.addEventListener('dragleave', handleDragLeave, false);
  
  // When you drag a file on top
  function handleDragOver(evt) {
    // Stop the default browser behavior
    evt.stopPropagation();
    evt.preventDefault();
    dropZone.style('background','#fff000');
  }
  
  // If mouse leaves
  function handleDragLeave(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    dropZone.style('background','#fff');
  }
  
  // If you drop the file
  function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    dropZone.style('background','');

    // A FileList
    var files = evt.dataTransfer.files;
    // Show some properties
    for (var i = 0, f; f = files[i]; i++) {
      // Read the file and process the result
      var reader = new FileReader();
      reader.readAsText(f);
      reader.onload = function(e) {
        parseSeqObj(JSON.parse(e.target.result));
        sendDrumPattern();
      }
    }
  }
} // end setup

// ========================
// keep time and play drums
// ========================
var step = 0;
function increment(time) {
  step++;
  for (var i in blocks) {
    var stepColumn = map( (step%wDiv), 0, wDiv, 0, width);
    if (blocks[i].x === stepColumn) {
      var whichDrum = Math.round( map(blocks[i].y, 0, height, hDiv, 0) ) - 1;
      playDrum(whichDrum, time);
      blocks[i].c[1] = 0;
      blocks[i].c[2] = 0;
    }
  }
}

function playDrum(whichDrum, time) {
  drumArray[whichDrum].triggerAttackRelease(1, time);
  current++;
}

// ==========================
// draw and mouse interaction
// ==========================
function draw() {
  background(0);

  // draw four boxes
  fill(20);
  rect(0, 0, width/4, height);
  fill(40);
  rect(width/4, 0, width/4, height);
  fill(20);
  rect(width/4 * 2, 0, width/4, height);
  fill(40);
  rect(width/4 * 3, 0, width/4, height);

  for (var i in blocks) {
    blocks[i].update();
  }
}

var pressedX, pressedY;
function mousePressed() {
  // if it is touching a preexisting block, remove that block
  pressedX = null;
  pressedY = null;
  for (var i in blocks) {
    if (blocks[i].isTouching(mouseX, mouseY) ) {
      pressedX = blocks[i].x;
      pressedY = blocks[i].y;
      blocks[i].remove();
      blocks.splice(i, 1);
    }
  }
}

function mouseDragged() {
  var halfBlockWidth = width/wDiv/2;
  var halfBlockHeight = height/hDiv/2;

  pressedX = null;
  pressedY = null;
  rect(mouseX - halfBlockWidth, mouseY - halfBlockHeight, width/wDiv, height/hDiv);
}

function mouseReleased() {
  // map block position to grid

  var halfBlockWidth = width/wDiv/2;
  var halfBlockHeight = height/hDiv/2;
  var bX = Math.round(map(mouseX - halfBlockWidth, 0, width, 0, wDiv))/wDiv * width;
  var bY = Math.round(map(mouseY - halfBlockHeight, 0, height, 0, hDiv))/hDiv * height;
  // do not re-add block if mouse is in same place
  if (bX === pressedX && bY === pressedY) {
    return;
  }
  if (pressedX !== null && Math.abs(pmouseX - mouseX) < 3 && Math.abs(pmouseY - mouseY) < 3) {
    return;
  }
  // only add block if mouse is within width/height of canvas
  if (bX < width && bX >= 0 && bY < height && bY >= 0) {
    blocks.push( new Block(bX, bY));
  }

  // update socket.io
  sendDrumPattern();
}

// ============
// Block class
// ============
var Block = function(x, y) {
  this.x = x;
  this.y = y;
  this.w = width/wDiv;
  this.h = height/hDiv;
  this.c = [255, 255, 255];

  // update drumPattern array
  var whichDrum = drumArray.length - Math.round( map(this.y, 0, height, hDiv, 0) );
  var whichStep = Math.round(map(this.x, 0, width, 0, wDiv));
  drumPatterns[whichDrum][whichStep] = true;
}

Block.prototype.update = function() {
  this.c[2] += 20;
  this.c[1] += 20;
  fill(this.c);
  rect(this.x, this.y, this.w, this.h);
}

Block.prototype.isTouching = function(x, y) {
  if ( (this.x <= x) &&  (x <= (this.x + this.w) ) && (this.y <= y) && (y <= (this.y + this.h) ) ) {
    return true;
  }
  else {
    return false;
  }
}

Block.prototype.remove = function() {
  // update drumPattern array to zero
  var whichDrum = drumArray.length - Math.round( map(this.y, 0, height, hDiv, 0) );
  var whichStep = Math.round(map(this.x, 0, width, 0, wDiv));
  drumPatterns[whichDrum][whichStep] = null;

  // update socket.io
  sendDrumPattern();
}

// ============
// SOCKET STUFF
// ============
function sendDrumPattern() {
  socket.emit('changedPattern', drumPatterns);
}

socket.on('updateSeq', function (data) {
  console.log(data);
  parseSeqObj(data);
});