1 | function AudioSynthView() {
|
---|
2 |
|
---|
3 | var isMobile = !!navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i);
|
---|
4 | if(isMobile) { var evtListener = ['touchstart', 'touchend']; } else { var evtListener = ['mousedown', 'mouseup']; }
|
---|
5 |
|
---|
6 | var __audioSynth = new AudioSynth();
|
---|
7 | __audioSynth.setVolume(0.5);
|
---|
8 | var __octave = 4;
|
---|
9 |
|
---|
10 | // Change octave
|
---|
11 | var fnChangeOctave = function(x) {
|
---|
12 |
|
---|
13 | x |= 0;
|
---|
14 |
|
---|
15 | __octave += x;
|
---|
16 |
|
---|
17 | __octave = Math.min(5, Math.max(3, __octave));
|
---|
18 |
|
---|
19 | var octaveName = document.getElementsByName('OCTAVE_LABEL');
|
---|
20 | var i = octaveName.length;
|
---|
21 | while(i--) {
|
---|
22 | var val = parseInt(octaveName[i].getAttribute('value'));
|
---|
23 | octaveName[i].innerHTML = (val + __octave);
|
---|
24 | }
|
---|
25 |
|
---|
26 | document.getElementById('OCTAVE_LOWER').innerHTML = __octave-1;
|
---|
27 | document.getElementById('OCTAVE_UPPER').innerHTML = __octave+1;
|
---|
28 |
|
---|
29 | };
|
---|
30 |
|
---|
31 | // Key bindings, notes to keyCodes.
|
---|
32 | var keyboard = {
|
---|
33 |
|
---|
34 | /* 2 */
|
---|
35 | 50: 'C#,-1',
|
---|
36 |
|
---|
37 | /* 3 */
|
---|
38 | 51: 'D#,-1',
|
---|
39 |
|
---|
40 | /* 5 */
|
---|
41 | 53: 'F#,-1',
|
---|
42 |
|
---|
43 | /* 6 */
|
---|
44 | 54: 'G#,-1',
|
---|
45 |
|
---|
46 | /* 7 */
|
---|
47 | 55: 'A#,-1',
|
---|
48 |
|
---|
49 | /* 9 */
|
---|
50 | 57: 'C#,0',
|
---|
51 |
|
---|
52 | /* 0 */
|
---|
53 | 48: 'D#,0',
|
---|
54 |
|
---|
55 | /* + */
|
---|
56 | 187: 'F#,0',
|
---|
57 | 61: 'F#,0',
|
---|
58 |
|
---|
59 | /* Q */
|
---|
60 | 81: 'C,-1',
|
---|
61 |
|
---|
62 | /* W */
|
---|
63 | 87: 'D,-1',
|
---|
64 |
|
---|
65 | /* E */
|
---|
66 | 69: 'E,-1',
|
---|
67 |
|
---|
68 | /* R */
|
---|
69 | 82: 'F,-1',
|
---|
70 |
|
---|
71 | /* T */
|
---|
72 | 84: 'G,-1',
|
---|
73 |
|
---|
74 | /* Y */
|
---|
75 | 89: 'A,-1',
|
---|
76 |
|
---|
77 | /* U */
|
---|
78 | 85: 'B,-1',
|
---|
79 |
|
---|
80 | /* I */
|
---|
81 | 73: 'C,0',
|
---|
82 |
|
---|
83 | /* O */
|
---|
84 | 79: 'D,0',
|
---|
85 |
|
---|
86 | /* P */
|
---|
87 | 80: 'E,0',
|
---|
88 |
|
---|
89 | /* [ */
|
---|
90 | 219: 'F,0',
|
---|
91 |
|
---|
92 | /* ] */
|
---|
93 | 221: 'G,0',
|
---|
94 |
|
---|
95 | /* A */
|
---|
96 | 65: 'G#,0',
|
---|
97 |
|
---|
98 | /* S */
|
---|
99 | 83: 'A#,0',
|
---|
100 |
|
---|
101 | /* F */
|
---|
102 | 70: 'C#,1',
|
---|
103 |
|
---|
104 | /* G */
|
---|
105 | 71: 'D#,1',
|
---|
106 |
|
---|
107 | /* J */
|
---|
108 | 74: 'F#,1',
|
---|
109 |
|
---|
110 | /* K */
|
---|
111 | 75: 'G#,1',
|
---|
112 |
|
---|
113 | /* L */
|
---|
114 | 76: 'A#,1',
|
---|
115 |
|
---|
116 | /* Z */
|
---|
117 | 90: 'A,0',
|
---|
118 |
|
---|
119 | /* X */
|
---|
120 | 88: 'B,0',
|
---|
121 |
|
---|
122 | /* C */
|
---|
123 | 67: 'C,1',
|
---|
124 |
|
---|
125 | /* V */
|
---|
126 | 86: 'D,1',
|
---|
127 |
|
---|
128 | /* B */
|
---|
129 | 66: 'E,1',
|
---|
130 |
|
---|
131 | /* N */
|
---|
132 | 78: 'F,1',
|
---|
133 |
|
---|
134 | /* M */
|
---|
135 | 77: 'G,1',
|
---|
136 |
|
---|
137 | /* , */
|
---|
138 | 188: 'A,1',
|
---|
139 |
|
---|
140 | /* . */
|
---|
141 | 190: 'B,1'
|
---|
142 |
|
---|
143 | };
|
---|
144 |
|
---|
145 | var reverseLookupText = {};
|
---|
146 | var reverseLookup = {};
|
---|
147 |
|
---|
148 | // Create a reverse lookup table.
|
---|
149 | for(var i in keyboard) {
|
---|
150 |
|
---|
151 | var val;
|
---|
152 |
|
---|
153 | switch(i|0) {
|
---|
154 |
|
---|
155 | case 187:
|
---|
156 | val = 61;
|
---|
157 | break;
|
---|
158 |
|
---|
159 | case 219:
|
---|
160 | val = 91;
|
---|
161 | break;
|
---|
162 |
|
---|
163 | case 221:
|
---|
164 | val = 93;
|
---|
165 | break;
|
---|
166 |
|
---|
167 | case 188:
|
---|
168 | val = 44;
|
---|
169 | break;
|
---|
170 |
|
---|
171 | case 190:
|
---|
172 | val = 46;
|
---|
173 | break;
|
---|
174 |
|
---|
175 | default:
|
---|
176 | val = i;
|
---|
177 | break;
|
---|
178 |
|
---|
179 | }
|
---|
180 |
|
---|
181 | reverseLookupText[keyboard[i]] = val;
|
---|
182 | reverseLookup[keyboard[i]] = i;
|
---|
183 |
|
---|
184 | }
|
---|
185 |
|
---|
186 | // Keys you have pressed down.
|
---|
187 | var keysPressed = [];
|
---|
188 | var visualKeyboard = null;
|
---|
189 | var selectSound = null;
|
---|
190 |
|
---|
191 | var fnCreateKeyboard = function(keyboardElement) {
|
---|
192 | // Generate keyboard
|
---|
193 | // This is our main keyboard element! It's populated dynamically based on what you've set above.
|
---|
194 | visualKeyboard = document.getElementById('keyboard');
|
---|
195 | selectSound = document.getElementById('sound');
|
---|
196 |
|
---|
197 | var iKeys = 0;
|
---|
198 | var iWhite = 0;
|
---|
199 | var notes = __audioSynth._notes;
|
---|
200 |
|
---|
201 | for(var i=-1;i<=1;i++) {
|
---|
202 | for(var n in notes) {
|
---|
203 | if(n[2]!='b') {
|
---|
204 | var thisKey = document.createElement('div');
|
---|
205 | if(n.length>1) {
|
---|
206 | thisKey.className = 'black key';
|
---|
207 | thisKey.style.width = '30px';
|
---|
208 | thisKey.style.height = '120px';
|
---|
209 | thisKey.style.left = (40 * (iWhite - 1)) + 25 + 'px';
|
---|
210 | } else {
|
---|
211 | thisKey.className = 'white key';
|
---|
212 | thisKey.style.width = '40px';
|
---|
213 | thisKey.style.height = '200px';
|
---|
214 | thisKey.style.left = 40 * iWhite + 'px';
|
---|
215 | iWhite++;
|
---|
216 | }
|
---|
217 | var label = document.createElement('div');
|
---|
218 | label.className = 'label';
|
---|
219 | label.innerHTML = '<b>' + String.fromCharCode(reverseLookupText[n + ',' + i]) + '</b>' + '<br /><br />' + n.substr(0,1) +
|
---|
220 | '<span name="OCTAVE_LABEL" value="' + i + '">' + (__octave + parseInt(i)) + '</span>' + (n.substr(1,1)?n.substr(1,1):'');
|
---|
221 | thisKey.appendChild(label);
|
---|
222 | thisKey.setAttribute('ID', 'KEY_' + n + ',' + i);
|
---|
223 | thisKey.addEventListener(evtListener[0], (function(_temp) { return function() {
|
---|
224 | console.log("**** bespoke evtListener for mousedown, away to call fnPlayKeyboard()");
|
---|
225 | fnPlayKeyboard({keyCode:_temp}); } })(reverseLookup[n + ',' + i]));
|
---|
226 |
|
---|
227 | visualKeyboard[n + ',' + i] = thisKey;
|
---|
228 | visualKeyboard.appendChild(thisKey);
|
---|
229 | iKeys++;
|
---|
230 | }
|
---|
231 | }
|
---|
232 | }
|
---|
233 |
|
---|
234 | visualKeyboard.style.width = iWhite * 40 + 'px';
|
---|
235 |
|
---|
236 | window.addEventListener(evtListener[1], function() { n = keysPressed.length; while(n--) { fnRemoveKeyBinding({keyCode:keysPressed[n]}); } });
|
---|
237 |
|
---|
238 | };
|
---|
239 |
|
---|
240 | var midiMiddleC = 60;
|
---|
241 |
|
---|
242 | //var freqOctaveMap = {'C':261.63,'C#':277.18,'D':293.66,'D#':311.13,'E':329.63,'F':346.23,'F#':369.99,'G':392.00,'G#':415.30,'A':440.00,'A#':466.16,'B':493.88});
|
---|
243 |
|
---|
244 | var midiOctaveMap = {'C':0,'C#':1,'D':2,'D#':3,'E':4,'F':5,'F#':6,'G':7,'G#':8,'A':9,'A#':10,'B':11};
|
---|
245 |
|
---|
246 | var playedNotePitch;
|
---|
247 | var playedMidiPitch;
|
---|
248 |
|
---|
249 | var playedNoteStartTime;
|
---|
250 | var playedNoteStartCurrentTime;
|
---|
251 |
|
---|
252 | var playedDuration;
|
---|
253 |
|
---|
254 |
|
---|
255 | // Creates our audio player
|
---|
256 | var fnPlayNote = function(note, octave) {
|
---|
257 |
|
---|
258 | //console.log("**** Note started: " + Date.now()/1000.0)
|
---|
259 |
|
---|
260 | playedNoteStartTime = Date.now();
|
---|
261 | playedNoteStartCurrentTime = mediaPlayer.currentTime;
|
---|
262 |
|
---|
263 | playedNotePitch = "note = " + note + ", octave = " + octave;
|
---|
264 | playedMidiPitch = midiMiddleC + (12 * (octave-4)) + midiOctaveMap[note];
|
---|
265 |
|
---|
266 | inTheGroove();
|
---|
267 | MIDI.noteOn(0, playedMidiPitch, 50);
|
---|
268 |
|
---|
269 | //src = __audioSynth.generate(selectSound.value, note, octave, 2);
|
---|
270 | //container = new Audio(src);
|
---|
271 | //container.addEventListener('ended', function() {
|
---|
272 | // container = null;
|
---|
273 | //});
|
---|
274 | //container.addEventListener('loadeddata', function(e) { e.target.play(); });
|
---|
275 | //container.autoplay = false;
|
---|
276 | //container.setAttribute('type', 'audio/wav');
|
---|
277 | /*document.body.appendChild(container);*/
|
---|
278 | //container.load();
|
---|
279 | //return container;
|
---|
280 | return true;
|
---|
281 |
|
---|
282 | };
|
---|
283 |
|
---|
284 | // Detect keypresses, play notes.
|
---|
285 |
|
---|
286 | var fnPlayKeyboard = function(e) {
|
---|
287 | console.log("***### fnPlayKeyboard() e=" + e); //JSON.stringify(e));
|
---|
288 |
|
---|
289 | // check to see if any modal dialogs are open
|
---|
290 | var active_modal_dialogs = $('.ui-widget-overlay:visible');
|
---|
291 | if (active_modal_dialogs.length>0) {
|
---|
292 | return false;
|
---|
293 | }
|
---|
294 |
|
---|
295 |
|
---|
296 | if (e.ctrlKey) {
|
---|
297 | // avoid playing a note if some kind of control-key
|
---|
298 | // combination (such a web page reload) is pressed
|
---|
299 | return false;
|
---|
300 | }
|
---|
301 |
|
---|
302 | console.log("**** testing keyboard event key: '" + e.key + "' keycode = " + e.keyCode);
|
---|
303 |
|
---|
304 | if (e.keyCode == 32) { // <space-bar>
|
---|
305 | togglePlayPause();
|
---|
306 | //e.preventDefault();
|
---|
307 | return false;
|
---|
308 | }
|
---|
309 |
|
---|
310 | if (e.shiftKey) {
|
---|
311 | return fnDrumKeyboard(e);
|
---|
312 | }
|
---|
313 |
|
---|
314 |
|
---|
315 | var i = keysPressed.length;
|
---|
316 | while(i--) {
|
---|
317 | if(keysPressed[i]==e.keyCode) {
|
---|
318 | return false;
|
---|
319 | }
|
---|
320 | }
|
---|
321 | keysPressed.push(e.keyCode);
|
---|
322 |
|
---|
323 | switch(e.keyCode) {
|
---|
324 |
|
---|
325 | // left
|
---|
326 | case 37:
|
---|
327 | fnChangeOctave(-1);
|
---|
328 | break;
|
---|
329 |
|
---|
330 | // right
|
---|
331 | case 39:
|
---|
332 | fnChangeOctave(1);
|
---|
333 | break;
|
---|
334 |
|
---|
335 | // space
|
---|
336 | case 16:
|
---|
337 | break;
|
---|
338 | fnPlaySong([
|
---|
339 | ['E,0', 8],
|
---|
340 | ['D,0', 8],
|
---|
341 | ['C,0', 2],
|
---|
342 | ['C,0', 8],
|
---|
343 | ['D,0', 8],
|
---|
344 | ['C,0', 8],
|
---|
345 | ['E,0', 8],
|
---|
346 | ['D,0', 1],
|
---|
347 | ['C,0', 8],
|
---|
348 | ['D,0', 8],
|
---|
349 | ['E,0', 2],
|
---|
350 | ['A,0', 8],
|
---|
351 | ['G,0', 8],
|
---|
352 | ['E,0', 8],
|
---|
353 | ['C,0', 8],
|
---|
354 | ['D,0', 1],
|
---|
355 | ['A,0', 8],
|
---|
356 | ['B,0', 8],
|
---|
357 | ['C,1', 2],
|
---|
358 | ['B,0', 8],
|
---|
359 | ['C,1', 8],
|
---|
360 | ['D,1', 8],
|
---|
361 | ['C,1', 8],
|
---|
362 | ['A,0', 1],
|
---|
363 | ['G,0', 8],
|
---|
364 | ['A,0', 8],
|
---|
365 | ['B,0', 2],
|
---|
366 | ['C,1', 8],
|
---|
367 | ['B,0', 8],
|
---|
368 | ['A,0', 8],
|
---|
369 | ['G,0', 8],
|
---|
370 | ['A,0', 1]
|
---|
371 | ]);
|
---|
372 | break;
|
---|
373 |
|
---|
374 | }
|
---|
375 |
|
---|
376 | if(keyboard[e.keyCode]) {
|
---|
377 | if(visualKeyboard[keyboard[e.keyCode]]) {
|
---|
378 | visualKeyboard[keyboard[e.keyCode]].style.backgroundColor = '#ff0000';
|
---|
379 | visualKeyboard[keyboard[e.keyCode]].style.marginTop = '5px';
|
---|
380 | visualKeyboard[keyboard[e.keyCode]].style.boxShadow = 'none';
|
---|
381 | }
|
---|
382 | var arrPlayNote = keyboard[e.keyCode].split(',');
|
---|
383 | var note = arrPlayNote[0];
|
---|
384 | var octaveModifier = arrPlayNote[1]|0;
|
---|
385 | fnPlayNote(note, __octave + octaveModifier);
|
---|
386 | } else {
|
---|
387 | return false;
|
---|
388 | }
|
---|
389 |
|
---|
390 | }
|
---|
391 |
|
---|
392 | // Remove key bindings once note is done.
|
---|
393 |
|
---|
394 | function calcMidiNoteInfo() {
|
---|
395 | if (mediaStartPlayTime != null) {
|
---|
396 | var playedNoteEndTime = Date.now();
|
---|
397 | var relativePlayedNoteStartTime = playedNoteStartTime - mediaStartPlayTime;
|
---|
398 | var relativePlayedNoteEndTime = playedNoteEndTime - mediaStartPlayTime;
|
---|
399 | playedDuration = (relativePlayedNoteEndTime - relativePlayedNoteStartTime);
|
---|
400 |
|
---|
401 |
|
---|
402 | // console.log("**** " + playedNotePitch + " (midi pitch = " + playedMidiPitch+ ") startTime = ", relativePlayStartTime + ", duration = " + playedDuration);
|
---|
403 |
|
---|
404 | mediaPlayedNotes[String(mediaStartPlayTime)].push(
|
---|
405 | { 'midiPitch' : playedMidiPitch,
|
---|
406 | 'midiNoteOn' : relativePlayedNoteStartTime,
|
---|
407 | 'midiNoteOff' : relativePlayedNoteEndTime,
|
---|
408 | 'startCurrentTime' : playedNoteStartCurrentTime,
|
---|
409 | 'duration' : playedDuration,
|
---|
410 | 'startPercTime': playedNoteStartCurrentTime/mediaPlayer.duration,
|
---|
411 | 'humanReadble' : playedNotePitch }
|
---|
412 | );
|
---|
413 |
|
---|
414 | }
|
---|
415 | }
|
---|
416 |
|
---|
417 | var fnRemoveKeyBinding = function(e) {
|
---|
418 |
|
---|
419 | //console.log("**** key up at: " + Date.now())
|
---|
420 | // console.log("*** event e = " + JSON.stringify(e));
|
---|
421 |
|
---|
422 |
|
---|
423 | var i = keysPressed.length;
|
---|
424 | while(i--) {
|
---|
425 | if(keysPressed[i]==e.keyCode) {
|
---|
426 | if(visualKeyboard[keyboard[e.keyCode]]) {
|
---|
427 |
|
---|
428 | delayedMissingTheGroove(500);
|
---|
429 |
|
---|
430 | MIDI.noteOff(0, playedMidiPitch, 0);
|
---|
431 | if (mediaPlaybackMode == "record") {
|
---|
432 | calcMidiNoteInfo();
|
---|
433 | }
|
---|
434 |
|
---|
435 | visualKeyboard[keyboard[e.keyCode]].style.backgroundColor = '';
|
---|
436 | visualKeyboard[keyboard[e.keyCode]].style.marginTop = '';
|
---|
437 | visualKeyboard[keyboard[e.keyCode]].style.boxShadow = '';
|
---|
438 | }
|
---|
439 | keysPressed.splice(i, 1);
|
---|
440 | }
|
---|
441 | }
|
---|
442 |
|
---|
443 | }
|
---|
444 |
|
---|
445 | var fnPlaySong = function(arr) {
|
---|
446 |
|
---|
447 | if(arr.length>0) {
|
---|
448 |
|
---|
449 | var noteLen = 1000*(1/parseInt(arr[0][1]));
|
---|
450 | if(!(arr[0][0] instanceof Array)) {
|
---|
451 | arr[0][0] = [arr[0][0]];
|
---|
452 | }
|
---|
453 | var i = arr[0][0].length;
|
---|
454 | var keys = [];
|
---|
455 | while(i--) {
|
---|
456 | keys.unshift(reverseLookup[arr[0][0][i]]);
|
---|
457 | console.log("**** In fnPlaySong(), away to call fnPlayKeyboard()");
|
---|
458 | fnPlayKeyboard({keyCode:keys[0]});
|
---|
459 | }
|
---|
460 | arr.shift();
|
---|
461 | setTimeout(function(array, val){ return function() { var i = val.length; while(i--) { fnRemoveKeyBinding({keyCode:val[i]}); } fnPlaySong(array); } }(arr, keys), noteLen);
|
---|
462 |
|
---|
463 | }
|
---|
464 |
|
---|
465 | };
|
---|
466 |
|
---|
467 | var fnDrumKeyboard = function(e) {
|
---|
468 | //console.log("*** drum pressed: " + e.key);
|
---|
469 | if (e.key == "C") {
|
---|
470 | $('#drum1').trigger("mousedown");
|
---|
471 | }
|
---|
472 | else if (e.key == "V") {
|
---|
473 | $('#drum2').trigger("mousedown");
|
---|
474 | }
|
---|
475 | else if (e.key == "B") {
|
---|
476 | $('#drum3').trigger("mousedown");
|
---|
477 | }
|
---|
478 | else if (e.key == "N") {
|
---|
479 | $('#drum4').trigger("mousedown");
|
---|
480 | }
|
---|
481 | else if (e.key == "M") {
|
---|
482 | $('#drum5').trigger("mousedown");
|
---|
483 | }
|
---|
484 |
|
---|
485 | e.preventDefault();
|
---|
486 | return false;
|
---|
487 | }
|
---|
488 |
|
---|
489 |
|
---|
490 | // Set up global event listeners
|
---|
491 |
|
---|
492 | window.addEventListener('keydown', fnPlayKeyboard);
|
---|
493 | window.addEventListener('keyup', fnRemoveKeyBinding);
|
---|
494 | document.getElementById('-_OCTAVE').addEventListener('click', function() { fnChangeOctave(-1); });
|
---|
495 | document.getElementById('+_OCTAVE').addEventListener('click', function() { fnChangeOctave(1); });
|
---|
496 |
|
---|
497 | Object.defineProperty(this, 'draw', {
|
---|
498 | value: fnCreateKeyboard
|
---|
499 | });
|
---|
500 |
|
---|
501 | }
|
---|