source: main/trunk/model-sites-dev/respooled/collect/popup-video-respooled/js/jam-guitar.js@ 29942

Last change on this file since 29942 was 29942, checked in by davidb, 9 years ago

Hitting drum image annimated with CSS3. Guitar now has a custom chord section and the strings on the jam-guitar tied to play the selected chord.

  • Property svn:executable set to *
File size: 12.8 KB
Line 
1"use strict";
2
3// Script derived from Chrome Jam
4
5// Shim by Paul Irish
6// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7window.requestAnimFrame = (function() {
8 return window.requestAnimationFrame ||
9 window.webkitRequestAnimationFrame ||
10 window.mozRequestAnimationFrame ||
11 window.oRequestAnimationFrame ||
12 window.msRequestAnimationFrame ||
13 function(callback) {
14 window.setTimeout(callback, 1000 / 60);
15 };
16})();
17
18function Stage(id) {
19 this.el = document.getElementById(id);
20
21 this.position();
22 this.listeners();
23 this.hitZones = [];
24 return this;
25}
26
27Stage.prototype.position = function() {
28 var offset = this.offset();
29 this.positionTop = Math.floor(offset.left);
30 this.positionLeft = Math.floor(offset.top);
31};
32
33Stage.prototype.offset = function() {
34 var _x, _y,
35 el = this.el;
36
37 if (typeof el.getBoundingClientRect !== "undefined") {
38 return el.getBoundingClientRect();
39 } else {
40 _x = 0;
41 _y = 0;
42 while(el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
43 _x += el.offsetLeft;
44 _y += el.offsetTop;
45 el = el.offsetParent;
46 }
47 return { top: _y - window.scrollY, left: _x - window.scrollX };
48 }
49};
50
51Stage.prototype.listeners = function() {
52 var _self = this;
53
54 _self.dragging = false;
55 _self.limit = false;
56
57 window.addEventListener('resize', function() {
58 _self.position();
59 }, false);
60
61 window.addEventListener('scroll', function() {
62 _self.position();
63 }, false);
64
65 this.el.addEventListener('mousedown', function(e) {
66 var x = e.clientX - _self.positionTop,
67 y = e.clientY - _self.positionLeft;
68
69 _self.hitZones.forEach(function(zone) {
70 _self.checkPoint(x, y, zone);
71 });
72
73 _self.dragging = true;
74 _self.prev = [x, y];
75 }, false);
76
77
78 document.addEventListener('mousemove', function(e) {
79 var x, y;
80
81 if (!_self.dragging || _self.limit) return;
82 _self.limit = true;
83
84 x = e.clientX - _self.positionTop,
85 y = e.clientY - _self.positionLeft;
86
87
88 _self.hitZones.forEach(function(zone) {
89 _self.checkIntercept(_self.prev[0],
90 _self.prev[1],
91 x,
92 y,
93 zone);
94 });
95
96 _self.prev = [x, y];
97
98 setInterval(function() {
99 _self.limit = false;
100 }, 50);
101 }, false);
102
103 document.addEventListener('mouseup', function(e) {
104 var x, y;
105
106 if (!_self.dragging) return;
107 _self.dragging = false;
108
109 x = e.clientX - _self.positionTop,
110 y = e.clientY - _self.positionLeft;
111
112 _self.hitZones.forEach(function(zone) {
113 _self.checkIntercept(_self.prev[0],
114 _self.prev[1],
115 x,
116 y,
117 zone);
118 });
119 }, false);
120};
121
122Stage.prototype.check = function(x, y, zone) {
123 if(!zone.el) return;
124
125 if(zone.inside(x, y)){
126 zone.el.classList.add('hit');
127 this.el.classList.add('active');
128 }else{
129 zone.el.classList.remove('hit');
130 this.el.classList.remove('active');
131 }
132};
133
134Stage.prototype.addRect = function(id) {
135 var el = document.getElementById(id),
136 rect = new Rect(el.offsetLeft,
137 el.offsetTop,
138 el.offsetWidth,
139 el.offsetHeight
140 );
141 rect.el = el;
142
143 this.hitZones.push(rect);
144 return rect;
145};
146
147Stage.prototype.addString = function(rect, string) {
148 rect.string = string;
149
150 this.hitZones.push(rect);
151 return rect;
152};
153
154Stage.prototype.checkPoint = function(x, y, zone) {
155 if(zone.inside(x, y)) {
156 zone.string.strum();
157 }
158};
159
160Stage.prototype.checkIntercept = function(x1, y1, x2, y2, zone) {
161 if(zone.intercept(x1, y1, x2, y2)) {
162 zone.string.strum();
163 }
164};
165
166
167function Rect(x, y, width, height) {
168 this.x = x;
169 this.y = y;
170 this.width = width;
171 this.height = height;
172
173 return this;
174}
175
176Rect.prototype.inside = function(x,y) {
177 return x >= this.x && y >= this.y
178 && x <= this.x + this.width
179 && y <= this.y + this.height;
180};
181
182Rect.prototype.midLine = function() {
183 if (this.middle) return this.middle;
184
185 this.middle = [
186 {x: this.x, y: this.y + this.height / 2},
187 {x: this.x + this.width, y: this.y + this.height / 2}
188 ]
189 return this.middle;
190};
191
192Rect.prototype.intercept = function(x1, y1, x2, y2) {
193 var result = false,
194 segment = this.midLine(),
195 start = {x: x1, y: y1},
196 end = {x: x2, y: y2};
197
198 return this.intersectLine(segment[0], segment[1], start, end);
199};
200
201Rect.prototype.intersectLine = function(a1, a2, b1, b2) {
202 //-- http://www.kevlindev.com/gui/math/intersection/Intersection.js
203 var result,
204 ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
205 ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
206 u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
207
208 if (u_b != 0) {
209 var ua = ua_t / u_b;
210 var ub = ub_t / u_b;
211
212 if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
213 result = true;
214 } else {
215 result = false; //--"No Intersection"
216 }
217 } else {
218 if (ua_t == 0 || ub_t == 0) {
219 result = false; //-- Coincident"
220 } else {
221 result = false; //-- Parallel
222 }
223 }
224
225 return result;
226};
227
228
229function GuitarString(rect) {
230 this.x = rect.x;
231 this.y = rect.y + rect.height / 2;
232 this.width = rect.width;
233 this._strumForce = 0;
234 this.a = 0;
235}
236
237GuitarString.prototype.strum = function() {
238 this._strumForce = 5;
239 this._strumDuration = 100;
240 if (this._midiPitch>=0) {
241 MIDI.noteOn(0, this._midiPitch, 40);
242 console.log("*** Setting Jam guitar strumForce = 5");
243 }
244};
245
246GuitarString.prototype.render = function(ctx, canvas) {
247 ctx.strokeStyle = "#000000";
248 ctx.lineWidth = 1;
249 ctx.beginPath();
250 ctx.moveTo(this.x, this.y);
251 ctx.bezierCurveTo(
252 this.x, this.y + Math.sin(this.a) * this._strumForce,
253 this.x + this.width, this.y + Math.sin(this.a) * this._strumForce,
254 this.x + this.width, this.y);
255 ctx.stroke();
256
257 this._strumForce *= 0.99;
258 this.a += 0.5;
259
260 if (this._strumDuration>0) {
261 this._strumDuration--;
262 }
263 else if (this._strumDuration == 0) {
264 this._strumDuration = -1;
265 if (this._midiPitch>=0) {
266 console.log("*** setting strumDuration = 0");
267 MIDI.noteOff(0, this._midiPitch, 0);
268 }
269 }
270};
271
272
273function StringInstrument(stageID, canvasID, stringNum){
274 this.strings = [];
275 this.canvas = document.getElementById(canvasID);
276 this.stage = new Stage(stageID);
277 this.ctx = this.canvas.getContext('2d');
278 this.stringNum = stringNum;
279
280 this.create();
281 this.render();
282
283 return this;
284}
285
286var jamStringLength = 680; // used to be 380
287var jamStringThickness = 5;
288
289//var jamMidiStringPitches = [ 60, 64, 67, 72, 76, 79 ];
290//var jamMidiStringPitches = [ 48, 52, 55, 60, 64, 67 ];
291// E A D G B E
292// 52, 57, 62, 67, 71, 76
293var jamMidiStringPitches = [ 52, 57, 62, 67, 71, 76 ];
294
295StringInstrument.prototype.create = function() {
296 for (var i = 0; i < this.stringNum; i++) {
297 var srect = new Rect(10, 90 + i * (jamStringThickness * 1.6), jamStringLength, jamStringThickness);
298 var s = new GuitarString(srect);
299 s._midiPitch = jamMidiStringPitches[i];
300 this.stage.addString(srect, s);
301 this.strings.push(s);
302 }
303};
304
305StringInstrument.prototype.setStringFret = function(spos,fbase, fpos) {
306
307 var s = this.strings[spos];
308 if (fpos>=0) {
309 s._midiPitch = jamMidiStringPitches[spos] + fbase + fpos;
310 }
311 else {
312 s._midiPitch = -1;
313 }
314
315};
316
317
318
319var lastTime = 0;
320
321StringInstrument.prototype.render = function() {
322 var _self = this;
323
324 requestAnimFrame(function(){
325 _self.render();
326 });
327
328
329 var currTime = new Date().getTime();
330 var dt = currTime - lastTime;
331
332 if (dt>0) {
333 lastTime = currTime;
334 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
335
336 for (var i = 0; i < this.stringNum; i++) {
337 this.strings[i].render(this.ctx);
338 }
339 }
340};
341
342var guitar = null;
343$(document).ready(function() {
344 console.log("**** creating guitar");
345 guitar = new StringInstrument("jamStage", "jamStrings", 6);
346});
347
348var jamLineupLen = 17;
349
350var jam_chords = { 'maj': [ 'C', 'C#' ,'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab',' A', 'A#', 'Bb', 'B'],
351 'min': [ 'Cm','C#m','Dbm','Dm','D#m','Ebm','Em','Fm','F#m','Gbm','Gm','G#m','Abm','Am','A#m','Bbm','Bm'],
352 'cus': [ 'Dsus4' ],
353 'cus-skip': {"1":true,"4":true,"8":true,"11":true,"14":true}
354 };
355
356var cgc_mode = null; // cgc => 'current guitar chord'
357var cgc_id = null;
358
359
360function whitespaceTidyUp(str)
361{
362 if (str) {
363 str = str.replace(/^\s+/,"").replace(/\s+$/,"");
364 }
365
366 return str;
367}
368
369
370function chordChange(bid) {
371
372 console.log("chordChange(" +bid + ")");
373 if (cgc_id != null) {
374 $('#offscreen-jtab-chord-'+cgc_id).css("background-color","#FFFFFF");
375 }
376 cgc_id = bid;
377 $('#offscreen-jtab-chord-'+cgc_id).css("background-color","rgba(46,82,164,0.50)");
378
379 var cgc_code = whitespaceTidyUp($('#offscreen-jtab-chord-'+cgc_id+'>div>svg>text>tspan').html());
380 console.log("*** clicked on guitar chord = " + cgc_code);
381
382 var cgc_data = jtab.Chords[cgc_code][0];
383 var cgc_base_fret = cgc_data[0];
384 //var cgc_string_frets = [];
385 for (var i=1; i<=6; i++) {
386 var cgc_string_fret_pos = cgc_data[i][0]; // pick out the fret position
387 //cgc_string_frets.push(cgc_string_fret_pos); // pick out the fret position
388 guitar.setStringFret(i-1,cgc_base_fret,cgc_string_fret_pos);
389 }
390
391 //console.log("*** strings = " + JSON.stringify(cgc_string_frets));
392}
393
394
395
396function scaleJTab(pos)
397{
398
399 // ... and scale the newly rendered element down
400 var $tab_svg = $('#offscreen-jtab-chord-'+pos+'>div>svg');
401 $tab_svg.attr("width", "58"); // go thinner than 'natural' width of 69 (for a height of 59)
402 $tab_svg.attr("height","59");
403 // True box is 0 0 138 118, but shaving off values so not so much white-space
404 $tab_svg[0].setAttribute("viewBox","20 0 90 118"); // W3C DOM way to avoid jquery lower-casing 'viewBox' into 'viewbox'
405 $tab_svg.parent().css("height","59px");
406
407}
408
409function setChordLineupMode(mode) {
410 cgc_mode = mode;
411
412 if (mode == "cus") {
413 $('#addCustomChord').show({effect: "fade", duration: 400});
414 }
415 else {
416 $('#addCustomChord').hide({effect: "fade", duration: 300});
417 }
418
419 var lineup = jam_chords[mode];
420 var lineup_len = lineup.length;
421
422 var lineup_skip = jam_chords[mode+"-skip"];
423
424
425 for (var i=0; i<jamLineupLen; i++) {
426
427 var active_div_id = '#offscreen-jtab-chord-'+i;
428
429 if ((i<lineup_len) &&
430 (!lineup_skip || (lineup_skip && !lineup_skip[""+i]))) {
431 console.log("*** lineup_skip = " + lineup_skip);
432 if (lineup_skip) {
433 console.log("*** lineup_skip lookup = " + lineup_skip[""+i]);
434 }
435 jtab.render($(active_div_id),lineup[i]);
436
437 scaleJTab(i);
438 }
439 else {
440 // make empty
441 jtab.render($(active_div_id).empty());
442 }
443
444 }
445
446 if (cgc_id != null) {
447 chordChange(cgc_id);
448 }
449}
450
451
452
453function addCustomChord() {
454
455
456 var custom_lineup = jam_chords['cus'];
457 var custom_lineup_len = custom_lineup.length;
458
459 var custom_lineup_skip = jam_chords['cus-skip'];
460
461
462 var chord_text = whitespaceTidyUp($('#addCustomChordText').val());
463
464 if (chord_text) {
465 // check the chord exists ...
466 if (jtab.Chords[chord_text]) {
467 // check there is space to add the chord ...
468
469 var custom_pos = null;
470 if (custom_lineup_len < jamLineupLen) {
471 // append (determin append pos) ...
472
473 custom_pos = custom_lineup_len;
474
475 // ... but need to increment one higher, if in a skip position
476 if (custom_lineup_skip["" + custom_pos]) {
477 custom_pos++;
478 }
479
480 custom_lineup[custom_pos] = chord_text;
481
482 }
483 else {
484 //replace the one that is currently highlighted
485 custom_pos = cgc_id;
486 custom_lineup[custom_pos] = chord_text;
487 }
488
489 jtab.render($('#offscreen-jtab-chord-'+custom_pos),chord_text);
490 scaleJTab(custom_pos);
491
492 // update local storage under this doc-id
493 }
494
495
496 }
497
498
499}
Note: See TracBrowser for help on using the repository browser.