Browse Source

Move most of the code into a function

Thomas Dy 11 years ago
parent
commit
9a7474b365
1 changed files with 521 additions and 517 deletions
  1. 521 517
      game.js

+ 521 - 517
game.js

@@ -1,559 +1,563 @@
-var canvas = document.getElementById('game');
-var ctx = canvas.getContext('2d');
-
-var beatUrls = [
-	'sound/b1.mp3',
-	'sound/b2.mp3',
-	'sound/b3.mp3',
-	'sound/b4.mp3',
-	'sound/b5.mp3'
-];
-
-var createSound = function(name, url) {
-	var deferred = Q.defer();
-	soundManager.createSound({
-		id: name,
-		url: url,
-		autoLoad: true,
-		onload: function() {
-			deferred.resolve(this);
-		}
-	});
-	return deferred.promise;
-};
-
-var starColors = [
-  'maroon',
-	'red',
-	'orange',
-	'yellow',
-  'olive',
-	'green',
-  'teal',
-	'blue',
-  'navy',
-	'purple'
-];
-
-var paused = false;
-var debug = false;
-var beats = [];
-var beatTime = 0;
-var currentBeats = [];
-var gotPoint = false;
-
-var switchBeat = function(index) {
-	var currentBeat = beats[index];
-  activeTarget.beat = currentBeat;
-	currentBeats.push(currentBeat);
-	if(currentBeats.length > 4) {
-		currentBeats.splice(0, 1);
-	}
-};
-
-// Sound
-var soundDeferred = Q.defer();
-var soundPromise = soundDeferred.promise;
-soundManager.setup({
-	url: 'swf/',
-	flashVersion: 9,
-	onready: function() {
-		soundDeferred.resolve(this);
-	}
-});
-
-soundPromise = soundPromise.then(function() {
-	var beatPromises = [];
-	beatUrls.map(function(elem, index) {
-		beatPromises.push(createSound('beat'+index, elem));
-	});
-
-	return Q.spread([
-		Q.all(beatPromises),
-		createSound('get', 'sound/get.mp3')
-	], function(beatz) {
-		beats = beatz;
-	})
-});
-
-// Input handling
-var keysDown = {};
-
-addEventListener('keydown', function(e) {
-	keysDown[e.keyCode] = true;
-}, false);
-
-addEventListener('keyup', function(e) {
-	if(e.keyCode == 68) {
-		debug = !debug;
-	}
-	if(e.keyCode == 80) {
-		pause();
-	}
-	delete keysDown[e.keyCode];
-}, false);
-
-addEventListener('blur', function() {
-  if(!paused) pause();
-});
-
-
-// Constants
-var RADIUS = 8;
-var LEAF_SIZE = 8;
-var LEVEL_SIZE = 800;
-var ALLOWANCE = (LEVEL_SIZE - canvas.width)/2;
-var METER_WIDTH = 120;
-var METER_HEIGHT = 40;
-var METER_SPEED = 10;
-var PLAYER_SPEED = 50;
-var STAGE_SPEED = 25;
-var RIPPLE_SIZE = 10;
-var RIPPLE_SPEED = 10;
-var DOT_SIZE = 3;
-var DOT_DISTANCE = 4 * 5;
-
-
-// Drawing functions
-function drawCircle(point, r, w, color) {
-	ctx.beginPath();
-	ctx.arc(point.x, point.y, r, 0, 2*Math.PI, false);
-	ctx.lineWidth = w;
-	ctx.strokeStyle = color;
-	ctx.stroke();
-}
-
-function drawLeafV(point, h, w) {
-	ctx.beginPath();
-	ctx.moveTo(point.x, point.y);
-	ctx.quadraticCurveTo(point.x + w, point.y + h/2, point.x, point.y + h);
-	ctx.quadraticCurveTo(point.x - w, point.y + h/2, point.x, point.y);
-	ctx.fill();
-}
-
-function drawLeafH(point, w, h) {
-	ctx.beginPath();
-	ctx.moveTo(point.x, point.y);
-	ctx.quadraticCurveTo(point.x + w/2, point.y + h, point.x + w, point.y);
-	ctx.quadraticCurveTo(point.x + w/2, point.y - h, point.x, point.y);
-	ctx.fill();
-}
-
-function drawPlayer(point) {
-	drawCircle(point, RADIUS, 5, "#FFFFFF");
-}
-
-function drawSpectrum() {
-  var barHeight = 30;
-  var barWidth = 6;
-  var barSpacing = 2;
-  var margin = 5;
-  ctx.strokeStyle = 'white';
-  ctx.lineWidth = 2;
-  ctx.beginPath();
-  ctx.rect(
-    margin - ctx.lineWidth,
-    canvas.height - barHeight - ctx.lineWidth - margin,
-    activeTarget.spectrum.length * (barWidth+barSpacing) + 2 * ctx.lineWidth - barSpacing,
-    barHeight + 2 * ctx.lineWidth
-  );
-  ctx.stroke();
-  for(var i = 0; i < activeTarget.spectrum.length; ++i) {
-    var height = activeTarget.spectrum[i]*20;
-    ctx.fillStyle = starColors[i];
-    ctx.fillRect(i * (barWidth + barSpacing) + margin, canvas.height - height - margin, barWidth, height);
-  }
+function createSound(name, url) {
+  var deferred = Q.defer();
+  soundManager.createSound({
+    id: name,
+    url: url,
+    autoLoad: true,
+    onload: function() {
+      deferred.resolve(this);
+    }
+  });
+  return deferred.promise;
 }
 
 function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }
 
 function lerp(from, to, p) {
-	return to * p + from * (1 - p);
+  return to * p + from * (1 - p);
 }
 
 function ease(v) { return v * v * (3 - 2 * v); }
 
 function easeOutExpo(t, b, c, d) {
-	return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+  return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
 }
 
 function color(r,g,b,a) {
-	return 'rgba('+r+','+g+','+b+','+a.toFixed(5)+')';
-}
-
-function distX(a, b) {
-  var dx = a.x - b.x;
-  if(dx > LEVEL_SIZE / 2) dx -= LEVEL_SIZE;
-  if(dx < -LEVEL_SIZE / 2) dx += LEVEL_SIZE;
-  return dx;
-}
-
-function distSq(a, b) {
-	var c = distX(a, b);
-	var d = a.y - b.y;
-	return c * c + d * d;
-}
-
-function colliding(a, b) {
-	return distSq(a, b) <= 4 * RADIUS * RADIUS;
-}
-
-// "Entities"
-function Meter() {
-	this.offset = 10;
-	this.amp = 0;
-	this.percentage = 0;
-	this.direction = METER_SPEED;
+  return 'rgba('+r+','+g+','+b+','+a.toFixed(5)+')';
 }
 
