// Script derived from Chrome Jam // Shim by Paul Irish // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); function Stage(id) { this.el = document.getElementById(id); this.position(); this.listeners(); this.hitZones = []; return this; } Stage.prototype.position = function() { var offset = this.offset(); this.positionTop = Math.floor(offset.left); this.positionLeft = Math.floor(offset.top); }; Stage.prototype.offset = function() { var _x, _y, el = this.el; if (typeof el.getBoundingClientRect !== "undefined") { return el.getBoundingClientRect(); } else { _x = 0; _y = 0; while(el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { _x += el.offsetLeft; _y += el.offsetTop; el = el.offsetParent; } return { top: _y - window.scrollY, left: _x - window.scrollX }; } }; Stage.prototype.listeners = function() { var _self = this; _self.dragging = false; _self.limit = false; window.addEventListener('resize', function() { _self.position(); }, false); window.addEventListener('scroll', function() { _self.position(); }, false); this.el.addEventListener('mousedown', function(e) { var x = e.clientX - _self.positionTop, y = e.clientY - _self.positionLeft; _self.hitZones.forEach(function(zone) { _self.checkPoint(x, y, zone); }); _self.dragging = true; _self.prev = [x, y]; }, false); document.addEventListener('mousemove', function(e) { var x, y; if (!_self.dragging || _self.limit) return; _self.limit = true; x = e.clientX - _self.positionTop, y = e.clientY - _self.positionLeft; _self.hitZones.forEach(function(zone) { _self.checkIntercept(_self.prev[0], _self.prev[1], x, y, zone); }); _self.prev = [x, y]; setInterval(function() { _self.limit = false; }, 50); }, false); document.addEventListener('mouseup', function(e) { var x, y; if (!_self.dragging) return; _self.dragging = false; x = e.clientX - _self.positionTop, y = e.clientY - _self.positionLeft; _self.hitZones.forEach(function(zone) { _self.checkIntercept(_self.prev[0], _self.prev[1], x, y, zone); }); }, false); }; Stage.prototype.check = function(x, y, zone) { if(!zone.el) return; if(zone.inside(x, y)){ zone.el.classList.add('hit'); this.el.classList.add('active'); }else{ zone.el.classList.remove('hit'); this.el.classList.remove('active'); } }; Stage.prototype.addRect = function(id) { var el = document.getElementById(id), rect = new Rect(el.offsetLeft, el.offsetTop, el.offsetWidth, el.offsetHeight ); rect.el = el; this.hitZones.push(rect); return rect; }; Stage.prototype.addString = function(rect, string) { rect.string = string; this.hitZones.push(rect); return rect; }; Stage.prototype.checkPoint = function(x, y, zone) { if(zone.inside(x, y)) { zone.string.strum(); } }; Stage.prototype.checkIntercept = function(x1, y1, x2, y2, zone) { if(zone.intercept(x1, y1, x2, y2)) { zone.string.strum(); } }; function Rect(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; return this; } Rect.prototype.inside = function(x,y) { return x >= this.x && y >= this.y && x <= this.x + this.width && y <= this.y + this.height; }; Rect.prototype.midLine = function() { if (this.middle) return this.middle; this.middle = [ {x: this.x, y: this.y + this.height / 2}, {x: this.x + this.width, y: this.y + this.height / 2} ] return this.middle; }; Rect.prototype.intercept = function(x1, y1, x2, y2) { var result = false, segment = this.midLine(), start = {x: x1, y: y1}, end = {x: x2, y: y2}; return this.intersectLine(segment[0], segment[1], start, end); }; Rect.prototype.intersectLine = function(a1, a2, b1, b2) { //-- http://www.kevlindev.com/gui/math/intersection/Intersection.js var result, ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); if (u_b != 0) { var ua = ua_t / u_b; var ub = ub_t / u_b; if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { result = true; } else { result = false; //--"No Intersection" } } else { if (ua_t == 0 || ub_t == 0) { result = false; //-- Coincident" } else { result = false; //-- Parallel } } return result; }; function GuitarString(rect) { this.x = rect.x; this.y = rect.y + rect.height / 2; this.width = rect.width; this._strumForce = 0; this.a = 0; } GuitarString.prototype.strum = function() { this._strumForce = 5; this._strumDuration = 100; MIDI.noteOn(0, this._midiPitch, 40); console.log("*** Setting Jam guitar strumForce = 5"); }; GuitarString.prototype.render = function(ctx, canvas) { ctx.strokeStyle = "#000000"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.bezierCurveTo( this.x, this.y + Math.sin(this.a) * this._strumForce, this.x + this.width, this.y + Math.sin(this.a) * this._strumForce, this.x + this.width, this.y); ctx.stroke(); this._strumForce *= 0.99; this.a += 0.5; if (this._strumDuration>0) { this._strumDuration--; } else if (this._strumDuration == 0) { this._strumDuration = -1; console.log("*** setting strumDuration = 0"); MIDI.noteOff(0, this._midiPitch, 0); } }; function StringInstrument(stageID, canvasID, stringNum){ this.strings = []; this.canvas = document.getElementById(canvasID); this.stage = new Stage(stageID); this.ctx = this.canvas.getContext('2d'); this.stringNum = stringNum; this.create(); this.render(); return this; } var jamStringLength = 680; // used to be 380 var jamStringThickness = 5; //var jamMidiStringPitches = [ 60, 64, 67, 72, 76, 79 ]; var jamMidiStringPitches = [ 48, 52, 55, 60, 64, 67 ]; StringInstrument.prototype.create = function() { for (var i = 0; i < this.stringNum; i++) { var srect = new Rect(10, 90 + i * (jamStringThickness * 1.6), jamStringLength, jamStringThickness); var s = new GuitarString(srect); s._midiPitch = jamMidiStringPitches[i]; this.stage.addString(srect, s); this.strings.push(s); } }; var lastTime = 0; StringInstrument.prototype.render = function() { var _self = this; requestAnimFrame(function(){ _self.render(); }); var currTime = new Date().getTime(); var dt = currTime - lastTime; if (dt>0) { lastTime = currTime; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); for (var i = 0; i < this.stringNum; i++) { this.strings[i].render(this.ctx); } } }; $(document).ready(function() { var guitar = new StringInstrument("jamStage", "jamStrings", 6); });