-Meter.prototype.draw = function() {
-	var left = (canvas.width - METER_WIDTH) / 2;
-	var vmid = this.offset + METER_HEIGHT / 2;
-	var third = METER_WIDTH / 3;
-	var peak = METER_HEIGHT / 2;
-
-	ctx.fillStyle = 'black';
-	ctx.rect(left, this.offset, METER_WIDTH, METER_HEIGHT);
-	ctx.fill();
-
-	var cpUp = lerp(vmid, vmid + peak * this.amp, this.percentage);
-	var cpDown = lerp(vmid, vmid - peak * this.amp, this.percentage);
-
-	ctx.lineWidth = 2;
-	ctx.strokeStyle = 'red';
-	ctx.beginPath();
-	var pos = left;
-	ctx.moveTo(left, vmid);
-	ctx.quadraticCurveTo(pos+third/2, cpUp, pos+third, vmid);
-	pos += third;
-	ctx.quadraticCurveTo(pos+third/2, cpDown, pos+third, vmid);
-	pos += third;
-	ctx.quadraticCurveTo(pos+third/2, cpUp, pos+third, vmid);
-	ctx.stroke();
-
-	ctx.lineWidth = 3;
-	ctx.strokeStyle = 'white';
-	ctx.beginPath();
-	ctx.rect(left, this.offset, METER_WIDTH, METER_HEIGHT);
-	ctx.stroke();
-};
-
-Meter.prototype.onUpdate = function(dt) {
-	if(this.percentage > 1) {
-		this.direction = -METER_SPEED;
-	}
-	if(this.percentage < -1) {
-		this.direction = METER_SPEED;
-	}
-	this.percentage += this.direction * dt;
-	this.amp = 0.5 + lerp(0, 1, activeTarget.shimmerFactor);
-};
-
-function Star(x, y) {
-	this.x = x;
-	this.y = y;
-	this.alpha = 0;
-  this.randomize();
-}
-
-Star.prototype.randomize = function() {
-  this.type = Math.floor(Math.random()*starColors.length);
-  this.color = starColors[this.type];
-};
-
-Star.prototype.draw = function() {
-	ctx.fillStyle = this.color;
-	ctx.globalAlpha = this.alpha;
-	var adjPosition = translatePoints(this);
-	ctx.fillRect(adjPosition.x, adjPosition.y, DOT_SIZE, DOT_SIZE);
-
-	var top    = {x: adjPosition.x + DOT_SIZE/2,   y: adjPosition.y - DOT_SIZE/2  };
-	var bottom = {x: adjPosition.x + DOT_SIZE/2,   y: adjPosition.y + DOT_SIZE/2*3};
-	var left   = {x: adjPosition.x - DOT_SIZE/2,   y: adjPosition.y + DOT_SIZE/2  };
-	var right  = {x: adjPosition.x + DOT_SIZE/2*3, y: adjPosition.y + DOT_SIZE/2  };
-
-	drawLeafH(left, -LEAF_SIZE, LEAF_SIZE/2);
-	drawLeafH(right, LEAF_SIZE, LEAF_SIZE/2);
-	drawLeafV(top, -LEAF_SIZE, LEAF_SIZE/2);
-	drawLeafV(bottom, LEAF_SIZE, LEAF_SIZE/2);
-	ctx.globalAlpha = 1;
-};
-
-Star.prototype.onUpdate = function() {
-	if(gotPoint) {
-		this.randomize();
-	}
-	if(this.y > canvas.height + DOT_DISTANCE * 2) {
-		this.y -= canvas.height + DOT_DISTANCE * 4;
-	}
-	var newAlpha = 1.5 * activeTarget.spectrum[this.type];
-  var a = 0.001;
-  if(newAlpha > this.alpha) {
-    a = 0.9;
+var SoundSafari = function(canvas, beatInfo, pSoundManager) {
+  var ctx = canvas.getContext('2d');
+
+  // Constants
+  var RADIUS = 8;
+  var LEAF_SIZE = 8;
+  var LEVEL_SIZE = 800;
+  var ALLOWANCE = (LEVEL_SIZE - canvas.width)/2;
+  var METER_WIDTH = 120;
+  var METER_HEIGHT = 40;
+  var METER_SPEED = 10;
+  var PLAYER_SPEED = 50;
+  var STAGE_SPEED = 25;
+  var RIPPLE_SIZE = 10;
+  var RIPPLE_SPEED = 10;
+  var DOT_SIZE = 3;
+  var DOT_DISTANCE = 4 * 5;
+
+  var starColors = [
+    'maroon',
+    'red',
+    'orange',
+    'yellow',
+    'olive',
+    'green',
+    'teal',
+    'blue',
+    'navy',
+    'purple'
+  ];
+
+  var paused = false;
+  var debug = false;
+
+  var beats = [];
+  var gameTime = 0;
+  var currentBeats = [];
+  var gotPoint = false;
+
+  var switchBeat = function(index) {
+    var currentBeat = beats[index];
+    currentBeats.push(currentBeat);
+    if(currentBeats.length > 4) {
+      currentBeats.splice(0, 1);
+    }
+
+    activeTarget = new Target(currentBeat);
+    entities.push(activeTarget);
+  };
+
+  var soundPromise = pSoundManager.then(function() {
+    var soundPromises = beatInfo.map(function(elem, index) {
+      var beat = { info: elem, rounds: 0 };
+      var promise = createSound('beat'+index, elem.url).then(function(sound) {
+        beat.sound = sound;
+      });
+      beats.push(beat);
+      return promise;
+    });
+    soundPromises.push(createSound('get', 'sound/get.mp3'));
+
+    return Q.all(soundPromises);
+  });
+
+  // Input handling
+  var keysDown = {};
+
+  addEventListener('keydown', function(e) {
+    keysDown[e.keyCode] = true;
+  }, false);
+
+  addEventListener('keyup', function(e) {
+    if(e.keyCode == 68) {
+      debug = !debug;
+    }
+    if(e.keyCode == 80) {
+      pause();
+    }
+    delete keysDown[e.keyCode];
+  }, false);
+
+  addEventListener('blur', function() {
+    if(!paused) pause();
+  });
+
+  // Drawing functions
+  function drawCircle(point, r, w, color) {
+    ctx.beginPath();
+    ctx.arc(point.x, point.y, r, 0, 2*Math.PI, false);
+    ctx.lineWidth = w;
+    ctx.strokeStyle = color;
+    ctx.stroke();
   }
-	this.alpha = Math.min(1, activeTarget.shimmerFactor * (this.alpha * (1-a) + newAlpha * a));
-};
-
-function Dot(x, y) {
-	this.x = x;
-	this.y = y;
-}
-
-Dot.prototype.draw = function() {
-	ctx.fillStyle = 'gray';
-	var adjPosition = translatePoints(this);
-	ctx.fillRect(adjPosition.x, adjPosition.y, DOT_SIZE, DOT_SIZE);
-};
 
-Dot.prototype.onUpdate = function() {
-	if(this.y > canvas.height + DOT_DISTANCE * 2) {
-		this.y -= canvas.height + DOT_DISTANCE * 4;
-	}
-};
-
-function Target(beat) {
-  this.reset();
-  this.beat = beat;
-  this.alpha = 0;
-  if(points < 4) {
-    this.alpha = ease(1-points/4);
+  function drawLeafV(point, h, w) {
+    ctx.beginPath();
+    ctx.moveTo(point.x, point.y);
+    ctx.quadraticCurveTo(point.x + w, point.y + h/2, point.x, point.y + h);
+    ctx.quadraticCurveTo(point.x - w, point.y + h/2, point.x, point.y);
+    ctx.fill();
   }
-	this.rippleCounter = 0;
-}
-
-Target.prototype.reset = function() {
-  this.y = -20;
-  this.x = Math.random()*LEVEL_SIZE;
-};
 
-Target.prototype.color = function(p) {
-	return color(0, 255, 0, p*this.alpha);
-};
+  function drawLeafH(point, w, h) {
+    ctx.beginPath();
+    ctx.moveTo(point.x, point.y);
+    ctx.quadraticCurveTo(point.x + w/2, point.y + h, point.x + w, point.y);
+    ctx.quadraticCurveTo(point.x + w/2, point.y - h, point.x, point.y);
+    ctx.fill();
+  }
 
-Target.prototype.draw = function() {
-	var adjPosition = translatePoints(this);
-	drawCircle(adjPosition, RADIUS, 3, this.color(1));
-	drawCircle(adjPosition, 2, 3, this.color(1));
+  function drawPlayer(point) {
+    drawCircle(point, RADIUS, 5, "#FFFFFF");
+  }
 
-	// ripples
-	var p = lerp(1, 0, ease(this.rippleCounter/RIPPLE_SIZE));
-	drawCircle(adjPosition, RADIUS+7+this.rippleCounter, 1, this.color(p));
-	drawCircle(adjPosition, RADIUS+4+this.rippleCounter, 1, this.color(p*0.8));
-	drawCircle(adjPosition, RADIUS+1+this.rippleCounter, 1, this.color(p*0.5));
-};
+  function drawSpectrum() {
+    var barHeight = 30;
+    var barWidth = 6;
+    var barSpacing = 2;
+    var margin = 5;
+    ctx.strokeStyle = 'white';
+    ctx.lineWidth = 2;
+    ctx.beginPath();
+    ctx.rect(
+      margin - ctx.lineWidth,
+      canvas.height - barHeight - ctx.lineWidth - margin,
+      activeTarget.spectrum.length * (barWidth+barSpacing) + 2 * ctx.lineWidth - barSpacing,
+      barHeight + 2 * ctx.lineWidth
+    );
+    ctx.stroke();
+    for(var i = 0; i < activeTarget.spectrum.length; ++i) {
+      var height = activeTarget.spectrum[i]*20;
+      ctx.fillStyle = starColors[i];
+      ctx.fillRect(i * (barWidth + barSpacing) + margin, canvas.height - height - margin, barWidth, height);
+    }
+  }
 
-Target.prototype.onUpdate = function(dt, t) {
-	this.rippleCounter += RIPPLE_SPEED * dt;
-	if(this.rippleCounter > RIPPLE_SIZE) {
-		this.rippleCounter = 0;
-	}
-
-	if(gotPoint) {
-		gotPoint = false;
-	}
-
-	if(colliding(player, this)) {
-		gotPoint = true;
-    this.beat.setPan(0);
-    this.beat.setVolume(100);
-		this.shouldDelete = true;
-    return;
-	}
-
-	if(this.y > canvas.height + 20) {
-		this.reset();
-	}
-
-  var xDist = distX(this, player);
-	var s = sign(xDist);
-	var xDistAbs = easeOutExpo(Math.abs(xDist), 0, 100, LEVEL_SIZE/2);
-
-	var yDist = this.y - player.y;
-	var angle = 0;
-	if(yDist != 0) {
-		angle = easeOutExpo(Math.abs(Math.atan2(Math.abs(yDist), xDist) - Math.PI/2), 0, 100, Math.PI);
-	}
-	else {
-		angle = s * 100;
-	}
-  this.beat.setPan(Math.max(xDistAbs, angle) * s);
-
-	var rDist = xDist*xDist + yDist*yDist;
-	if(rDist >= 250000) {
-		rDist = 250000;
-	}
-	rDist = 250000 - rDist;
-	rDist /= 2500;
-  this.beat.setVolume(rDist);
-
-	this.shimmerFactor = easeOutExpo(Math.abs(xDist), 1, -1, LEVEL_SIZE/2) * rDist / 100;
-	if(isNaN(this.shimmerFactor) || this.shimmerFactor < 0) {
-		this.shimmerFactor = 0;
-	}
-
-  this.spectrum = this.beat.getSpectrum(t*1000);
-};
+  function distX(a, b) {
+    var dx = a.x - b.x;
+    if(dx > LEVEL_SIZE / 2) dx -= LEVEL_SIZE;
+    if(dx < -LEVEL_SIZE / 2) dx += LEVEL_SIZE;
+    return dx;
+  }
 
-var player = {
-	x: 0,
-	y: canvas.height - 20,
-	dx: PLAYER_SPEED
-};
+  function distSq(a, b) {
+    var c = distX(a, b);
+    var d = a.y - b.y;
+    return c * c + d * d;
+  }
 
-var meter = new Meter();
+  function colliding(a, b) {
+    return distSq(a, b) <= 4 * RADIUS * RADIUS;
+  }
 
-var points;
-var entities;
-var activeTarget;
+  // "Entities"
+  function Meter() {
+    this.offset = 10;
+    this.amp = 0;
+    this.percentage = 0;
+    this.direction = METER_SPEED;
+  }
 
-var respawnTarget = function() {
-  activeTarget = new Target();
-	entities.push(activeTarget);
-};
+  Meter.prototype.draw = function() {
+    var left = (canvas.width - METER_WIDTH) / 2;
+    var vmid = this.offset + METER_HEIGHT / 2;
+    var third = METER_WIDTH / 3;
+    var peak = METER_HEIGHT / 2;
+
+    ctx.fillStyle = 'black';
+    ctx.rect(left, this.offset, METER_WIDTH, METER_HEIGHT);
+    ctx.fill();
+
+    var cpUp = lerp(vmid, vmid + peak * this.amp, this.percentage);
+    var cpDown = lerp(vmid, vmid - peak * this.amp, this.percentage);
+
+    ctx.lineWidth = 2;
+    ctx.strokeStyle = 'red';
+    ctx.beginPath();
+    var pos = left;
+    ctx.moveTo(left, vmid);
+    ctx.quadraticCurveTo(pos+third/2, cpUp, pos+third, vmid);
+    pos += third;
+    ctx.quadraticCurveTo(pos+third/2, cpDown, pos+third, vmid);
+    pos += third;
+    ctx.quadraticCurveTo(pos+third/2, cpUp, pos+third, vmid);
+    ctx.stroke();
+
+    ctx.lineWidth = 3;
+    ctx.strokeStyle = 'white';
+    ctx.beginPath();
+    ctx.rect(left, this.offset, METER_WIDTH, METER_HEIGHT);
+    ctx.stroke();
+  };
+
+  Meter.prototype.onUpdate = function(dt) {
+    if(this.percentage > 1) {
+      this.direction = -METER_SPEED;
+    }
+    if(this.percentage < -1) {
+      this.direction = METER_SPEED;
+    }
+    this.percentage += this.direction * dt;
+    this.amp = 0.5 + lerp(0, 1, activeTarget.shimmerFactor);
+  };
+
+  function Star(x, y) {
+    this.x = x;
+    this.y = y;
+    this.alpha = 0;
+    this.randomize();
+  }
 
-var reset = function() {
-	points = 0;
-	entities = [];
-
-	for(var j = 0; j < canvas.height / DOT_DISTANCE + 4; ++j) {
-		for(var i = 0; i < LEVEL_SIZE / DOT_DISTANCE; ++i) {
-			var x = i * DOT_DISTANCE;
-			var y = (j-4) * DOT_DISTANCE;
-			if(j % 2 == 0 && i % 2 == 0) {
-				entities.push(new Dot(x, y));
-			}
-
-			if(j % 4 == 1) {
-				if(i % 4 == 0) {
-					entities.push(new Star(x, y));
-				}
-				else {
-					entities.push(new Dot(x, y));
-				}
-
-			}
-
-			if(j % 4 == 3) {
-				if(i % 4 == 2) {
-					entities.push(new Star(x, y));
-				}
-				else {
-					entities.push(new Dot(x, y));
-				}
-			}
-		}
-	}
-	respawnTarget();
-};
+  Star.prototype.randomize = function() {
+    this.type = Math.floor(Math.random()*starColors.length);
+    this.color = starColors[this.type];
+  };
+
+  Star.prototype.draw = function() {
+    ctx.fillStyle = this.color;
+    ctx.globalAlpha = this.alpha;
+    var adjPosition = translatePoints(this);
+    ctx.fillRect(adjPosition.x, adjPosition.y, DOT_SIZE, DOT_SIZE);
+
+    var top    = {x: adjPosition.x + DOT_SIZE/2,   y: adjPosition.y - DOT_SIZE/2  };
+    var bottom = {x: adjPosition.x + DOT_SIZE/2,   y: adjPosition.y + DOT_SIZE/2*3};
+    var left   = {x: adjPosition.x - DOT_SIZE/2,   y: adjPosition.y + DOT_SIZE/2  };
+    var right  = {x: adjPosition.x + DOT_SIZE/2*3, y: adjPosition.y + DOT_SIZE/2  };
+
+    drawLeafH(left, -LEAF_SIZE, LEAF_SIZE/2);
+    drawLeafH(right, LEAF_SIZE, LEAF_SIZE/2);
+    drawLeafV(top, -LEAF_SIZE, LEAF_SIZE/2);
+    drawLeafV(bottom, LEAF_SIZE, LEAF_SIZE/2);
+    ctx.globalAlpha = 1;
+  };
+
+  Star.prototype.onUpdate = function() {
+    if(gotPoint) {
+      this.randomize();
+    }
+    if(this.y > canvas.height + DOT_DISTANCE * 2) {
+      this.y -= canvas.height + DOT_DISTANCE * 4;
+    }
+    var newAlpha = 1.5 * activeTarget.spectrum[this.type];
+    var a = 0.001;
+    if(newAlpha > this.alpha) {
+      a = 0.9;
+    }
+    this.alpha = Math.min(1, activeTarget.shimmerFactor * (this.alpha * (1-a) + newAlpha * a));
+  };
+
+  function Dot(x, y) {
+    this.x = x;
+    this.y = y;
+  }
 
-reset();
+  Dot.prototype.draw = function() {
+    ctx.fillStyle = 'gray';
+    var adjPosition = translatePoints(this);
+    ctx.fillRect(adjPosition.x, adjPosition.y, DOT_SIZE, DOT_SIZE);
+  };
+
+  Dot.prototype.onUpdate = function() {
+    if(this.y > canvas.height + DOT_DISTANCE * 2) {
+      this.y -= canvas.height + DOT_DISTANCE * 4;
+    }
+  };
+
+  function Target(beat) {
+    this.beat = beat;
+    this.alpha = 0;
+    if(points < 4) {
+      this.alpha = ease(1-points/4);
+    }
+    this.rippleCounter = 0;
+    this.reset();
+  }
 
-// Update
-var update = function(dt) {
-  if(paused) return;
-	var repeat = false;
-	beatTime += dt;
-	while(beatTime > 4) {
-		repeat = true;
-		beatTime -= 4;
-	}
+  Target.prototype.reset = function() {
+    this.y = -20;
+    this.x = Math.random()*LEVEL_SIZE;
+  };
+
+  Target.prototype.color = function(p) {
+    return color(0, 255, 0, p*this.alpha);
+  };
+
+  Target.prototype.draw = function() {
+    var adjPosition = translatePoints(this);
+    drawCircle(adjPosition, RADIUS, 3, this.color(1));
+    drawCircle(adjPosition, 2, 3, this.color(1));
+
+    // ripples
+    var p = lerp(1, 0, ease(this.rippleCounter/RIPPLE_SIZE));
+    drawCircle(adjPosition, RADIUS+7+this.rippleCounter, 1, this.color(p));
+    drawCircle(adjPosition, RADIUS+4+this.rippleCounter, 1, this.color(p*0.8));
+    drawCircle(adjPosition, RADIUS+1+this.rippleCounter, 1, this.color(p*0.5));
+  };
+
+  Target.prototype.onUpdate = function(dt, t) {
+    this.rippleCounter += RIPPLE_SPEED * dt;
+    if(this.rippleCounter > RIPPLE_SIZE) {
+      this.rippleCounter = 0;
+    }
+
+    if(gotPoint) {
+      gotPoint = false;
+    }
+
+    if(colliding(player, this)) {
+      gotPoint = true;
+      this.beat.sound.setPan(0);
+      this.beat.sound.setVolume(100);
+      this.shouldDelete = true;
+      return;
+    }
+
+    if(this.y > canvas.height + 20) {
+      this.reset();
+    }
+
+    var xDist = distX(this, player);
+    var s = sign(xDist);
+    var xDistAbs = easeOutExpo(Math.abs(xDist), 0, 100, LEVEL_SIZE/2);
+
+    var yDist = this.y - player.y;
+    var angle = 0;
+    if(yDist != 0) {
+      angle = easeOutExpo(Math.abs(Math.atan2(Math.abs(yDist), xDist) - Math.PI/2), 0, 100, Math.PI);
+    }
+    else {
+      angle = s * 100;
+    }
+    this.beat.sound.setPan(Math.max(xDistAbs, angle) * s);
+
+    var rDist = xDist*xDist + yDist*yDist;
+    if(rDist >= 250000) {
+      rDist = 250000;
+    }
+    rDist = 250000 - rDist;
+    rDist /= 2500;
+    this.beat.sound.setVolume(rDist);
+
+    this.shimmerFactor = easeOutExpo(Math.abs(xDist), 1, -1, LEVEL_SIZE/2) * rDist / 100;
+    if(isNaN(this.shimmerFactor) || this.shimmerFactor < 0) {
+      this.shimmerFactor = 0;
+    }
+
+    this.spectrum = this.beat.sound.getSpectrum(t%this.beat.info.duration);
+  };
+
+  var player = {
+    x: 0,
+    y: canvas.height - 20,
+    dx: PLAYER_SPEED
+  };
+
+  var meter = new Meter();
+
+  var points;
+  var entities;
+  var activeTarget;
+
+  var reset = function() {
+    points = 0;
+    entities = [];
+
+    for(var j = 0; j < canvas.height / DOT_DISTANCE + 4; ++j) {
+      for(var i = 0; i < LEVEL_SIZE / DOT_DISTANCE; ++i) {
+        var x = i * DOT_DISTANCE;
+        var y = (j-4) * DOT_DISTANCE;
+        if(j % 2 == 0 && i % 2 == 0) {
+          entities.push(new Dot(x, y));
+        }
+
+        if(j % 4 == 1) {
+          if(i % 4 == 0) {
+            entities.push(new Star(x, y));
+          }
+          else {
+            entities.push(new Dot(x, y));
+          }
+
+        }
+
+        if(j % 4 == 3) {
+          if(i % 4 == 2) {
+            entities.push(new Star(x, y));
+          }
+          else {
+            entities.push(new Dot(x, y));
+          }
+        }
+      }
+    }
+  };
+
+  // Update
+  var update = function(delta) {
+    if(paused) return;
+    var dt = delta / 1000;
 
-	if(repeat) {
     currentBeats.map(function(beat) {
-      beat.play();
+      if(beat.repeat) {
+        beat.repeat = false;
+      }
+      else {
+        if(gameTime + delta > Math.ceil(gameTime / beat.info.duration) * beat.info.duration) {
+          beat.repeat = true;
+        }
+      }
     });
-	}
-
-	if(37 in keysDown) {
-		player.x -= player.dx * dt;
-	}
-	if(39 in keysDown) {
-		player.x += player.dx * dt;
-	}
-	player.x = (player.x + LEVEL_SIZE) % LEVEL_SIZE;
-
-	for(var i = entities.length - 1; i >= 0; --i) {
-		var entity = entities[i];
-		entity.y += STAGE_SPEED * dt;
-		entity.onUpdate(dt, beatTime);
-		if(entity.shouldDelete) {
-			entities.splice(i, 1);
-		}
-	}
-	meter.onUpdate(dt);
-
-	if(gotPoint) {
-		++points;
-		soundManager.play('get');
-		if(beats.length > 0) {
-      respawnTarget();
-			switchBeat(points % beats.length);
-		}
-	}
-};
 
-// Render
-var translatePoints = function(point) {
-	var x = point.x - player.x + canvas.width/2;
-	if(x < -ALLOWANCE) x += LEVEL_SIZE;
-	if(x > LEVEL_SIZE - ALLOWANCE) x -= LEVEL_SIZE;
-	return {x:x, y:point.y};
-};
-
-var render = function() {
-	ctx.fillStyle = "black";
-	ctx.fillRect(0, 0, canvas.width, canvas.height);
-	entities.map(function(elem) {
-		elem.draw();
-	});
-	drawPlayer(translatePoints(player));
-	meter.draw();
-	ctx.fillStyle = "white";
-	ctx.fillText(points+"", 5, 15);
-
-	if(debug) {
-    drawSpectrum();
-	}
-
-  if(paused) {
-    ctx.textAlign = 'center';
-    ctx.fillText("Paused (P to unpause)", canvas.width / 2, canvas.height / 2);
-    ctx.textAlign = 'start';
-  }
-};
 
+    currentBeats.map(function(beat) {
+      if(beat.repeat) beat.sound.play();
+    });
 
-var main = function() {
-	var now = Date.now();
-	var delta = (now - then);
-	delta /= 1000;
-	update(delta);
-	render();
-	then = now;
+    if(37 in keysDown) {
+      player.x -= player.dx * dt;
+    }
+    if(39 in keysDown) {
+      player.x += player.dx * dt;
+    }
+    player.x = (player.x + LEVEL_SIZE) % LEVEL_SIZE;
+
+    for(var i = entities.length - 1; i >= 0; --i) {
+      var entity = entities[i];
+      entity.y += STAGE_SPEED * dt;
+      entity.onUpdate(dt, gameTime);
+      if(entity.shouldDelete) {
+        entities.splice(i, 1);
+      }
+    }
+    meter.onUpdate(dt);
+
+    if(gotPoint) {
+      ++points;
+      soundManager.play('get');
+      if(beats.length > 0) {
+        switchBeat(points % beats.length);
+      }
+    }
+
+    gameTime += delta;
+  };
+
+  // Render
+  var translatePoints = function(point) {
+    var x = point.x - player.x + canvas.width/2;
+    if(x < -ALLOWANCE) x += LEVEL_SIZE;
+    if(x > LEVEL_SIZE - ALLOWANCE) x -= LEVEL_SIZE;
+    return {x:x, y:point.y};
+  };
+
+  var render = function() {
+    ctx.fillStyle = "black";
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    entities.map(function(elem) {
+      elem.draw();
+    });
+    drawPlayer(translatePoints(player));
+    meter.draw();
+    ctx.fillStyle = "white";
+    ctx.fillText(points+"", 5, 15);
+
+    if(debug) {
+      drawSpectrum();
+    }
+
+    if(paused) {
+      ctx.fillStyle = 'white';
+      ctx.textAlign = 'center';
+      ctx.fillText("Paused (P to unpause)", canvas.width / 2, canvas.height / 2);
+      ctx.textAlign = 'start';
+    }
+  };
+
+  var then;
+
+  var main = function() {
+    var now = Date.now();
+    var delta = (now - then);
+    update(delta);
+    render();
+    then = now;
+  };
+
+  var start = function() {
+    reset();
+    switchBeat(0);
+    then = Date.now();
+    setInterval(main, 10);
+  };
+
+  var pause = function() {
+    paused = !paused;
+    if(paused) {
+      currentBeats.map(function(beat) {beat.sound.pause()});
+    }
+    else {
+      currentBeats.map(function(beat) {beat.sound.resume()});
+    }
+  };
+
+  ctx.textAlign = 'center';
+  ctx.fillText("Loading...", canvas.width / 2, canvas.height / 2);
+  ctx.textAlign = 'start';
+
+  Q.all([soundPromise]).done(function() {
+    start();
+  });
 };
 
-var then;
+var canvas = document.getElementById('game');
 
-var start = function() {
-  then = Date.now();
-  setInterval(main, 10);
-};
+var beats = [
+  {url: 'sound/b1.mp3', duration: 4000},
+  {url: 'sound/b2.mp3', duration: 4000},
+  {url: 'sound/b3.mp3', duration: 4000},
+  {url: 'sound/b4.mp3', duration: 4000},
+  {url: 'sound/b5.mp3', duration: 4000}
+];
 
-var pause = function() {
-  paused = !paused;
-  if(paused) {
-    currentBeats.map(function(beat) {beat.pause()});
-  }
-  else {
-    currentBeats.map(function(beat) {beat.resume()});
+var soundDeferred = Q.defer();
+var pSoundManager = soundDeferred.promise;
+soundManager.setup({
+  url: 'swf/',
+  flashVersion: 9,
+  onready: function() {
+    soundDeferred.resolve(this);
   }
-};
+});
 
-ctx.textAlign = 'center';
-ctx.fillText("Loading...", canvas.width / 2, canvas.height / 2);
-ctx.textAlign = 'start';
+SoundSafari(canvas, beats, pSoundManager);
 
-Q.all([soundPromise]).done(function() {
-	switchBeat(0);
-	start();
-});