· 6 years ago · Mar 28, 2020, 04:56 PM
1// 钢琴
2
3$(function() {
4
5
6 var test_mode = (window.location.hash && window.location.hash.match(/^(?:#.+)*#test(?:#.+)*$/i));
7
8
9 var gSeeOwnCursor = (window.location.hash && window.location.hash.match(/^(?:#.+)*#seeowncursor(?:#.+)*$/i));
10
11
12 var gMidiOutTest = (window.location.hash && window.location.hash.match(/^(?:#.+)*#midiout(?:#.+)*$/i)); // todo this is no longer needed
13
14
15 if (!Array.prototype.indexOf) {
16 Array.prototype.indexOf = function(elt /*, from*/ ) {
17 var len = this.length >>> 0;
18 var from = Number(arguments[1]) || 0;
19 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
20 if (from < 0) from += len;
21 for (; from < len; from++) {
22 if (from in this && this[from] === elt) return from;
23 }
24 return -1;
25 };
26 }
27
28
29 window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(cb) {
30 setTimeout(cb, 1000 / 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 var DEFAULT_VELOCITY = 0.5;
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 var TIMING_TARGET = 1000;
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 // Utility
137
138 ////////////////////////////////////////////////////////////////
139
140
141
142 var Rect = function(x, y, w, h) {
143 this.x = x;
144 this.y = y;
145 this.w = w;
146 this.h = h;
147 this.x2 = x + w;
148 this.y2 = y + h;
149 };
150 Rect.prototype.contains = function(x, y) {
151 return (x >= this.x && x <= this.x2 && y >= this.y && y <= this.y2);
152 };
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 // performing translation
170
171 ////////////////////////////////////////////////////////////////
172
173
174 var Translation = (function() {
175 var strings = {
176 "people are playing": {
177 "pt": "pessoas estão jogando",
178 "es": "personas están jugando",
179 "ru": "человек играет",
180 "fr": "personnes jouent",
181 "ja": "人が遊んでいる",
182 "de": "Leute spielen",
183 "zh": "人在玩",
184 "nl": "mensen spelen",
185 "pl": "osób grają",
186 "hu": "ember játszik"
187 },
188 "New Room...": {
189 "pt": "Nova Sala ...",
190 "es": "Nueva sala de...",
191 "ru": "Новый номер...",
192 "ja": "新しい部屋",
193 "zh": "新房间",
194 "nl": "nieuwe Kamer",
195 "hu": "új szoba"
196 },
197 "room name": {
198 "pt": "nome da sala",
199 "es": "sala de nombre",
200 "ru": "название комнаты",
201 "fr": "nom de la chambre",
202 "ja": "ルーム名",
203 "de": "Raumnamen",
204 "zh": "房间名称",
205 "nl": "kamernaam",
206 "pl": "nazwa pokój",
207 "hu": "szoba neve"
208 },
209 "Visible (open to everyone)": {
210 "pt": "Visível (aberto a todos)",
211 "es": "Visible (abierto a todo el mundo)",
212 "ru": "Visible (открытый для всех)",
213 "fr": "Visible (ouvert à tous)",
214 "ja": "目に見える(誰にでも開いている)",
215 "de": "Sichtbar (offen für alle)",
216 "zh": "可见(向所有人开放)",
217 "nl": "Zichtbaar (open voor iedereen)",
218 "pl": "Widoczne (otwarte dla wszystkich)",
219 "hu": "Látható (nyitott mindenki számára)"
220 },
221 "Enable Chat": {
222 "pt": "Ativar bate-papo",
223 "es": "Habilitar chat",
224 "ru": "Включить чат",
225 "fr": "Activer discuter",
226 "ja": "チャットを有効にする",
227 "de": "aktivieren Sie chatten",
228 "zh": "启用聊天",
229 "nl": "Chat inschakelen",
230 "pl": "Włącz czat",
231 "hu": "a csevegést"
232 },
233 "Play Alone": {
234 "pt": "Jogar Sozinho",
235 "es": "Jugar Solo",
236 "ru": "Играть в одиночку",
237 "fr": "Jouez Seul",
238 "ja": "一人でプレイ",
239 "de": "Alleine Spielen",
240 "zh": "独自玩耍",
241 "nl": "Speel Alleen",
242 "pl": "Zagraj sam",
243 "hu": "Játssz egyedül"
244 } // todo: it, tr, th, sv, ar, fi, nb, da, sv, he, cs, ko, ro, vi, id, nb, el, sk, bg, lt, sl, hr
245 // todo: Connecting, Offline mode, input placeholder, Notifications
246
247 };
248
249
250 var setLanguage = function(lang) {
251 language = lang
252 };
253
254
255 var getLanguage = function() {
256 if (window.navigator && navigator.language && navigator.language.length >= 2) {
257 return navigator.language.substr(0, 2).toLowerCase();
258 } else {
259 return "en";
260 }
261 };
262
263
264 var get = function(text, lang) {
265 if (typeof lang === "undefined") lang = language;
266 var row = strings[text];
267 if (row == undefined) return text;
268 var string = row[lang];
269 if (string == undefined) return text;
270 return string;
271 };
272
273
274 var perform = function(lang) {
275 if (typeof lang === "undefined") lang = language;
276 $(".translate").each(function(i, ele) {
277 var th = $(this);
278 if (ele.tagName && ele.tagName.toLowerCase() == "input") {
279 if (typeof ele.placeholder != "undefined") {
280 th.attr("placeholder", get(th.attr("placeholder"), lang))
281 }
282 } else {
283 th.text(get(th.text(), lang));
284 }
285 });
286 };
287
288
289 var language = getLanguage();
290
291
292 return {
293 setLanguage: setLanguage,
294 getLanguage: getLanguage,
295 get: get,
296 perform: perform
297 };
298 })();
299
300
301 Translation.perform();
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 // AudioEngine classes
318
319 ////////////////////////////////////////////////////////////////
320
321
322 var AudioEngine = function() { };
323
324
325 AudioEngine.prototype.init = function(cb) {
326 this.volume = 0.6;
327 this.sounds = {};
328 this.paused = true;
329 return this;
330 };
331
332
333 AudioEngine.prototype.load = function(id, url, cb) { };
334
335
336 AudioEngine.prototype.play = function() { };
337
338
339 AudioEngine.prototype.stop = function() { };
340
341
342 AudioEngine.prototype.setVolume = function(vol) {
343 this.volume = vol;
344 };
345 AudioEngine.prototype.resume = function() {
346 this.paused = false;
347 };
348
349
350
351 AudioEngineWeb = function() {
352 this.threshold = 1000;
353 this.worker = new Worker("/workerTimer.js");
354 var self = this;
355 this.worker.onmessage = function(event) {
356 if (event.data.args)
357 if (event.data.args.action == 0) {
358 self.actualPlay(event.data.args.id, event.data.args.vol, event.data.args.time, event.data.args.part_id);
359 }
360 else {
361 self.actualStop(event.data.args.id, event.data.args.time, event.data.args.part_id);
362 }
363 }
364 };
365
366
367 AudioEngineWeb.prototype = new AudioEngine();
368
369
370 AudioEngineWeb.prototype.init = function(cb) {
371 AudioEngine.prototype.init.call(this);
372
373
374 this.context = new AudioContext();
375
376
377 this.masterGain = this.context.createGain();
378 this.masterGain.connect(this.context.destination);
379 this.masterGain.gain.value = this.volume;
380
381
382 this.limiterNode = this.context.createDynamicsCompressor();
383 this.limiterNode.threshold.value = -10;
384 this.limiterNode.knee.value = 0;
385 this.limiterNode.ratio.value = 20;
386 this.limiterNode.attack.value = 0;
387 this.limiterNode.release.value = 0.1;
388 this.limiterNode.connect(this.masterGain);
389
390 // for synth mix
391
392 this.pianoGain = this.context.createGain();
393 this.pianoGain.gain.value = 0.5;
394 this.pianoGain.connect(this.limiterNode);
395 this.synthGain = this.context.createGain();
396 this.synthGain.gain.value = 0.5;
397 this.synthGain.connect(this.limiterNode);
398
399
400 this.playings = {};
401 if (cb) setTimeout(cb, 0);
402 return this;
403 };
404
405
406 AudioEngineWeb.prototype.load = function(id, url, cb) {
407 var audio = this;
408 var req = new XMLHttpRequest();
409 req.open("GET", url);
410 req.responseType = "arraybuffer";
411 req.addEventListener("readystatechange", function(evt) {
412 if (req.readyState !== 4) return;
413 try {
414 audio.context.decodeAudioData(req.response, function(buffer) {
415 audio.sounds[id] = buffer;
416 if (cb) cb();
417 });
418 } catch (e) {
419 /*throw new Error(e.message
420 + " / id: " + id
421 + " / url: " + url
422 + " / status: " + req.status
423 + " / ArrayBuffer: " + (req.response instanceof ArrayBuffer)
424 + " / byteLength: " + (req.response && req.response.byteLength ? req.response.byteLength : "undefined"));*/
425
426 new Notification({
427 id: "audio-download-error",
428 title: "Problem",
429 text: "For some reason, an audio download failed with a status of " + req.status + ". ",
430 target: "#piano",
431 duration: 10000
432 });
433 }
434 });
435 req.send();
436 };
437
438
439 AudioEngineWeb.prototype.actualPlay = function(id, vol, time, part_id) { //the old play(), but with time insted of delay_ms.
440
441 if (this.paused) return;
442 if (!this.sounds.hasOwnProperty(id)) return;
443 var source = this.context.createBufferSource();
444 source.buffer = this.sounds[id];
445 var gain = this.context.createGain();
446 gain.gain.value = vol;
447 source.connect(gain);
448 gain.connect(this.pianoGain);
449 source.start(time); // Patch from ste-art remedies stuttering under heavy load
450
451 if (this.playings[id]) {
452 var playing = this.playings[id];
453 playing.gain.gain.setValueAtTime(playing.gain.gain.value, time);
454 playing.gain.gain.linearRampToValueAtTime(0.0, time + 0.2);
455 playing.source.stop(time + 0.21);
456 if (enableSynth && playing.voice) {
457 playing.voice.stop(time);
458 }
459 }
460 this.playings[id] = {
461 "source": source,
462 "gain": gain,
463 "part_id": part_id
464 };
465
466
467 if (enableSynth) {
468 this.playings[id].voice = new synthVoice(id, time);
469 }
470 }
471 AudioEngineWeb.prototype.play = function(id, vol, delay_ms, part_id) {
472 if (!this.sounds.hasOwnProperty(id)) return;
473 var time = this.context.currentTime + (delay_ms / 1000); //calculate time on note receive.
474
475 var delay = delay_ms - this.threshold;
476 if (delay <= 0) this.actualPlay(id, vol, time, part_id);
477 else {
478 this.worker.postMessage({
479 delay: delay,
480 args: {
481 action: 0 /*play*/ ,
482 id: id,
483 vol: vol,
484 time: time,
485 part_id: part_id
486 }
487 }); // but start scheduling right before play.
488
489 }
490 }
491 AudioEngineWeb.prototype.actualStop = function(id, time, part_id) {
492 if (this.playings.hasOwnProperty(id) && this.playings[id] && this.playings[id].part_id === part_id) {
493 var gain = this.playings[id].gain.gain;
494 gain.setValueAtTime(gain.value, time);
495 gain.linearRampToValueAtTime(gain.value * 0.1, time + 0.16);
496 gain.linearRampToValueAtTime(0.0, time + 0.4);
497 this.playings[id].source.stop(time + 0.41);
498
499
500 if (this.playings[id].voice) {
501 this.playings[id].voice.stop(time);
502 }
503
504
505 this.playings[id] = null;
506 }
507 };
508
509
510 AudioEngineWeb.prototype.stop = function(id, delay_ms, part_id) {
511 var time = this.context.currentTime + (delay_ms / 1000);
512 var delay = delay_ms - this.threshold;
513 if (delay <= 0) this.actualStop(id, time, part_id);
514 else {
515 this.worker.postMessage({
516 delay: delay,
517 args: {
518 action: 1 /*stop*/ ,
519 id: id,
520 time: time,
521 part_id: part_id
522 }
523 });
524 }
525 };
526
527
528 AudioEngineWeb.prototype.setVolume = function(vol) {
529 AudioEngine.prototype.setVolume.call(this, vol);
530 this.masterGain.gain.value = this.volume;
531 };
532 AudioEngineWeb.prototype.resume = function() {
533 this.paused = false;
534 this.context.resume();
535 };
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562 // Renderer classes
563
564 ////////////////////////////////////////////////////////////////
565
566
567 var Renderer = function() { };
568
569
570 Renderer.prototype.init = function(piano) {
571 this.piano = piano;
572 this.resize();
573 return this;
574 };
575
576
577 Renderer.prototype.resize = function(width, height) {
578 if (typeof width == "undefined") width = $(this.piano.rootElement).width();
579 if (typeof height == "undefined") height = Math.floor(width * 0.2);
580 $(this.piano.rootElement).css({
581 "height": height + "px",
582 marginTop: Math.floor($(window).height() / 2 - height / 2) + "px"
583 });
584 this.width = width * window.devicePixelRatio;
585 this.height = height * window.devicePixelRatio;
586 };
587
588
589 Renderer.prototype.visualize = function(key, color) { };
590
591
592
593
594
595 var CanvasRenderer = function() {
596 Renderer.call(this);
597 };
598
599
600 CanvasRenderer.prototype = new Renderer();
601
602
603 CanvasRenderer.prototype.init = function(piano) {
604 this.canvas = document.createElement("canvas");
605 this.ctx = this.canvas.getContext("2d");
606 piano.rootElement.appendChild(this.canvas);
607
608
609 Renderer.prototype.init.call(this, piano); // calls resize()
610
611 // create render loop
612
613 var self = this;
614 var render = function() {
615 self.redraw();
616 requestAnimationFrame(render);
617 };
618 requestAnimationFrame(render);
619
620 // add event listeners
621
622 var mouse_down = false;
623 var last_key = null;
624 $(piano.rootElement).mousedown(function(event) {
625 mouse_down = true; //event.stopPropagation();
626
627 event.preventDefault();
628
629
630 var pos = CanvasRenderer.translateMouseEvent(event);
631 var hit = self.getHit(pos.x, pos.y);
632 if (hit) {
633 press(hit.key.note, hit.v);
634 last_key = hit.key;
635 }
636 });
637 piano.rootElement.addEventListener("touchstart", function(event) {
638 mouse_down = true; //event.stopPropagation();
639
640 event.preventDefault();
641 for (var i in event.changedTouches) {
642 var pos = CanvasRenderer.translateMouseEvent(event.changedTouches[i]);
643 var hit = self.getHit(pos.x, pos.y);
644 if (hit) {
645 press(hit.key.note, hit.v);
646 last_key = hit.key;
647 }
648 }
649 }, false);
650 $(window).mouseup(function(event) {
651 if (last_key) {
652 release(last_key.note);
653 }
654 mouse_down = false;
655 last_key = null;
656 });
657 /*$(piano.rootElement).mousemove(function(event) {
658 if(!mouse_down) return;
659 var pos = CanvasRenderer.translateMouseEvent(event);
660 var hit = self.getHit(pos.x, pos.y);
661 if(hit && hit.key != last_key) {
662 press(hit.key.note, hit.v);
663 last_key = hit.key;
664 }
665 });*/
666
667
668 return this;
669 };
670
671
672 CanvasRenderer.prototype.resize = function(width, height) {
673 Renderer.prototype.resize.call(this, width, height);
674 if (this.width < 52 * 2) this.width = 52 * 2;
675 if (this.height < this.width * 0.2) this.height = Math.floor(this.width * 0.2);
676 this.canvas.width = this.width;
677 this.canvas.height = this.height;
678 this.canvas.style.width = this.width / window.devicePixelRatio + "px";
679 this.canvas.style.height = this.height / window.devicePixelRatio + "px"; // calculate key sizes
680
681 this.whiteKeyWidth = Math.floor(this.width / 52);
682 this.whiteKeyHeight = Math.floor(this.height * 0.9);
683 this.blackKeyWidth = Math.floor(this.whiteKeyWidth * 0.75);
684 this.blackKeyHeight = Math.floor(this.height * 0.5);
685
686
687 this.blackKeyOffset = Math.floor(this.whiteKeyWidth - (this.blackKeyWidth / 2));
688 this.keyMovement = Math.floor(this.whiteKeyHeight * 0.015);
689
690
691 this.whiteBlipWidth = Math.floor(this.whiteKeyWidth * 0.7);
692 this.whiteBlipHeight = Math.floor(this.whiteBlipWidth * 0.8);
693 this.whiteBlipX = Math.floor((this.whiteKeyWidth - this.whiteBlipWidth) / 2);
694 this.whiteBlipY = Math.floor(this.whiteKeyHeight - this.whiteBlipHeight * 1.2);
695 this.blackBlipWidth = Math.floor(this.blackKeyWidth * 0.7);
696 this.blackBlipHeight = Math.floor(this.blackBlipWidth * 0.8);
697 this.blackBlipY = Math.floor(this.blackKeyHeight - this.blackBlipHeight * 1.2);
698 this.blackBlipX = Math.floor((this.blackKeyWidth - this.blackBlipWidth) / 2); // prerender white key
699
700 this.whiteKeyRender = document.createElement("canvas");
701 this.whiteKeyRender.width = this.whiteKeyWidth;
702 this.whiteKeyRender.height = this.height + 10;
703 var ctx = this.whiteKeyRender.getContext("2d");
704 if (ctx.createLinearGradient) {
705 var gradient = ctx.createLinearGradient(0, 0, 0, this.whiteKeyHeight);
706 gradient.addColorStop(0, "#eee");
707 gradient.addColorStop(0.75, "#fff");
708 gradient.addColorStop(1, "#dad4d4");
709 ctx.fillStyle = gradient;
710 } else {
711 ctx.fillStyle = "#fff";
712 }
713 ctx.strokeStyle = "#000";
714 ctx.lineJoin = "round";
715 ctx.lineCap = "round";
716 ctx.lineWidth = 10;
717 ctx.strokeRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth);
718 ctx.lineWidth = 4;
719 ctx.fillRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth); // prerender black key
720
721 this.blackKeyRender = document.createElement("canvas");
722 this.blackKeyRender.width = this.blackKeyWidth + 10;
723 this.blackKeyRender.height = this.blackKeyHeight + 10;
724 var ctx = this.blackKeyRender.getContext("2d");
725 if (ctx.createLinearGradient) {
726 var gradient = ctx.createLinearGradient(0, 0, 0, this.blackKeyHeight);
727 gradient.addColorStop(0, "#000");
728 gradient.addColorStop(1, "#444");
729 ctx.fillStyle = gradient;
730 } else {
731 ctx.fillStyle = "#000";
732 }
733 ctx.strokeStyle = "#222";
734 ctx.lineJoin = "round";
735 ctx.lineCap = "round";
736 ctx.lineWidth = 8;
737 ctx.strokeRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth);
738 ctx.lineWidth = 4;
739 ctx.fillRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth);
740
741 // prerender shadows
742
743 this.shadowRender = [];
744 var y = -this.canvas.height * 2;
745 for (var j = 0; j < 2; j++) {
746 var canvas = document.createElement("canvas");
747 this.shadowRender[j] = canvas;
748 canvas.width = this.canvas.width;
749 canvas.height = this.canvas.height;
750 var ctx = canvas.getContext("2d");
751 var sharp = j ? true : false;
752 ctx.lineJoin = "round";
753 ctx.lineCap = "round";
754 ctx.lineWidth = 1;
755 ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
756 ctx.shadowBlur = this.keyMovement * 3;
757 ctx.shadowOffsetY = -y + this.keyMovement;
758 if (sharp) {
759 ctx.shadowOffsetX = this.keyMovement;
760 } else {
761 ctx.shadowOffsetX = 0;
762 ctx.shadowOffsetY = -y + this.keyMovement;
763 }
764 for (var i in this.piano.keys) {
765 if (!this.piano.keys.hasOwnProperty(i)) continue;
766 var key = this.piano.keys[i];
767 if (key.sharp != sharp) continue;
768
769
770 if (key.sharp) {
771 ctx.fillRect(this.blackKeyOffset + this.whiteKeyWidth * key.spatial + ctx.lineWidth / 2, y + ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth);
772 } else {
773 ctx.fillRect(this.whiteKeyWidth * key.spatial + ctx.lineWidth / 2, y + ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth);
774 }
775 }
776 }
777
778 // update key rects
779
780 for (var i in this.piano.keys) {
781 if (!this.piano.keys.hasOwnProperty(i)) continue;
782 var key = this.piano.keys[i];
783 if (key.sharp) {
784 key.rect = new Rect(this.blackKeyOffset + this.whiteKeyWidth * key.spatial, 0, this.blackKeyWidth, this.blackKeyHeight);
785 } else {
786 key.rect = new Rect(this.whiteKeyWidth * key.spatial, 0, this.whiteKeyWidth, this.whiteKeyHeight);
787 }
788 }
789 };
790
791
792 CanvasRenderer.prototype.visualize = function(key, color) {
793 key.timePlayed = Date.now();
794 key.blips.push({
795 "time": key.timePlayed,
796 "color": color
797 });
798 };
799
800
801 CanvasRenderer.prototype.redraw = function() {
802 var now = Date.now();
803 var timeLoadedEnd = now - 1000;
804 var timePlayedEnd = now - 100;
805 var timeBlipEnd = now - 1000;
806
807
808 this.ctx.save();
809 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // draw all keys
810
811 for (var j = 0; j < 2; j++) {
812 this.ctx.globalAlpha = 1.0;
813 this.ctx.drawImage(this.shadowRender[j], 0, 0);
814 var sharp = j ? true : false;
815 for (var i in this.piano.keys) {
816 if (!this.piano.keys.hasOwnProperty(i)) continue;
817 var key = this.piano.keys[i];
818 if (key.sharp != sharp) continue;
819
820
821 if (!key.loaded) {
822 this.ctx.globalAlpha = 0.2;
823 } else if (key.timeLoaded > timeLoadedEnd) {
824 this.ctx.globalAlpha = ((now - key.timeLoaded) / 1000) * 0.8 + 0.2;
825 } else {
826 this.ctx.globalAlpha = 1.0;
827 }
828 var y = 0;
829 if (key.timePlayed > timePlayedEnd) {
830 y = Math.floor(this.keyMovement - (((now - key.timePlayed) / 100) * this.keyMovement));
831 }
832 var x = Math.floor(key.sharp ? this.blackKeyOffset + this.whiteKeyWidth * key.spatial : this.whiteKeyWidth * key.spatial);
833 var image = key.sharp ? this.blackKeyRender : this.whiteKeyRender;
834 this.ctx.drawImage(image, x, y);
835
836 // render blips
837
838 if (key.blips.length) {
839 var alpha = this.ctx.globalAlpha;
840 var w, h;
841 if (key.sharp) {
842 x += this.blackBlipX;
843 y = this.blackBlipY;
844 w = this.blackBlipWidth;
845 h = this.blackBlipHeight;
846 } else {
847 x += this.whiteBlipX;
848 y = this.whiteBlipY;
849 w = this.whiteBlipWidth;
850 h = this.whiteBlipHeight;
851 }
852 for (var b = 0; b < key.blips.length; b++) {
853 var blip = key.blips[b];
854 if (blip.time > timeBlipEnd) {
855 this.ctx.fillStyle = blip.color;
856 this.ctx.globalAlpha = alpha - ((now - blip.time) / 1000);
857 this.ctx.fillRect(x, y, w, h);
858 } else {
859 key.blips.splice(b, 1);
860 --b;
861 }
862 y -= Math.floor(h * 1.1);
863 }
864 }
865 }
866 }
867 this.ctx.restore();
868 };
869
870
871 CanvasRenderer.prototype.getHit = function(x, y) {
872 for (var j = 0; j < 2; j++) {
873 var sharp = j ? false : true; // black keys first
874
875 for (var i in this.piano.keys) {
876 if (!this.piano.keys.hasOwnProperty(i)) continue;
877 var key = this.piano.keys[i];
878 if (key.sharp != sharp) continue;
879 if (key.rect.contains(x, y)) {
880 var v = y / (key.sharp ? this.blackKeyHeight : this.whiteKeyHeight);
881 v += 0.25;
882 v *= DEFAULT_VELOCITY;
883 if (v > 1.0) v = 1.0;
884 return {
885 "key": key,
886 "v": v
887 };
888 }
889 }
890 }
891 return null;
892 };
893
894
895
896 CanvasRenderer.isSupported = function() {
897 var canvas = document.createElement("canvas");
898 return !!(canvas.getContext && canvas.getContext("2d"));
899 };
900
901
902 CanvasRenderer.translateMouseEvent = function(evt) {
903 var element = evt.target;
904 var offx = 0;
905 var offy = 0;
906 do {
907 if (!element) break; // wtf, wtf?
908
909 offx += element.offsetLeft;
910 offy += element.offsetTop;
911 } while (element = element.offsetParent);
912 return {
913 x: (evt.pageX - offx) * window.devicePixelRatio,
914 y: (evt.pageY - offy) * window.devicePixelRatio
915 }
916 };
917
918
919
920
921
922
923
924
925
926
927
928 // Soundpack Stuff by electrashave ♥
929
930 ////////////////////////////////////////////////////////////////
931
932
933 function SoundSelector(piano) {
934 this.initiated = false;
935 this.keys = piano.keys;
936 this.loading = {};
937 this.notification;
938 this.packs = [];
939 this.piano = piano;
940 this.soundSelection = localStorage.soundSelection || "MPP Classic";
941 this.addPack({
942 name: "MPP Classic",
943 keys: Object.keys(this.piano.keys),
944 ext: ".mp3",
945 url: "/sounds/mppclassic/"
946 });
947 }
948
949
950 SoundSelector.prototype.addPack = function(pack, load) {
951 var self = this;
952 self.loading[pack.url || pack] = true;
953 function add(obj) {
954 if (self.packs.indexOf(obj) != -1) return; //no adding packs twice D:<
955
956 var html = document.createElement("li");
957 html.classList = "pack";
958 html.innerText = obj.name + " (" + obj.keys.length + " keys)";
959 html.onclick = function() {
960 self.loadPack(obj.name);
961 self.notification.close();
962 };
963 obj.html = html;
964 self.packs.push(obj);
965 self.packs.sort(function(a, b) {
966 if (a.name < b.name) return -1;
967 if (a.name > b.name) return 1;
968 return 0;
969 });
970 if (load) self.loadPack(obj.name);
971 delete self.loading[obj.url];
972 }
973
974
975 if (typeof pack == "string") {
976 $.getJSON(pack + "/info.json").done(function(json) {
977 json.url = pack;
978 add(json);
979 });
980 } else add(pack); //validate packs??
981
982 };
983
984
985 SoundSelector.prototype.addPacks = function(packs) {
986 for (var i = 0; packs.length > i; i++) this.addPack(packs[i]);
987 };
988
989
990 SoundSelector.prototype.init = function() {
991 var self = this;
992 if (self.initialized) return console.warn("Sound selector already initialized!");
993
994
995 if (!!Object.keys(self.loading).length) return setTimeout(function() {
996 self.init();
997 }, 250);
998
999
1000 $("#sound-btn").on("click", function() {
1001 if (document.getElementById("Notification-Sound-Selector") != null) return;
1002 var html = document.createElement("ul");
1003 $(html).append("<h1>Current Sound: " + self.soundSelection + "</h1>");
1004
1005
1006 for (var i = 0; self.packs.length > i; i++) {
1007 var pack = self.packs[i];
1008 if (pack.name == self.soundSelection) pack.html.classList = "pack enabled";
1009 else pack.html.classList = "pack";
1010 html.appendChild(pack.html);
1011 }
1012 self.notification = new Notification({
1013 title: "Sound Selector:",
1014 html: html,
1015 id: "Sound-Selector",
1016 duration: -1,
1017 target: "#sound-btn"
1018 });
1019 });
1020 self.initialized = true;
1021 self.loadPack(self.soundSelection, true);
1022 };
1023
1024
1025 SoundSelector.prototype.loadPack = function(pack, f) {
1026 for (var i = 0; this.packs.length > i; i++) {
1027 var p = this.packs[i];
1028 if (p.name == pack) {
1029 pack = p;
1030 break;
1031 }
1032 }
1033 if (typeof pack == "String") {
1034 console.warn("Sound pack does not exist! Loading default pack...");
1035 pack = this.packs[0];
1036 }
1037
1038
1039 if (pack.name == this.soundSelection && !f) return;
1040 if (pack.keys.length != Object.keys(this.piano.keys).length) {
1041 this.piano.keys = {};
1042 for (var i = 0; pack.keys.length > i; i++) this.piano.keys[pack.keys[i]] = this.keys[pack.keys[i]];
1043 this.piano.renderer.resize();
1044 }
1045
1046
1047 var self = this;
1048 for (var i in this.piano.keys) {
1049 if (!this.piano.keys.hasOwnProperty(i)) continue;
1050 (function() {
1051 var key = self.piano.keys[i];
1052 key.loaded = false;
1053 self.piano.audio.load(key.note, pack.url + key.note + pack.ext, function() {
1054 key.loaded = true;
1055 key.timeLoaded = Date.now();
1056 });
1057 })();
1058 }
1059 localStorage.soundSelection = pack.name;
1060 this.soundSelection = pack.name;
1061 };
1062
1063
1064 SoundSelector.prototype.removePack = function(name) {
1065 var found = false;
1066 for (var i = 0; this.packs.length > i; i++) {
1067 var pack = this.packs[i];
1068 if (pack.name == name) {
1069 this.packs.splice(i, 1);
1070 if (pack.name == this.soundSelection) this.loadPack(this.packs[0].name); //add mpp default if none?
1071
1072 break;
1073 }
1074 }
1075 if (!found) console.warn("Sound pack not found!");
1076 };
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088 // Pianoctor
1089
1090 ////////////////////////////////////////////////////////////////
1091
1092
1093 var PianoKey = function(note, octave) {
1094 this.note = note + octave;
1095 this.baseNote = note;
1096 this.octave = octave;
1097 this.sharp = note.indexOf("s") != -1;
1098 this.loaded = false;
1099 this.timeLoaded = 0;
1100 this.domElement = null;
1101 this.timePlayed = 0;
1102 this.blips = [];
1103 };
1104
1105
1106 var Piano = function(rootElement) {
1107 var piano = this;
1108 piano.rootElement = rootElement;
1109 piano.keys = {};
1110 var white_spatial = 0;
1111 var black_spatial = 0;
1112 var black_it = 0;
1113 var black_lut = [2, 1, 2, 1, 1];
1114 var addKey = function(note, octave) {
1115 var key = new PianoKey(note, octave);
1116 piano.keys[key.note] = key;
1117 if (key.sharp) {
1118 key.spatial = black_spatial;
1119 black_spatial += black_lut[black_it % 5];
1120 ++black_it;
1121 } else {
1122 key.spatial = white_spatial;
1123 ++white_spatial;
1124 }
1125 }
1126 if (test_mode) {
1127 addKey("c", 2);
1128 } else {
1129 addKey("a", -1);
1130 addKey("as", -1);
1131 addKey("b", -1);
1132 var notes = "c cs d ds e f fs g gs a as b".split(" ");
1133 for (var oct = 0; oct < 7; oct++) {
1134 for (var i in notes) {
1135 addKey(notes[i], oct);
1136 }
1137 }
1138 addKey("c", 7);
1139 }
1140
1141
1142
1143 this.renderer = new CanvasRenderer().init(this);
1144 window.addEventListener("resize", function() {
1145 piano.renderer.resize();
1146 });
1147
1148
1149
1150 window.AudioContext = window.AudioContext || window.webkitAudioContext || undefined;
1151 var audio_engine = AudioEngineWeb;
1152 this.audio = new audio_engine().init();
1153 };
1154
1155
1156 Piano.prototype.play = function(note, vol, participant, delay_ms) {
1157 if (!this.keys.hasOwnProperty(note)) return;
1158 var key = this.keys[note];
1159 if (key.loaded) this.audio.play(key.note, vol, delay_ms, participant.id);
1160 if (typeof gMidiOutTest === "function") gMidiOutTest(key.note, vol * 100, delay_ms);
1161 var self = this;
1162 var jq_namediv = $(typeof participant == "undefined" ? null : participant.nameDiv);
1163 if (jq_namediv) {
1164 setTimeout(function() {
1165 self.renderer.visualize(key, typeof participant == "undefined" ? "yellow" : (participant.color || "#777"));
1166 jq_namediv.addClass("play");
1167 setTimeout(function() {
1168 jq_namediv.removeClass("play");
1169 }, 30);
1170 }, delay_ms);
1171 }
1172 };
1173
1174
1175 Piano.prototype.stop = function(note, participant, delay_ms) {
1176 if (!this.keys.hasOwnProperty(note)) return;
1177 var key = this.keys[note];
1178 if (key.loaded) this.audio.stop(key.note, delay_ms, participant.id);
1179 if (typeof gMidiOutTest === "function") gMidiOutTest(key.note, 0, delay_ms);
1180 };
1181 var gPiano = new Piano(document.getElementById("piano"));
1182 var gSoundSelector = new SoundSelector(gPiano);
1183 gSoundSelector.addPacks(["/sounds/Emotional_2.0/", "/sounds/Harp/", "/sounds/Music_Box/", "/sounds/Vintage_Upright/", "/sounds/Steinway_Grand/", "/sounds/Emotional/", "/sounds/Untitled/"]);
1184 gSoundSelector.init();
1185
1186
1187
1188
1189
1190
1191
1192
1193 var gAutoSustain = false;
1194 var gSustain = false;
1195
1196
1197 var gHeldNotes = {};
1198 var gSustainedNotes = {};
1199
1200
1201 function press(id, vol) {
1202 if (!gClient.preventsPlaying() && gNoteQuota.spend(1)) {
1203 gHeldNotes[id] = true;
1204 gSustainedNotes[id] = true;
1205 gPiano.play(id, vol !== undefined ? vol : DEFAULT_VELOCITY, gClient.getOwnParticipant(), 0);
1206 gClient.startNote(id, vol);
1207 }
1208 }
1209
1210
1211 function release(id) {
1212 if (gHeldNotes[id]) {
1213 gHeldNotes[id] = false;
1214 if ((gAutoSustain || gSustain) && !enableSynth) {
1215 gSustainedNotes[id] = true;
1216 } else {
1217 if (gNoteQuota.spend(1)) {
1218 gPiano.stop(id, gClient.getOwnParticipant(), 0);
1219 gClient.stopNote(id);
1220 gSustainedNotes[id] = false;
1221 }
1222 }
1223 }
1224 }
1225
1226
1227 function pressSustain() {
1228 gSustain = true;
1229 }
1230
1231
1232 function releaseSustain() {
1233 gSustain = false;
1234 if (!gAutoSustain) {
1235 for (var id in gSustainedNotes) {
1236 if (gSustainedNotes.hasOwnProperty(id) && gSustainedNotes[id] && !gHeldNotes[id]) {
1237 gSustainedNotes[id] = false;
1238 if (gNoteQuota.spend(1)) {
1239 gPiano.stop(id, gClient.getOwnParticipant(), 0);
1240 gClient.stopNote(id);
1241 }
1242 }
1243 }
1244 }
1245 }
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255 // internet science
1256
1257 ////////////////////////////////////////////////////////////////
1258
1259
1260 var channel_id = decodeURIComponent(window.location.pathname);
1261 if (channel_id.substr(0, 1) == "/") channel_id = channel_id.substr(1);
1262 if (channel_id == "") channel_id = "lobby";
1263
1264
1265 var wssport = window.location.hostname == "www.multiplayerpiano.com" ? 443 : 8080;
1266 var gClient = new Client("ws://" + window.location.hostname + ":" + wssport);
1267 gClient.setChannel(channel_id);
1268 gClient.start();
1269
1270
1271 // Setting status
1272
1273 (function() {
1274 gClient.on("status", function(status) {
1275 $("#status").text(status);
1276 });
1277 gClient.on("count", function(count) {
1278 if (count > 0) {
1279 $("#status").html('<span class="number">' + count + '</span> ' + (count == 1 ? 'person is' : 'people are') + ' playing');
1280 document.title = "Piano (" + count + ")";
1281 } else {
1282 document.title = "Multiplayer Piano";
1283 }
1284 });
1285 })();
1286
1287 // Handle changes to participants
1288
1289 (function() {
1290 gClient.on("participant added", function(part) {
1291
1292
1293 part.displayX = 150;
1294 part.displayY = 50;
1295
1296 // add nameDiv
1297
1298 var div = document.createElement("div");
1299 div.className = "name";
1300 div.participantId = part.id;
1301 div.textContent = part.name || "";
1302 div.style.backgroundColor = part.color || "#777";
1303 if (gClient.participantId === part.id) {
1304 $(div).addClass("me");
1305 }
1306 if (gClient.channel && gClient.channel.crown && gClient.channel.crown.participantId === part.id) {
1307 $(div).addClass("owner");
1308 }
1309 if (gPianoMutes.indexOf(part._id) !== -1) {
1310 $(part.nameDiv).addClass("muted-notes");
1311 }
1312 if (gChatMutes.indexOf(part._id) !== -1) {
1313 $(part.nameDiv).addClass("muted-chat");
1314 }
1315 div.style.display = "none";
1316 part.nameDiv = $("#names")[0].appendChild(div);
1317 $(part.nameDiv).fadeIn(2000);
1318
1319 // sort names
1320
1321 var arr = $("#names .name");
1322 arr.sort(function(a, b) {
1323 a = a.style.backgroundColor; // todo: sort based on user id instead
1324
1325 b = b.style.backgroundColor;
1326 if (a > b) return 1;
1327 else if (a < b) return -1;
1328 else return 0;
1329 });
1330 $("#names").html(arr);
1331
1332 // add cursorDiv
1333
1334 if (gClient.participantId !== part.id || gSeeOwnCursor) {
1335 var div = document.createElement("div");
1336 div.className = "cursor";
1337 div.style.display = "none";
1338 part.cursorDiv = $("#cursors")[0].appendChild(div);
1339 $(part.cursorDiv).fadeIn(2000);
1340
1341
1342 var div = document.createElement("div");
1343 div.className = "name";
1344 div.style.backgroundColor = part.color || "#777"
1345 div.textContent = part.name || "";
1346 part.cursorDiv.appendChild(div);
1347
1348
1349 } else {
1350 part.cursorDiv = undefined;
1351 }
1352 });
1353 gClient.on("participant removed", function(part) { // remove nameDiv
1354
1355 var nd = $(part.nameDiv);
1356 var cd = $(part.cursorDiv);
1357 cd.fadeOut(2000);
1358 nd.fadeOut(2000, function() {
1359 nd.remove();
1360 cd.remove();
1361 part.nameDiv = undefined;
1362 part.cursorDiv = undefined;
1363 });
1364 });
1365 gClient.on("participant update", function(part) {
1366 var name = part.name || "";
1367 var color = part.color || "#777";
1368 part.nameDiv.style.backgroundColor = color;
1369 part.nameDiv.textContent = name;
1370 $(part.cursorDiv) .find(".name") .text(name) .css("background-color", color);
1371 });
1372 gClient.on("ch", function(msg) {
1373 for (var id in gClient.ppl) {
1374 if (gClient.ppl.hasOwnProperty(id)) {
1375 var part = gClient.ppl[id];
1376 if (part.id === gClient.participantId) {
1377 $(part.nameDiv).addClass("me");
1378 } else {
1379 $(part.nameDiv).removeClass("me");
1380 }
1381 if (msg.ch.crown && msg.ch.crown.participantId === part.id) {
1382 $(part.nameDiv).addClass("owner");
1383 $(part.cursorDiv).addClass("owner");
1384 } else {
1385 $(part.nameDiv).removeClass("owner");
1386 $(part.cursorDiv).removeClass("owner");
1387 }
1388 if (gPianoMutes.indexOf(part._id) !== -1) {
1389 $(part.nameDiv).addClass("muted-notes");
1390 } else {
1391 $(part.nameDiv).removeClass("muted-notes");
1392 }
1393 if (gChatMutes.indexOf(part._id) !== -1) {
1394 $(part.nameDiv).addClass("muted-chat");
1395 } else {
1396 $(part.nameDiv).removeClass("muted-chat");
1397 }
1398 }
1399 }
1400 });
1401 function updateCursor(msg) {
1402 const part = gClient.ppl[msg.id];
1403 if (part && part.cursorDiv) {
1404 part.cursorDiv.style.left = msg.x + "%";
1405 part.cursorDiv.style.top = msg.y + "%";
1406 }
1407 }
1408 gClient.on("m", updateCursor);
1409 gClient.on("participant added", updateCursor);
1410 })();
1411
1412
1413 // Handle changes to crown
1414
1415 (function() {
1416 var jqcrown = $('<div id="crown"></div>').appendTo(document.body).hide();
1417 var jqcountdown = $('<span></span>').appendTo(jqcrown);
1418 var countdown_interval;
1419 jqcrown.click(function() {
1420 gClient.sendArray([{
1421 m: "chown",
1422 id: gClient.participantId
1423 }]);
1424 });
1425 gClient.on("ch", function(msg) {
1426 if (msg.ch.crown) {
1427 var crown = msg.ch.crown;
1428 if (!crown.participantId || !gClient.ppl[crown.participantId]) {
1429 var land_time = crown.time + 2000 - gClient.serverTimeOffset;
1430 var avail_time = crown.time + 15000 - gClient.serverTimeOffset;
1431 jqcountdown.text("");
1432 jqcrown.show();
1433 if (land_time - Date.now() <= 0) {
1434 jqcrown.css({
1435 "left": crown.endPos.x + "%",
1436 "top": crown.endPos.y + "%"
1437 });
1438 } else {
1439 jqcrown.css({
1440 "left": crown.startPos.x + "%",
1441 "top": crown.startPos.y + "%"
1442 });
1443 jqcrown.addClass("spin");
1444 jqcrown.animate({
1445 "left": crown.endPos.x + "%",
1446 "top": crown.endPos.y + "%"
1447 }, 2000, "linear", function() {
1448 jqcrown.removeClass("spin");
1449 });
1450 }
1451 clearInterval(countdown_interval);
1452 countdown_interval = setInterval(function() {
1453 var time = Date.now();
1454 if (time >= land_time) {
1455 var ms = avail_time - time;
1456 if (ms > 0) {
1457 jqcountdown.text(Math.ceil(ms / 1000) + "s");
1458 } else {
1459 jqcountdown.text("");
1460 clearInterval(countdown_interval);
1461 }
1462 }
1463 }, 1000);
1464 } else {
1465 jqcrown.hide();
1466 }
1467 } else {
1468 jqcrown.hide();
1469 }
1470 });
1471 gClient.on("disconnect", function() {
1472 jqcrown.fadeOut(2000);
1473 });
1474 })();
1475
1476 // Playing notes
1477
1478 gClient.on("n", function(msg) {
1479 var t = msg.t - gClient.serverTimeOffset + TIMING_TARGET - Date.now();
1480 var participant = gClient.findParticipantById(msg.p);
1481 if (gPianoMutes.indexOf(participant._id) !== -1) return;
1482 for (var i = 0; i < msg.n.length; i++) {
1483 var note = msg.n[i];
1484 var ms = t + (note.d || 0);
1485 if (ms < 0) {
1486 ms = 0;
1487 }
1488 else if (ms > 10000) continue;
1489 if (note.s) {
1490 gPiano.stop(note.n, participant, ms);
1491 } else {
1492 var vel = (typeof note.v !== "undefined") ? parseFloat(note.v) : DEFAULT_VELOCITY;
1493 if (vel < 0) vel = 0;
1494 else if (vel > 1) vel = 1;
1495 gPiano.play(note.n, vel, participant, ms);
1496 if (enableSynth) {
1497 gPiano.stop(note.n, participant, ms + 1000);
1498 }
1499 }
1500 }
1501 });
1502
1503 // Send cursor updates
1504
1505 var mx = 0,
1506 last_mx = -10,
1507 my = 0,
1508 last_my = -10;
1509 setInterval(function() {
1510 if (Math.abs(mx - last_mx) > 0.1 || Math.abs(my - last_my) > 0.1) {
1511 last_mx = mx;
1512 last_my = my;
1513 gClient.sendArray([{
1514 m: "m",
1515 x: mx,
1516 y: my
1517 }]);
1518 if (gSeeOwnCursor) {
1519 gClient.emit("m", {
1520 m: "m",
1521 id: gClient.participantId,
1522 x: mx,
1523 y: my
1524 });
1525 }
1526 var part = gClient.getOwnParticipant();
1527 if (part) {
1528 part.x = mx;
1529 part.y = my;
1530 }
1531 }
1532 }, 50);
1533 $(document).mousemove(function(event) {
1534 mx = ((event.pageX / $(window).width()) * 100).toFixed(2);
1535 my = ((event.pageY / $(window).height()) * 100).toFixed(2);
1536 });
1537
1538
1539 // Room settings button
1540
1541 (function() {
1542 gClient.on("ch", function(msg) {
1543 if (gClient.isOwner()) {
1544 $("#room-settings-btn").show();
1545 } else {
1546 $("#room-settings-btn").hide();
1547 }
1548 });
1549 $("#room-settings-btn").click(function(evt) {
1550 if (gClient.channel && gClient.isOwner()) {
1551 var settings = gClient.channel.settings;
1552 openModal("#room-settings");
1553 setTimeout(function() {
1554 $("#room-settings .checkbox[name=visible]").prop("checked", settings.visible);
1555 $("#room-settings .checkbox[name=chat]").prop("checked", settings.chat);
1556 $("#room-settings .checkbox[name=crownsolo]").prop("checked", settings.crownsolo);
1557 $("#room-settings input[name=color]").val(settings.color);
1558 }, 100);
1559 }
1560 });
1561 $("#room-settings .submit").click(function() {
1562 var settings = {
1563 visible: $("#room-settings .checkbox[name=visible]").is(":checked"),
1564 chat: $("#room-settings .checkbox[name=chat]").is(":checked"),
1565 crownsolo: $("#room-settings .checkbox[name=crownsolo]").is(":checked"),
1566 color: $("#room-settings input[name=color]").val()
1567 };
1568 gClient.sendArray([{
1569 m: "chset",
1570 set: settings
1571 }]);
1572 closeModal();
1573 });
1574 $("#room-settings .drop-crown").click(function() {
1575 closeModal();
1576 if (confirm("This will drop the crown...!")) gClient.sendArray([{
1577 m: "chown"
1578 }]);
1579 });
1580 })();
1581
1582 // Handle notifications
1583
1584 gClient.on("notification", function(msg) {
1585 new Notification(msg);
1586 });
1587
1588 // Don't foget spin
1589
1590 gClient.on("ch", function(msg) {
1591 var chidlo = msg.ch._id.toLowerCase();
1592 if (chidlo === "spin" || chidlo.substr(-5) === "/spin") {
1593 $("#piano").addClass("spin");
1594 } else {
1595 $("#piano").removeClass("spin");
1596 }
1597 });
1598
1599
1600 /*function eb() {
1601 if(gClient.channel && gClient.channel._id.toLowerCase() === "test/fishing") {
1602 ebsprite.start(gClient);
1603 } else {
1604 ebsprite.stop();
1605 }
1606 }
1607 if(ebsprite) {
1608 gClient.on("ch", eb);
1609 eb();
1610 }*/
1611
1612 // Crownsolo notice
1613
1614 gClient.on("ch", function(msg) {
1615 if (msg.ch.settings.crownsolo) {
1616 if ($("#crownsolo-notice").length == 0) {
1617 $('<div id="crownsolo-notice">').text('This room is set to "only the owner can play."').appendTo("body").fadeIn(1000);
1618 }
1619 } else {
1620 $("#crownsolo-notice").remove();
1621 }
1622 });
1623 gClient.on("disconnect", function() {
1624 $("#crownsolo-notice").remove();
1625 });
1626
1627
1628 // Background color
1629
1630 (function() {
1631 var old_color1 = new Color("#3b5054");
1632 var old_color2 = new Color("#3b5054");
1633 function setColor(hex) {
1634 var color1 = new Color(hex);
1635 var color2 = new Color(hex);
1636 color2.add(-0x40, -0x40, -0x40);
1637
1638
1639 var bottom = document.getElementById("bottom");
1640 var duration = 500;
1641 var step = 0;
1642 var steps = 30;
1643 var step_ms = duration / steps;
1644 var difference = new Color(color1.r, color1.g, color1.b);
1645 difference.r -= old_color1.r;
1646 difference.g -= old_color1.g;
1647 difference.b -= old_color1.b;
1648 var inc = new Color(difference.r / steps, difference.g / steps, difference.b / steps);
1649 var iv;
1650 iv = setInterval(function() {
1651 old_color1.add(inc.r, inc.g, inc.b);
1652 old_color2.add(inc.r, inc.g, inc.b);
1653 document.body.style.background = "radial-gradient(ellipse at center, " + old_color1.toHexa() + " 0%," + old_color2.toHexa() + " 100%)";
1654 bottom.style.background = old_color2.toHexa();
1655 if (++step >= steps) {
1656 clearInterval(iv);
1657 old_color1 = color1;
1658 old_color2 = color2;
1659 document.body.style.background = "radial-gradient(ellipse at center, " + color1.toHexa() + " 0%," + color2.toHexa() + " 100%)";
1660 bottom.style.background = color2.toHexa();
1661 }
1662 }, step_ms);
1663 }
1664
1665
1666 setColor("#3b5054");
1667
1668
1669 gClient.on("ch", function(ch) {
1670 if (ch.ch.settings) {
1671 if (ch.ch.settings.color) {
1672 setColor(ch.ch.settings.color);
1673 } else {
1674 setColor("#3b5054");
1675 }
1676 }
1677 });
1678 })();
1679
1680
1681
1682
1683
1684
1685
1686 var gPianoMutes = [];
1687
1688
1689 var gChatMutes = [];
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709 var volume_slider = document.getElementById("volume-slider");
1710 volume_slider.value = gPiano.audio.volume;
1711 $("#volume-label").text("Volume: " + Math.floor(gPiano.audio.volume * 100) + "%");
1712 volume_slider.addEventListener("input", function(evt) {
1713 var v = +volume_slider.value;
1714 gPiano.audio.setVolume(v);
1715 if (window.localStorage) localStorage.volume = v;
1716 $("#volume-label").text("Volume: " + Math.floor(v * 100) + "%");
1717 });
1718
1719
1720
1721
1722
1723 var Note = function(note, octave) {
1724 this.note = note;
1725 this.octave = octave || 0;
1726 };
1727
1728
1729
1730
1731 var n = function(a, b) {
1732 return {
1733 note: new Note(a, b),
1734 held: false
1735 };
1736 };
1737 var key_binding = {
1738 37: n("a", -2),
1739 39: n("b", -2),
1740 112: n("c", -1),
1741 113: n("d", -1),
1742 114: n("e", -1),
1743 115: n("f", -1),
1744 116: n("g", -1),
1745 117: n("a", -1),
1746 118: n("b", -1),
1747
1748 49: n("c"),
1749 50: n("d"),
1750 51: n("e"),
1751 52: n("f"),
1752 53: n("g"),
1753 54: n("a"),
1754 55: n("b"),
1755 56: n("c", 1),
1756 57: n("d", 1),
1757 48: n("e", 1),
1758 81: n("f", 1),
1759 87: n("g", 1),
1760 69: n("a", 1),
1761 82: n("b", 1),
1762 84: n("c", 2),
1763 89: n("d", 2),
1764 85: n("e", 2),
1765 73: n("f", 2),
1766 79: n("g", 2),
1767 80: n("a", 2),
1768 65: n("b", 2),
1769 83: n("c", 3),
1770 68: n("d", 3),
1771 70: n("e", 3),
1772 71: n("f", 3),
1773 72: n("g", 3),
1774 74: n("a", 3),
1775 75: n("b", 3),
1776 76: n("c", 4),
1777 90: n("d", 4),
1778 88: n("e", 4),
1779 67: n("f", 4),
1780 86: n("g", 4),
1781 66: n("a", 4),
1782 78: n("b", 4),
1783 77: n("c", 5),
1784 };
1785
1786
1787 var capsLockKey = false;
1788
1789 var transLvl = 0;
1790 var octave = 0;
1791 var keys = [];
1792
1793
1794 var transpose_octave = 0;
1795
1796 var additional_octaves = 0; // number of additional octaves to play at same time
1797 var transpose_halfsteps = 0; // number of half steps to increase note by
1798 var transpose_array = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"];
1799
1800 function handleKeyDown(evt) {
1801 //console.log(evt);
1802 var code = parseInt(evt.keyCode);
1803 if (key_binding[code] !== undefined) {
1804 var binding = key_binding[code];
1805 if (!binding.held) {
1806 binding.held = true;
1807
1808 var note = binding.note; // the note ie c cs d ds
1809 var octave = 1 + note.octave + transpose_octave; // the octave (a number)
1810
1811 if (evt.shiftKey) note = note.note + "s";
1812 else note = note.note;
1813
1814 if (note === "bs") note = "c", octave++;
1815 else if (note === "es") note = "f";
1816
1817 // if the transpose halfsteps is negative and greater than the index of the note then treat as going positive by 12 plus the negative but octave goes down once
1818 // because of adding 12
1819 var c_o_octave_mod = 0;
1820 var new_note_index = (transpose_array.indexOf(note) + transpose_halfsteps)
1821 if (transpose_array.indexOf(note) < -transpose_halfsteps) {
1822 c_o_octave_mod = -1;
1823 new_note_index = (transpose_array.indexOf(note) + transpose_halfsteps + 12)
1824 }
1825 note = transpose_array[new_note_index % 12];
1826 var carry_over_octave = Math.floor(new_note_index / 12); // if theres overlap then increase the octave
1827 octave += carry_over_octave + c_o_octave_mod;
1828
1829 var add_oct_mult = 1;
1830 if (additional_octaves < 0) add_oct_mult = -1;
1831 for (var add_oct = 0; add_oct <= Math.abs(additional_octaves); add_oct++) {
1832 var note1 = note + (octave + add_oct * add_oct_mult);
1833 var vol = velocityFromMouseY();
1834 //console.log(note1);
1835 press(note1, vol);
1836 }
1837 }
1838
1839
1840 evt.preventDefault();
1841 evt.stopPropagation();
1842 return false;
1843 } else if(code == 20) { // Caps Lock
1844 capsLockKey = true;
1845 evt.preventDefault();
1846 } else if(code === 0x20) { // Space Bar
1847 pressSustain();
1848 evt.preventDefault();
1849 } else if((code === 33) && transpose_octave < 3) {
1850 ++transpose_octave;
1851 } else if((code === 34) && transpose_octave > -2) {
1852 --transpose_octave;
1853 } else if(code == 9) { // Tab (don't tab away from the piano)
1854 evt.preventDefault();
1855 } else if(code == 8) { // Backspace (don't navigate Back)
1856 gAutoSustain = !gAutoSustain;
1857 evt.preventDefault();
1858 }
1859 };
1860
1861keyDown = function(evt) {
1862 var code = parseInt(evt.keyCode);
1863 if (code == 36) {
1864 transLvl++;
1865
1866 }
1867 if (code == 35) {
1868 transLvl--;
1869
1870 }
1871 if (!keys[code]) {
1872 octave = 0;
1873 keys[code] = true;
1874 }
1875};
1876keyUp = function(evt) {
1877 var code = parseInt(evt.keyCode);
1878 if (keys[code]) {
1879 octave = 0;
1880 keys[code] = false;
1881 }
1882};
1883$(document).on("keydown", keyDown);
1884$(document).on("keyup", keyUp);
1885gClient.startNote = function(note, vel) {
1886 note = MPP.piano.keys[Object.keys(MPP.piano.keys)[Object.keys(MPP.piano.keys).indexOf(note) + transLvl]].note;
1887 var octaves = parseInt(note.replace(/[^\d.]/, '').replace('s', ''));
1888 var note = note.replace(/[0-9]/g, '').replace("-", "");
1889 note = note + (octaves + octave);
1890 if (this.isConnected()) {
1891 var vel = typeof vel === "undefined" ? undefined : +vel.toFixed(3);
1892 if (!this.noteBufferTime) {
1893 this.noteBufferTime = Date.now();
1894 this.noteBuffer.push({
1895 n: note,
1896 v: vel
1897 });
1898 } else {
1899 this.noteBuffer.push({
1900 d: Date.now() - this.noteBufferTime,
1901 n: note,
1902 v: vel
1903 });
1904 }
1905 }
1906};
1907gPiano.play = function(note, vol, participant, delay_ms) {
1908 if (participant._id == MPP.client.getOwnParticipant()._id) {
1909 note = MPP.piano.keys[Object.keys(MPP.piano.keys)[Object.keys(MPP.piano.keys).indexOf(note) + transLvl]].note;
1910 var octaves = parseInt(note.replace(/[^\d.]/, '').replace('s', ''));
1911 var note = note.replace(/[0-9]/g, '').replace("-", "");
1912 note = note + (octaves + octave);
1913 }
1914 if (!this.keys.hasOwnProperty(note)) return;
1915 var key = this.keys[note];
1916 if (key.loaded) this.audio.play(key.note, vol, delay_ms, participant.id);
1917 var self = this;
1918 var jq_namediv = $(typeof participant == "undefined" ? null : participant.nameDiv);
1919 if (jq_namediv) {
1920 setTimeout(function() {
1921 self.renderer.visualize(key, typeof participant == "undefined" ? "yellow" : (participant.color || "#777"));
1922 jq_namediv.addClass("play");
1923 setTimeout(function() {
1924 jq_namediv.removeClass("play");
1925 }, 30);
1926 }, delay_ms);
1927 }
1928};
1929gClient.stopNote = function(note) {
1930 if(gClient.isConnected()) {
1931 note = MPP.piano.keys[Object.keys(MPP.piano.keys)[Object.keys(MPP.piano.keys).indexOf(note) + transLvl]].note;
1932 var octaves = parseInt(note.replace(/[^\d.]/, '').replace('s', ''));
1933 var note = note.replace(/[0-9]/g, '').replace("-", "");
1934 note = note + (octaves + octave);
1935 if(!gClient.noteBufferTime) {
1936 gClient.noteBufferTime = Date.now();
1937 gClient.noteBuffer.push({n: note, s: 1});
1938 } else {
1939 gClient.noteBuffer.push({d: Date.now() - gClient.noteBufferTime, n: note, s: 1});
1940 }
1941 }
1942};
1943gPiano.stop = function(note, participant, delay_ms) {
1944 if (!gPiano.keys.hasOwnProperty(note)) return;
1945 if (participant._id == MPP.client.getOwnParticipant()._id) {
1946 note = MPP.piano.keys[Object.keys(MPP.piano.keys)[Object.keys(MPP.piano.keys).indexOf(note) + transLvl]].note;
1947 var octaves = parseInt(note.replace(/[^\d.]/, '').replace('s', ''));
1948 var note = note.replace(/[0-9]/g, '').replace("-", "");
1949 note = note + (octaves + octave);
1950 }
1951 var key = gPiano.keys[note];
1952 if (key.loaded) gPiano.audio.stop(key.note, delay_ms, participant.id);
1953 if (typeof gMidiOutTest === "function") gMidiOutTest(key.note, 0, delay_ms);
1954};
1955 function handleKeyUp(evt) {
1956 var code = parseInt(evt.keyCode);
1957 if (key_binding[code] !== undefined) {
1958 var binding = key_binding[code];
1959 if (binding.held) {
1960 binding.held = false;
1961
1962 var note = binding.note;
1963 var octave = 1 + note.octave + transpose_octave;
1964 if (evt.shiftKey) ++octave;
1965 else if (capsLockKey || evt.ctrlKey) --octave;
1966 note = note.note + octave;
1967 release(note);
1968 }
1969
1970 evt.preventDefault();
1971 evt.stopPropagation();
1972 return false;
1973 } else if (code == 20) { // Caps Lock
1974 capsLockKey = false;
1975 evt.preventDefault();
1976 } else if (code === 0x20) { // Space Bar
1977 releaseSustain();
1978 evt.preventDefault();
1979 }
1980 };
1981
1982 function handleKeyPress(evt) {
1983 evt.preventDefault();
1984 evt.stopPropagation();
1985 if (evt.keyCode == 27 || evt.keyCode == 13) {
1986 //$("#chat input").focus();
1987 }
1988 return false;
1989 };
1990
1991
1992 var recapListener = function(evt) {
1993 captureKeyboard();
1994 };
1995
1996 function captureKeyboard() {
1997 $("#piano").off("mousedown", recapListener);
1998 $("#piano").off("touchstart", recapListener);
1999 $(document).on("keydown", handleKeyDown);
2000 $(document).on("keyup", handleKeyUp);
2001 $(window).on("keypress", handleKeyPress);
2002 };
2003
2004 function releaseKeyboard() {
2005 $(document).off("keydown", handleKeyDown);
2006 $(document).off("keyup", handleKeyUp);
2007 $(window).off("keypress", handleKeyPress);
2008 $("#piano").on("mousedown", recapListener);
2009 $("#piano").on("touchstart", recapListener);
2010 };
2011
2012 captureKeyboard();
2013
2014
2015 var velocityFromMouseY = function() {
2016 return 0.1 + (my / 100) * 0.6;
2017 };
2018
2019
2020
2021
2022
2023 // NoteQuota
2024
2025 var gNoteQuota = (function() {
2026 var last_rat = 0;
2027 var nqjq = $("#quota .value");
2028 setInterval(function() {
2029 gNoteQuota.tick();
2030 }, 2000);
2031 return new NoteQuota(function(points) { // update UI
2032
2033 var rat = (points / this.max) * 100;
2034 if (rat <= last_rat) nqjq.stop(true, true).css("width", rat.toFixed(0) + "%");
2035 else nqjq.stop(true, true).animate({
2036 "width": rat.toFixed(0) + "%"
2037 }, 2000, "linear");
2038 last_rat = rat;
2039 });
2040 })();
2041 gClient.on("nq", function(nq_params) {
2042 gNoteQuota.setParams(nq_params);
2043 });
2044 gClient.on("disconnect", function() {
2045 gNoteQuota.setParams(NoteQuota.PARAMS_OFFLINE);
2046 });
2047
2048
2049
2050 // click participant names
2051
2052 (function() {
2053 var ele = document.getElementById("names");
2054 var touchhandler = function(e) {
2055 var target_jq = $(e.target);
2056 if (target_jq.hasClass("name")) {
2057 target_jq.addClass("play");
2058 if (e.target.participantId == gClient.participantId) {
2059 openModal("#rename", "input[name=name]");
2060 setTimeout(function() {
2061 $("#rename input[name=name]").val(gClient.ppl[gClient.participantId].name);
2062 $("#rename input[name=color]").val(gClient.ppl[gClient.participantId].color);
2063 }, 100);
2064 } else if (e.target.participantId) {
2065 var id = e.target.participantId;
2066 var part = gClient.ppl[id] || null;
2067 if (part) {
2068 participantMenu(part);
2069 e.stopPropagation();
2070 }
2071 }
2072 }
2073 };
2074 ele.addEventListener("mousedown", touchhandler);
2075 ele.addEventListener("touchstart", touchhandler);
2076 var releasehandler = function(e) {
2077 $("#names .name").removeClass("play");
2078 };
2079 document.body.addEventListener("mouseup", releasehandler);
2080 document.body.addEventListener("touchend", releasehandler);
2081
2082
2083 var removeParticipantMenus = function() {
2084 $(".participant-menu").remove();
2085 $(".participantSpotlight").hide();
2086 document.removeEventListener("mousedown", removeParticipantMenus);
2087 document.removeEventListener("touchstart", removeParticipantMenus);
2088 };
2089
2090
2091 var participantMenu = function(part) {
2092 if (!part) return;
2093 removeParticipantMenus();
2094 document.addEventListener("mousedown", removeParticipantMenus);
2095 document.addEventListener("touchstart", removeParticipantMenus);
2096 $("#" + part.id).find(".enemySpotlight").show();
2097 var menu = $('<div class="participant-menu"></div>');
2098 $("body").append(menu); // move menu to name position
2099
2100 var jq_nd = $(part.nameDiv);
2101 var pos = jq_nd.position();
2102 menu.css({
2103 "top": pos.top + jq_nd.height() + 15,
2104 "left": pos.left + 6,
2105 "background": part.color || "black"
2106 });
2107 menu.on("mousedown touchstart", function(evt) {
2108 evt.stopPropagation();
2109 var target = $(evt.target);
2110 if (target.hasClass("menu-item")) {
2111 target.addClass("clicked");
2112 menu.fadeOut(200, function() {
2113 removeParticipantMenus();
2114 });
2115 }
2116 }); // this spaces stuff out but also can be used for informational
2117
2118 $('<div class="info"></div>').appendTo(menu).text(part._id); // add menu items
2119
2120 if (gPianoMutes.indexOf(part._id) == -1) {
2121 $('<div class="menu-item">Mute Notes</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2122 gPianoMutes.push(part._id);
2123 $(part.nameDiv).addClass("muted-notes");
2124 });
2125 } else {
2126 $('<div class="menu-item">Unmute Notes</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2127 var i;
2128 while ((i = gPianoMutes.indexOf(part._id)) != -1) gPianoMutes.splice(i, 1);
2129 $(part.nameDiv).removeClass("muted-notes");
2130 });
2131 }
2132 if (gChatMutes.indexOf(part._id) == -1) {
2133 $('<div class="menu-item">Mute Chat</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2134 gChatMutes.push(part._id);
2135 $(part.nameDiv).addClass("muted-chat");
2136 });
2137 } else {
2138 $('<div class="menu-item">Unmute Chat</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2139 var i;
2140 while ((i = gChatMutes.indexOf(part._id)) != -1) gChatMutes.splice(i, 1);
2141 $(part.nameDiv).removeClass("muted-chat");
2142 });
2143 }
2144 if (!(gPianoMutes.indexOf(part._id) >= 0) || !(gChatMutes.indexOf(part._id) >= 0)) {
2145 $('<div class="menu-item">Mute Completely</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2146 gPianoMutes.push(part._id);
2147 gChatMutes.push(part._id);
2148 $(part.nameDiv).addClass("muted-notes");
2149 $(part.nameDiv).addClass("muted-chat");
2150 });
2151 }
2152 if ((gPianoMutes.indexOf(part._id) >= 0) || (gChatMutes.indexOf(part._id) >= 0)) {
2153 $('<div class="menu-item">Unmute Completely</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2154 var i;
2155 while ((i = gPianoMutes.indexOf(part._id)) != -1) gPianoMutes.splice(i, 1);
2156 while ((i = gChatMutes.indexOf(part._id)) != -1) gChatMutes.splice(i, 1);
2157 $(part.nameDiv).removeClass("muted-notes");
2158 $(part.nameDiv).removeClass("muted-chat");
2159 });
2160 }
2161 if (gClient.isOwner()) {
2162 $('<div class="menu-item give-crown">Give Crown</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2163 if (confirm("Give room ownership to " + part.name + "?")) gClient.sendArray([{
2164 m: "chown",
2165 id: part.id
2166 }]);
2167 });
2168 $('<div class="menu-item kickban">Kickban</div>').appendTo(menu) .on("mousedown touchstart", function(evt) {
2169 var minutes = prompt("How many minutes? (0-60)", "30");
2170 if (minutes === null) return;
2171 minutes = parseFloat(minutes) || 0;
2172 var ms = minutes * 60 * 1000;
2173 gClient.sendArray([{
2174 m: "kickban",
2175 _id: part._id,
2176 ms: ms
2177 }]);
2178 });
2179 }
2180 menu.fadeIn(100);
2181 };
2182 })();
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198 // Notification class
2199
2200 ////////////////////////////////////////////////////////////////
2201
2202
2203 var Notification = function(par) {
2204 EventEmitter.call(this);
2205
2206
2207 var par = par || {};
2208
2209
2210 this.id = "Notification-" + (par.id || Math.random());
2211 this.title = par.title || "";
2212 this.text = par.text || "";
2213 this.html = par.html || "";
2214 this.target = $(par.target || "#piano");
2215 this.duration = par.duration || 30000;
2216 this["class"] = par["class"] || "classic";
2217 var self = this;
2218 var eles = $("#" + this.id);
2219 if (eles.length > 0) {
2220 eles.remove();
2221 }
2222 this.domElement = $('<div class="notification"><div class="notification-body"><div class="title"></div>' + '<div class="text"></div></div><div class="x">x</div></div>');
2223 this.domElement[0].id = this.id;
2224 this.domElement.addClass(this["class"]);
2225 this.domElement.find(".title").text(this.title);
2226 if (this.text.length > 0) {
2227 this.domElement.find(".text").text(this.text);
2228 } else if (this.html instanceof HTMLElement) {
2229 this.domElement.find(".text")[0].appendChild(this.html);
2230 } else if (this.html.length > 0) {
2231 this.domElement.find(".text").html(this.html);
2232 }
2233 document.body.appendChild(this.domElement.get(0));
2234 this.position();
2235 this.onresize = function() {
2236 self.position();
2237 };
2238 window.addEventListener("resize", this.onresize);
2239
2240
2241 this.domElement.find(".x").click(function() {
2242 self.close();
2243 });
2244
2245
2246 if (this.duration > 0) {
2247 setTimeout(function() {
2248 self.close();
2249 }, this.duration);
2250 }
2251
2252
2253 return this;
2254 }
2255
2256
2257 mixin(Notification.prototype, EventEmitter.prototype);
2258 Notification.prototype.constructor = Notification;
2259
2260
2261 Notification.prototype.position = function() {
2262 var pos = this.target.offset();
2263 var x = pos.left - (this.domElement.width() / 2) + (this.target.width() / 4);
2264 var y = pos.top - this.domElement.height() - 8;
2265 var width = this.domElement.width();
2266 if (x + width > $("body").width()) {
2267 x -= ((x + width) - $("body").width());
2268 }
2269 if (x < 0) x = 0;
2270 this.domElement.offset({
2271 left: x,
2272 top: y
2273 });
2274 };
2275
2276
2277 Notification.prototype.close = function() {
2278 var self = this;
2279 window.removeEventListener("resize", this.onresize);
2280 this.domElement.fadeOut(500, function() {
2281 self.domElement.remove();
2282 self.emit("close");
2283 });
2284 };
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300 // set variables from settings or set settings
2301
2302 ////////////////////////////////////////////////////////////////
2303
2304
2305 var gKeyboardSeq = 0;
2306 var gKnowsYouCanUseKeyboard = false;
2307 if (localStorage && localStorage.knowsYouCanUseKeyboard) gKnowsYouCanUseKeyboard = true;
2308 if (!gKnowsYouCanUseKeyboard) {
2309 window.gKnowsYouCanUseKeyboardTimeout = setTimeout(function() {
2310 window.gKnowsYouCanUseKeyboardNotification = new Notification({
2311 title: "Did you know!?!",
2312 text: "You can play the piano with your keyboard, too. Try it!",
2313 target: "#piano",
2314 duration: 10000
2315 });
2316 }, 30000);
2317 }
2318
2319
2320
2321
2322
2323 if (window.localStorage) {
2324
2325
2326 if (localStorage.volume) {
2327 volume_slider.value = localStorage.volume;
2328 gPiano.audio.setVolume(localStorage.volume);
2329 $("#volume-label").text("Volume: " + Math.floor(gPiano.audio.volume * 100) + "%");
2330 }
2331 else localStorage.volume = gPiano.audio.volume;
2332
2333
2334 window.gHasBeenHereBefore = (localStorage.gHasBeenHereBefore || false);
2335 if (gHasBeenHereBefore) { }
2336 localStorage.gHasBeenHereBefore = true;
2337 } // warn user about loud noises before starting sound (no autoplay)
2338
2339 openModal("#sound-warning");
2340 var user_interact = function(evt) {
2341 document.removeEventListener("click", user_interact);
2342 closeModal();
2343 MPP.piano.audio.resume();
2344 }
2345 document.addEventListener("click", user_interact);
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359 // New room, change room
2360
2361 ////////////////////////////////////////////////////////////////
2362
2363
2364 $("#room > .info").text("--");
2365 gClient.on("ch", function(msg) {
2366 var channel = msg.ch;
2367 var info = $("#room > .info");
2368 info.text(channel._id);
2369 if (channel.settings.lobby) info.addClass("lobby");
2370 else info.removeClass("lobby");
2371 if (!channel.settings.chat) info.addClass("no-chat");
2372 else info.removeClass("no-chat");
2373 if (channel.settings.crownsolo) info.addClass("crownsolo");
2374 else info.removeClass("crownsolo");
2375 if (!channel.settings.visible) info.addClass("not-visible");
2376 else info.removeClass("not-visible");
2377 });
2378 gClient.on("ls", function(ls) {
2379 for (var i in ls.u) {
2380 if (!ls.u.hasOwnProperty(i)) continue;
2381 var room = ls.u[i];
2382 var info = $("#room .info[roomname=\"" + (room._id + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0') + "\"]");
2383 if (info.length == 0) {
2384 info = $("<div class=\"info\"></div>");
2385 info.attr("roomname", room._id);
2386 $("#room .more").append(info);
2387 }
2388 info.text(room._id + " (" + room.count + ")");
2389 if (room.settings.lobby) info.addClass("lobby");
2390 else info.removeClass("lobby");
2391 if (!room.settings.chat) info.addClass("no-chat");
2392 else info.removeClass("no-chat");
2393 if (room.settings.crownsolo) info.addClass("crownsolo");
2394 else info.removeClass("crownsolo");
2395 if (!room.settings.visible) info.addClass("not-visible");
2396 else info.removeClass("not-visible");
2397 if (room.banned) info.addClass("banned");
2398 else info.removeClass("banned");
2399 }
2400 });
2401 $("#room").on("click", function(evt) {
2402 evt.stopPropagation();
2403
2404 // clicks on a new room
2405
2406 if ($(evt.target).hasClass("info") && $(evt.target).parents(".more").length) {
2407 $("#room .more").fadeOut(250);
2408 var selected_name = $(evt.target).attr("roomname");
2409 if (typeof selected_name != "undefined") {
2410 changeRoom(selected_name, "right");
2411 }
2412 return false;
2413 } // clicks on "New Room..."
2414
2415 else if ($(evt.target).hasClass("new")) {
2416 openModal("#new-room", "input[name=name]");
2417 } // all other clicks
2418
2419 var doc_click = function(evt) {
2420 if ($(evt.target).is("#room .more")) return;
2421 $(document).off("mousedown", doc_click);
2422 $("#room .more").fadeOut(250);
2423 gClient.sendArray([{
2424 m: "-ls"
2425 }]);
2426 }
2427 $(document).on("mousedown", doc_click);
2428 $("#room .more .info").remove();
2429 $("#room .more").show();
2430 gClient.sendArray([{
2431 m: "+ls"
2432 }]);
2433 });
2434 $("#new-room-btn").on("click", function(evt) {
2435 evt.stopPropagation();
2436 openModal("#new-room", "input[name=name]");
2437 });
2438
2439
2440
2441 $("#play-alone-btn").on("click", function(evt) {
2442 evt.stopPropagation();
2443 var room_name = "Room" + Math.floor(Math.random() * 1000000000000);
2444 changeRoom(room_name, "right", {
2445 "visible": false,
2446 "chat": true,
2447 "crownsolo": false
2448 });
2449 setTimeout(function() {
2450 new Notification({
2451 id: "share",
2452 title: "Playing alone",
2453 html: 'You are playing alone in a room by yourself, but you can always invite \
2454 friends by sending them the link.<br/><br/>\
2455 <a href="#" onclick="window.open(\'https://www.facebook.com/sharer/sharer.php?u=\'+encodeURIComponent(location.href),\'facebook-share-dialog\',\'width=626,height=436\');return false;">Share on Facebook</a><br/><br/>\
2456 <a href="http://twitter.com/home?status=' + encodeURIComponent(location.href) + '" target="_blank">Tweet</a>',
2457 duration: 25000
2458 });
2459 }, 1000);
2460 });
2461
2462
2463
2464
2465 var gModal;
2466
2467
2468 function modalHandleEsc(evt) {
2469 if (evt.keyCode == 27) {
2470 closeModal();
2471 evt.preventDefault();
2472 evt.stopPropagation();
2473 }
2474 };
2475 function openModal(selector, focus) {
2476 if (chat) chat.blur();
2477 releaseKeyboard();
2478 $(document).on("keydown", modalHandleEsc);
2479 $("#modal #modals > *").hide();
2480 $("#modal").fadeIn(250);
2481 $(selector).show();
2482 setTimeout(function() {
2483 $(selector).find(focus).focus();
2484 }, 100);
2485 gModal = selector;
2486 };
2487
2488
2489 function closeModal() {
2490 $(document).off("keydown", modalHandleEsc);
2491 $("#modal").fadeOut(100);
2492 $("#modal #modals > *").hide();
2493 captureKeyboard();
2494 gModal = null;
2495 };
2496
2497
2498 var modal_bg = $("#modal .bg")[0];
2499 $(modal_bg).on("click", function(evt) {
2500 if (evt.target != modal_bg) return;
2501 closeModal();
2502 });
2503
2504
2505 (function() {
2506 function submit() {
2507 var name = $("#new-room .text[name=name]").val();
2508 var settings = {
2509 visible: $("#new-room .checkbox[name=visible]").is(":checked"),
2510 chat: true,
2511 crownsolo: false
2512 };
2513 $("#new-room .text[name=name]").val("");
2514 closeModal();
2515 changeRoom(name, "right", settings);
2516 setTimeout(function() {
2517 new Notification({
2518 id: "share",
2519 title: "Created a Room",
2520 html: 'You can invite friends to your room by sending them the link.<br/><br/>\
2521 <a href="#" onclick="window.open(\'https://www.facebook.com/sharer/sharer.php?u=\'+encodeURIComponent(location.href),\'facebook-share-dialog\',\'width=626,height=436\');return false;">Share on Facebook</a><br/><br/>\
2522 <a href="http://twitter.com/home?status=' + encodeURIComponent(location.href) + '" target="_blank">Tweet</a>',
2523 duration: 25000
2524 });
2525 }, 1000);
2526 };
2527 $("#new-room .submit").click(function(evt) {
2528 submit();
2529 });
2530 $("#new-room .text[name=name]").keypress(function(evt) {
2531 if (evt.keyCode == 13) {
2532 submit();
2533 } else if (evt.keyCode == 27) {
2534 closeModal();
2535 } else {
2536 return;
2537 }
2538 evt.preventDefault();
2539 evt.stopPropagation();
2540 return false;
2541 });
2542 })();
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552 function changeRoom(name, direction, settings, push) {
2553 if (!settings) settings = {};
2554 if (!direction) direction = "right";
2555 if (typeof push == "undefined") push = true;
2556 var opposite = direction == "left" ? "right" : "left";
2557
2558
2559 if (name == "") name = "lobby";
2560 if (gClient.channel && gClient.channel._id === name) return;
2561 if (push) {
2562 var url = "/" + encodeURIComponent(name).replace("'", "%27");
2563 if (window.history && history.pushState) {
2564 history.pushState({
2565 "depth": gHistoryDepth += 1,
2566 "name": name
2567 }, "Piano > " + name, url);
2568 } else {
2569 window.location = url;
2570 return;
2571 }
2572 }
2573 gClient.setChannel(name, settings);
2574
2575
2576 var t = 0,
2577 d = 100;
2578 $("#piano").addClass("ease-out").addClass("slide-" + opposite);
2579 setTimeout(function() {
2580 $("#piano").removeClass("ease-out").removeClass("slide-" + opposite).addClass("slide-" + direction);
2581 }, t += d);
2582 setTimeout(function() {
2583 $("#piano").addClass("ease-in").removeClass("slide-" + direction);
2584 }, t += d);
2585 setTimeout(function() {
2586 $("#piano").removeClass("ease-in");
2587 }, t += d);
2588 };
2589
2590
2591 var gHistoryDepth = 0;
2592 $(window).on("popstate", function(evt) {
2593 var depth = evt.state ? evt.state.depth : 0;
2594 if (depth == gHistoryDepth) return; // <-- forgot why I did that though...
2595
2596 var direction = depth <= gHistoryDepth ? "left" : "right";
2597 gHistoryDepth = depth;
2598
2599
2600 var name = decodeURIComponent(window.location.pathname);
2601 if (name.substr(0, 1) == "/") name = name.substr(1);
2602 changeRoom(name, direction, null, false);
2603 });
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624 // Rename
2625
2626 ////////////////////////////////////////////////////////////////
2627
2628 (function() {
2629 function submit() {
2630 var set = {
2631 name: $("#rename input[name=name]").val(),
2632 color: $("#rename input[name=color]").val()
2633 }; //$("#rename .text[name=name]").val("");
2634
2635 closeModal();
2636 gClient.sendArray([{
2637 m: "userset",
2638 set: set
2639 }]);
2640 };
2641 $("#rename .submit").click(function(evt) {
2642 submit();
2643 });
2644 $("#rename .text[name=name]").keypress(function(evt) {
2645 if (evt.keyCode == 13) {
2646 submit();
2647 } else if (evt.keyCode == 27) {
2648 closeModal();
2649 } else {
2650 return;
2651 }
2652 evt.preventDefault();
2653 evt.stopPropagation();
2654 return false;
2655 });
2656 })();
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672 // chatctor
2673
2674 ////////////////////////////////////////////////////////////////
2675
2676
2677 var chat = (function() {
2678 gClient.on("ch", function(msg) {
2679 if (msg.ch.settings.chat) {
2680 chat.show();
2681 } else {
2682 chat.hide();
2683 }
2684 });
2685 gClient.on("disconnect", function(msg) {
2686 chat.hide();
2687 });
2688 gClient.on("c", function(msg) {
2689 chat.clear();
2690 if (msg.c) {
2691 for (var i = 0; i < msg.c.length; i++) {
2692 chat.receive(msg.c[i]);
2693 }
2694 }
2695 });
2696 gClient.on("a", function(msg) {
2697 chat.receive(msg);
2698 });
2699
2700
2701 $("#chat input").on("focus", function(evt) {
2702 releaseKeyboard();
2703 $("#chat").addClass("chatting");
2704 chat.scrollToBottom();
2705 });
2706 /*$("#chat input").on("blur", function(evt) {
2707 captureKeyboard();
2708 $("#chat").removeClass("chatting");
2709 chat.scrollToBottom();
2710 });*/
2711
2712 $(document).mousedown(function(evt) {
2713 if (!$("#chat").has(evt.target).length > 0) {
2714 chat.blur();
2715 }
2716 });
2717 document.addEventListener("touchstart", function(event) {
2718 for (var i in event.changedTouches) {
2719 var touch = event.changedTouches[i];
2720 if (!$("#chat").has(touch.target).length > 0) {
2721 chat.blur();
2722 }
2723 }
2724 });
2725 $(document).on("keydown", function(evt) {
2726 if ($("#chat").hasClass("chatting")) {
2727 if (evt.keyCode == 27) {
2728 chat.blur();
2729 evt.preventDefault();
2730 evt.stopPropagation();
2731 } else if (evt.keyCode == 13) {
2732 $("#chat input").focus();
2733 }
2734 } else if (!gModal && (evt.keyCode == 27 || evt.keyCode == 13)) {
2735 $("#chat input").focus();
2736 }
2737 });
2738 $("#chat input").on("keydown", function(evt) {
2739 if (evt.keyCode == 13) {
2740 var message = $(this).val();
2741 if (message.length == 0) {
2742 setTimeout(function() {
2743 chat.blur();
2744 }, 100);
2745 } else if (message.length <= 512) {
2746 chat.send(message);
2747 $(this).val("");
2748 setTimeout(function() {
2749 chat.blur();
2750 }, 100);
2751 }
2752 evt.preventDefault();
2753 evt.stopPropagation();
2754 } else if (evt.keyCode == 27) {
2755 chat.blur();
2756 evt.preventDefault();
2757 evt.stopPropagation();
2758 } else if (evt.keyCode == 9) {
2759 evt.preventDefault();
2760 evt.stopPropagation();
2761 }
2762 });
2763
2764
2765 return {
2766 show: function() {
2767 $("#chat").fadeIn();
2768 },
2769
2770 hide: function() {
2771 $("#chat").fadeOut();
2772 },
2773
2774 clear: function() {
2775 $("#chat li").remove();
2776 },
2777
2778 scrollToBottom: function() {
2779 var ele = $("#chat ul").get(0);
2780 ele.scrollTop = ele.scrollHeight;
2781 },
2782
2783 blur: function() {
2784 if ($("#chat").hasClass("chatting")) {
2785 $("#chat input").get(0).blur();
2786 $("#chat").removeClass("chatting");
2787 chat.scrollToBottom();
2788 captureKeyboard();
2789 }
2790 },
2791
2792 send: function(message) {
2793 gClient.sendArray([{
2794 m: "a",
2795 message: message
2796 }]);
2797 },
2798
2799 receive: function(msg) {
2800 if (gChatMutes.indexOf(msg.p._id) != -1) return;
2801
2802
2803 var li = $('<li><span class="name"/><span class="message"/>');
2804
2805
2806 li.find(".name").text(msg.p.name + ":");
2807 li.find(".message").text(msg.a);
2808 li.css("color", msg.p.color || "white");
2809
2810
2811 $("#chat ul").append(li);
2812
2813
2814 var eles = $("#chat ul li").get();
2815 for (var i = 1; i <= 50 && i <= eles.length; i++) {
2816 eles[eles.length - i].style.opacity = 1.0 - (i * 0.03);
2817 }
2818 if (eles.length > 50) {
2819 eles[0].style.display = "none";
2820 }
2821 if (eles.length > 256) {
2822 $(eles[0]).remove();
2823 }
2824
2825 // scroll to bottom if not "chatting" or if not scrolled up
2826
2827 if (!$("#chat").hasClass("chatting")) {
2828 chat.scrollToBottom();
2829 } else {
2830 var ele = $("#chat ul").get(0);
2831 if (ele.scrollTop > ele.scrollHeight - ele.offsetHeight - 50) chat.scrollToBottom();
2832 }
2833 }
2834 };
2835 })();
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850 // MIDI
2851
2852 ////////////////////////////////////////////////////////////////
2853
2854
2855 var MIDI_TRANSPOSE = -12;
2856 var MIDI_KEY_NAMES = ["a-1", "as-1", "b-1"];
2857 var bare_notes = "c cs d ds e f fs g gs a as b".split(" ");
2858 for (var oct = 0; oct < 7; oct++) {
2859 for (var i in bare_notes) {
2860 MIDI_KEY_NAMES.push(bare_notes[i] + oct);
2861 }
2862 }
2863 MIDI_KEY_NAMES.push("c7");
2864
2865
2866 var devices_json;
2867 function sendDevices() {
2868 gClient.sendArray([{
2869 "m": "devices",
2870 "list": JSON.parse(devices_json)
2871 }]);
2872 }
2873 gClient.on("connect", sendDevices);
2874
2875
2876 (function() {
2877
2878
2879 if (navigator.requestMIDIAccess) {
2880 navigator.requestMIDIAccess().then( function(midi) {
2881 console.log(midi);
2882 function midimessagehandler(evt) {
2883 if (!evt.target.enabled) return; //console.log(evt);
2884
2885 var channel = evt.data[0] & 0xf;
2886 var cmd = evt.data[0] >> 4;
2887 var note_number = evt.data[1];
2888 var vel = evt.data[2]; //console.log(channel, cmd, note_number, vel);
2889
2890 if (cmd == 8 || (cmd == 9 && vel == 0)) { // NOTE_OFF
2891
2892 release(MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE]);
2893 } else if (cmd == 9) { // NOTE_ON
2894
2895 press(MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE], vel / 100);
2896 } else if (cmd == 11) { // CONTROL_CHANGE
2897
2898 if (!gAutoSustain) {
2899 if (note_number == 64) {
2900 if (vel > 0) {
2901 pressSustain();
2902 } else {
2903 releaseSustain();
2904 }
2905 }
2906 }
2907 }
2908 }
2909
2910
2911 function deviceInfo(dev) {
2912 return {
2913 type: dev.type,
2914 //id: dev.id,
2915 manufacturer: dev.manufacturer,
2916 name: dev.name,
2917 version: dev.version,
2918 //connection: dev.connection,
2919 //state: dev.state,
2920 enabled: dev.enabled
2921 };
2922 }
2923
2924
2925 function updateDevices() {
2926 var list = [];
2927 if (midi.inputs.size > 0) {
2928 var inputs = midi.inputs.values();
2929 for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
2930 var input = input_it.value;
2931 list.push(deviceInfo(input));
2932 }
2933 }
2934 if (midi.outputs.size > 0) {
2935 var outputs = midi.outputs.values();
2936 for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
2937 var output = output_it.value;
2938 list.push(deviceInfo(output));
2939 }
2940 }
2941 var new_json = JSON.stringify(list);
2942 if (new_json !== devices_json) {
2943 devices_json = new_json;
2944 sendDevices();
2945 }
2946 }
2947
2948
2949 function plug() {
2950 if (midi.inputs.size > 0) {
2951 var inputs = midi.inputs.values();
2952 for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
2953 var input = input_it.value; //input.removeEventListener("midimessage", midimessagehandler);
2954 //input.addEventListener("midimessage", midimessagehandler);
2955
2956 input.onmidimessage = midimessagehandler;
2957 if (input.enabled !== false) {
2958 input.enabled = true;
2959 }
2960 console.log("input", input);
2961 }
2962 }
2963 if (midi.outputs.size > 0) {
2964 var outputs = midi.outputs.values();
2965 for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
2966 var output = output_it.value; //output.enabled = false; // edit: don't touch
2967
2968 console.log("output", output);
2969 }
2970 gMidiOutTest = function(note_name, vel, delay_ms) {
2971 var note_number = MIDI_KEY_NAMES.indexOf(note_name);
2972 if (note_number == -1) return;
2973 note_number = note_number + 9 - MIDI_TRANSPOSE;
2974
2975
2976 var outputs = midi.outputs.values();
2977 for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
2978 var output = output_it.value;
2979 if (output.enabled) {
2980 output.send([0x90, note_number, vel], window.performance.now() + delay_ms);
2981 }
2982 }
2983 }
2984 }
2985 showConnections(false);
2986 updateDevices();
2987 }
2988
2989
2990 midi.addEventListener("statechange", function(evt) {
2991 if (evt instanceof MIDIConnectionEvent) {
2992 plug();
2993 }
2994 });
2995
2996
2997 plug();
2998
2999
3000
3001 var connectionsNotification;
3002
3003
3004 function showConnections(sticky) { //if(document.getElementById("Notification-MIDI-Connections"))
3005 //sticky = 1; // todo: instead,
3006
3007 var inputs_ul = document.createElement("ul");
3008 if (midi.inputs.size > 0) {
3009 var inputs = midi.inputs.values();
3010 for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
3011 var input = input_it.value;
3012 var li = document.createElement("li");
3013 li.connectionId = input.id;
3014 li.classList.add("connection");
3015 if (input.enabled) li.classList.add("enabled");
3016 li.textContent = input.name;
3017 li.addEventListener("click", function(evt) {
3018 var inputs = midi.inputs.values();
3019 for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
3020 var input = input_it.value;
3021 if (input.id === evt.target.connectionId) {
3022 input.enabled = !input.enabled;
3023 evt.target.classList.toggle("enabled");
3024 console.log("click", input);
3025 updateDevices();
3026 return;
3027 }
3028 }
3029 });
3030 inputs_ul.appendChild(li);
3031 }
3032 } else {
3033 inputs_ul.textContent = "(none)";
3034 }
3035 var outputs_ul = document.createElement("ul");
3036 if (midi.outputs.size > 0) {
3037 var outputs = midi.outputs.values();
3038 for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
3039 var output = output_it.value;
3040 var li = document.createElement("li");
3041 li.connectionId = output.id;
3042 li.classList.add("connection");
3043 if (output.enabled) li.classList.add("enabled");
3044 li.textContent = output.name;
3045 li.addEventListener("click", function(evt) {
3046 var outputs = midi.outputs.values();
3047 for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
3048 var output = output_it.value;
3049 if (output.id === evt.target.connectionId) {
3050 output.enabled = !output.enabled;
3051 evt.target.classList.toggle("enabled");
3052 console.log("click", output);
3053 updateDevices();
3054 return;
3055 }
3056 }
3057 });
3058 outputs_ul.appendChild(li);
3059 }
3060 } else {
3061 outputs_ul.textContent = "(none)";
3062 }
3063 var div = document.createElement("div");
3064 var h1 = document.createElement("h1");
3065 h1.textContent = "Inputs";
3066 div.appendChild(h1);
3067 div.appendChild(inputs_ul);
3068 h1 = document.createElement("h1");
3069 h1.textContent = "Outputs";
3070 div.appendChild(h1);
3071 div.appendChild(outputs_ul);
3072 connectionsNotification = new Notification({
3073 "id": "MIDI-Connections",
3074 "title": "MIDI Connections",
3075 "duration": sticky ? "-1" : "4500",
3076 "html": div,
3077 "target": "#midi-btn"
3078 });
3079 }
3080
3081
3082 document.getElementById("midi-btn").addEventListener("click", function(evt) {
3083 if (!document.getElementById("Notification-MIDI-Connections")) showConnections(true);
3084 else {
3085 connectionsNotification.close();
3086 }
3087 });
3088 }, function(err) {
3089 console.log(err);
3090 });
3091 }
3092 })();
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107 // bug supply
3108
3109 ////////////////////////////////////////////////////////////////
3110
3111 window.onerror = function(message, url, line) {
3112 var url = url || "(no url)";
3113 var line = line || "(no line)"; // errors in socket.io
3114
3115 if (url.indexOf("socket.io.js") !== -1) {
3116 if (message.indexOf("INVALID_STATE_ERR") !== -1) return;
3117 if (message.indexOf("InvalidStateError") !== -1) return;
3118 if (message.indexOf("DOM Exception 11") !== -1) return;
3119 if (message.indexOf("Property 'open' of object #<c> is not a function") !== -1) return;
3120 if (message.indexOf("Cannot call method 'close' of undefined") !== -1) return;
3121 if (message.indexOf("Cannot call method 'close' of null") !== -1) return;
3122 if (message.indexOf("Cannot call method 'onClose' of null") !== -1) return;
3123 if (message.indexOf("Cannot call method 'payload' of null") !== -1) return;
3124 if (message.indexOf("Unable to get value of the property 'close'") !== -1) return;
3125 if (message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) return;
3126 if (message.indexOf("Unable to get property 'close' of undefined or null reference") !== -1) return;
3127 if (message.indexOf("Unable to get value of the property 'close': object is null or undefined") !== -1) return;
3128 if (message.indexOf("this.transport is null") !== -1) return;
3129 } // errors in soundmanager2
3130
3131 if (url.indexOf("soundmanager2.js") !== -1) { // operation disabled in safe mode?
3132
3133 if (message.indexOf("Could not complete the operation due to error c00d36ef") !== -1) return;
3134 if (message.indexOf("_s.o._setVolume is not a function") !== -1) return;
3135 } // errors in midibridge
3136
3137 if (url.indexOf("midibridge") !== -1) {
3138 if (message.indexOf("Error calling method on NPObject") !== -1) return;
3139 } // too many failing extensions injected in my html
3140
3141 if (url.indexOf(".js") !== url.length - 3) return; // extensions inject cross-domain embeds too
3142
3143 if (url.toLowerCase().indexOf("multiplayerpiano.com") == -1) return;
3144
3145 // errors in my code
3146
3147 if (url.indexOf("script.js") !== -1) {
3148 if (message.indexOf("Object [object Object] has no method 'on'") !== -1) return;
3149 if (message.indexOf("Object [object Object] has no method 'off'") !== -1) return;
3150 if (message.indexOf("Property '$' of object [object Object] is not a function") !== -1) return;
3151 }
3152
3153
3154 var enc = "/bugreport/" + (message ? encodeURIComponent(message) : "") + "/" + (url ? encodeURIComponent(url) : "") + "/" + (line ? encodeURIComponent(line) : "");
3155 var img = new Image();
3156 img.src = enc;
3157 };
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167 // API
3168
3169 window.MPP = {
3170 press: press,
3171 release: release,
3172 piano: gPiano,
3173 client: gClient,
3174 chat: chat,
3175 noteQuota: gNoteQuota,
3176 soundSelector: gSoundSelector
3177 };
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188 // record mp3
3189
3190 (function() {
3191 var button = document.querySelector("#record-btn");
3192 var audio = MPP.piano.audio;
3193 var context = audio.context;
3194 var encoder_sample_rate = 44100;
3195 var encoder_kbps = 128;
3196 var encoder = null;
3197 var scriptProcessorNode = context.createScriptProcessor(4096, 2, 2);
3198 var recording = false;
3199 var recording_start_time = 0;
3200 var mp3_buffer = [];
3201 button.addEventListener("click", function(evt) {
3202 if (!recording) { // start recording
3203
3204 mp3_buffer = [];
3205 encoder = new lamejs.Mp3Encoder(2, encoder_sample_rate, encoder_kbps);
3206 scriptProcessorNode.onaudioprocess = onAudioProcess;
3207 audio.masterGain.connect(scriptProcessorNode);
3208 scriptProcessorNode.connect(context.destination);
3209 recording_start_time = Date.now();
3210 recording = true;
3211 button.textContent = "Stop Recording";
3212 button.classList.add("stuck");
3213 new Notification({
3214 "id": "mp3",
3215 "title": "Recording MP3...",
3216 "html": "It's recording now. This could make things slow, maybe. Maybe give it a moment to settle before playing.<br><br>This feature is experimental.<br>Send complaints to <a href=\"mailto:multiplayerpiano.com@gmail.com\">multiplayerpiano.com@gmail.com</a>.",
3217 "duration": 10000
3218 });
3219 } else { // stop recording
3220
3221 var mp3buf = encoder.flush();
3222 mp3_buffer.push(mp3buf);
3223 var blob = new Blob(mp3_buffer, {
3224 type: "audio/mp3"
3225 });
3226 var url = URL.createObjectURL(blob);
3227 scriptProcessorNode.onaudioprocess = null;
3228 audio.masterGain.disconnect(scriptProcessorNode);
3229 scriptProcessorNode.disconnect(context.destination);
3230 recording = false;
3231 button.textContent = "Record MP3";
3232 button.classList.remove("stuck");
3233 new Notification({
3234 "id": "mp3",
3235 "title": "MP3 recording finished",
3236 "html": "<a href=\"" + url + "\" target=\"blank\">And here it is!</a> (open or save as)<br><br>This feature is experimental.<br>Send complaints to <a href=\"mailto:multiplayerpiano.com@gmail.com\">multiplayerpiano.com@gmail.com</a>.",
3237 "duration": 0
3238 });
3239 }
3240 });
3241 function onAudioProcess(evt) {
3242 var inputL = evt.inputBuffer.getChannelData(0);
3243 var inputR = evt.inputBuffer.getChannelData(1);
3244 var mp3buf = encoder.encodeBuffer(convert16(inputL), convert16(inputR));
3245 mp3_buffer.push(mp3buf);
3246 }
3247 function convert16(samples) {
3248 var len = samples.length;
3249 var result = new Int16Array(len);
3250 for (var i = 0; i < len; i++) {
3251 result[i] = 0x8000 * samples[i];
3252 }
3253 return (result);
3254 }
3255 })();
3256
3257
3258
3259
3260
3261
3262
3263 // synth
3264
3265 var enableSynth = false;
3266 var audio = gPiano.audio;
3267 var context = gPiano.audio.context;
3268 var synth_gain = context.createGain();
3269 synth_gain.gain.value = 0.05;
3270 synth_gain.connect(audio.synthGain);
3271
3272
3273 var osc_types = ["sine", "square", "sawtooth", "triangle"];
3274 var osc_type_index = 1;
3275
3276
3277 var osc1_type = "square";
3278 var osc1_attack = 0;
3279 var osc1_decay = 0.2;
3280 var osc1_sustain = 0.5;
3281 var osc1_release = 2.0;
3282
3283
3284 function synthVoice(note_name, time) {
3285 var note_number = MIDI_KEY_NAMES.indexOf(note_name);
3286 note_number = note_number + 9 - MIDI_TRANSPOSE;
3287 var freq = Math.pow(2, (note_number - 69) / 12) * 440.0;
3288 this.osc = context.createOscillator();
3289 this.osc.type = osc1_type;
3290 this.osc.frequency.value = freq;
3291 this.gain = context.createGain();
3292 this.gain.gain.value = 0;
3293 this.osc.connect(this.gain);
3294 this.gain.connect(synth_gain);
3295 this.osc.start(time);
3296 this.gain.gain.setValueAtTime(0, time);
3297 this.gain.gain.linearRampToValueAtTime(1, time + osc1_attack);
3298 this.gain.gain.linearRampToValueAtTime(osc1_sustain, time + osc1_attack + osc1_decay);
3299 }
3300
3301
3302 synthVoice.prototype.stop = function(time) { //this.gain.gain.setValueAtTime(osc1_sustain, time);
3303
3304 this.gain.gain.linearRampToValueAtTime(0, time + osc1_release);
3305 this.osc.stop(time + osc1_release);
3306 };
3307
3308
3309 (function() {
3310 var button = document.getElementById("synth-btn");
3311 var notification;
3312
3313
3314 button.addEventListener("click", function() {
3315 if (notification) {
3316 notification.close();
3317 } else {
3318 showSynth();
3319 }
3320 });
3321
3322
3323 function showSynth() {
3324
3325
3326 var html = document.createElement("div");
3327
3328 // on/off button
3329
3330 (function() {
3331 var button = document.createElement("input");
3332 mixin(button, {
3333 type: "button",
3334 value: "ON/OFF",
3335 className: enableSynth ? "switched-on" : "switched-off"
3336 });
3337 button.addEventListener("click", function(evt) {
3338 enableSynth = !enableSynth;
3339 button.className = enableSynth ? "switched-on" : "switched-off";
3340 if (!enableSynth) { // stop all
3341
3342 for (var i in audio.playings) {
3343 if (!audio.playings.hasOwnProperty(i)) continue;
3344 var playing = audio.playings[i];
3345 if (playing && playing.voice) {
3346 playing.voice.osc.stop();
3347 playing.voice = undefined;
3348 }
3349 }
3350 }
3351 });
3352 html.appendChild(button);
3353 })();
3354
3355 // mix
3356
3357 var knob = document.createElement("canvas");
3358 mixin(knob, {
3359 width: 32 * window.devicePixelRatio,
3360 height: 32 * window.devicePixelRatio,
3361 className: "knob"
3362 });
3363 html.appendChild(knob);
3364 knob = new Knob(knob, 0, 100, 0.1, 50, "mix", "%");
3365 knob.canvas.style.width = "32px";
3366 knob.canvas.style.height = "32px";
3367 knob.on("change", function(k) {
3368 var mix = k.value / 100;
3369 audio.pianoGain.gain.value = 1 - mix;
3370 audio.synthGain.gain.value = mix;
3371 });
3372 knob.emit("change", knob);
3373
3374 // osc1 type
3375
3376 (function() {
3377 osc1_type = osc_types[osc_type_index];
3378 var button = document.createElement("input");
3379 mixin(button, {
3380 type: "button",
3381 value: osc_types[osc_type_index]
3382 });
3383 button.addEventListener("click", function(evt) {
3384 if (++osc_type_index >= osc_types.length) osc_type_index = 0;
3385 osc1_type = osc_types[osc_type_index];
3386 button.value = osc1_type;
3387 });
3388 html.appendChild(button);
3389 })();
3390
3391 // osc1 attack
3392
3393 var knob = document.createElement("canvas");
3394 mixin(knob, {
3395 width: 32 * window.devicePixelRatio,
3396 height: 32 * window.devicePixelRatio,
3397 className: "knob"
3398 });
3399 html.appendChild(knob);
3400 knob = new Knob(knob, 0, 1, 0.001, osc1_attack, "osc1 attack", "s");
3401 knob.canvas.style.width = "32px";
3402 knob.canvas.style.height = "32px";
3403 knob.on("change", function(k) {
3404 osc1_attack = k.value;
3405 });
3406 knob.emit("change", knob);
3407
3408 // osc1 decay
3409
3410 var knob = document.createElement("canvas");
3411 mixin(knob, {
3412 width: 32 * window.devicePixelRatio,
3413 height: 32 * window.devicePixelRatio,
3414 className: "knob"
3415 });
3416 html.appendChild(knob);
3417 knob = new Knob(knob, 0, 2, 0.001, osc1_decay, "osc1 decay", "s");
3418 knob.canvas.style.width = "32px";
3419 knob.canvas.style.height = "32px";
3420 knob.on("change", function(k) {
3421 osc1_decay = k.value;
3422 });
3423 knob.emit("change", knob);
3424
3425
3426 var knob = document.createElement("canvas");
3427 mixin(knob, {
3428 width: 32 * window.devicePixelRatio,
3429 height: 32 * window.devicePixelRatio,
3430 className: "knob"
3431 });
3432 html.appendChild(knob);
3433 knob = new Knob(knob, 0, 1, 0.001, osc1_sustain, "osc1 sustain", "x");
3434 knob.canvas.style.width = "32px";
3435 knob.canvas.style.height = "32px";
3436 knob.on("change", function(k) {
3437 osc1_sustain = k.value;
3438 });
3439 knob.emit("change", knob);
3440
3441 // osc1 release
3442
3443 var knob = document.createElement("canvas");
3444 mixin(knob, {
3445 width: 32 * window.devicePixelRatio,
3446 height: 32 * window.devicePixelRatio,
3447 className: "knob"
3448 });
3449 html.appendChild(knob);
3450 knob = new Knob(knob, 0, 2, 0.001, osc1_release, "osc1 release", "s");
3451 knob.canvas.style.width = "32px";
3452 knob.canvas.style.height = "32px";
3453 knob.on("change", function(k) {
3454 osc1_release = k.value;
3455 });
3456 knob.emit("change", knob);
3457
3458
3459
3460
3461 var div = document.createElement("div");
3462 div.innerHTML = "<br><br><br><br><center>this space intentionally left blank</center><br><br><br><br>";
3463 html.appendChild(div);
3464
3465
3466
3467 // notification
3468
3469 notification = new Notification({
3470 title: "Synthesize",
3471 html: html,
3472 duration: -1,
3473 target: "#synth-btn"
3474 });
3475 notification.on("close", function() {
3476 var tip = document.getElementById("tooltip");
3477 if (tip) tip.parentNode.removeChild(tip);
3478 notification = null;
3479 });
3480 }
3481 })();
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497});
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517// misc
3518
3519////////////////////////////////////////////////////////////////
3520
3521// analytics
3522window.google_analytics_uacct = "UA-882009-7";
3523var _gaq = _gaq || [];
3524_gaq.push(['_setAccount', 'UA-882009-7']);
3525_gaq.push(['_trackPageview']);
3526_gaq.push(['_setAllowAnchor', true]);
3527(function() {
3528 var ga = document.createElement('script');
3529 ga.type = 'text/javascript';
3530 ga.async = true;
3531 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
3532 var s = document.getElementsByTagName('script')[0];
3533 s.parentNode.insertBefore(ga, s);
3534})();
3535
3536// twitter
3537! function(d, s, id) {
3538 var js, fjs = d.getElementsByTagName(s)[0];
3539 if (!d.getElementById(id)) {
3540 js = d.createElement(s);
3541 js.id = id;
3542 js.src = "//platform.twitter.com/widgets.js";
3543 fjs.parentNode.insertBefore(js, fjs);
3544 }
3545}(document, "script", "twitter-wjs");
3546
3547// fb
3548(function(d, s, id) {
3549 var js, fjs = d.getElementsByTagName(s)[0];
3550 if (d.getElementById(id)) return;
3551 js = d.createElement(s);
3552 js.id = id;
3553 js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8";
3554 fjs.parentNode.insertBefore(js, fjs);
3555}(document, 'script', 'facebook-jssdk'));
3556
3557// non-ad-free experience
3558/*(function() {
3559 function adsOn() {
3560 if(window.localStorage) {
3561 var div = document.querySelector("#inclinations");
3562 div.innerHTML = "Ads:<br>ON / <a id=\"adsoff\" href=\"#\">OFF</a>";
3563 div.querySelector("#adsoff").addEventListener("click", adsOff);
3564 localStorage.ads = true;
3565 }
3566 // adsterra
3567 var script = document.createElement("script");
3568 script.src = "//pl132070.puhtml.com/68/7a/97/687a978dd26d579c788cb41e352f5a41.js";
3569 document.head.appendChild(script);
3570 }
3571
3572 function adsOff() {
3573 if(window.localStorage) localStorage.ads = false;
3574 document.location.reload(true);
3575 }
3576
3577 function noAds() {
3578 var div = document.querySelector("#inclinations");
3579 div.innerHTML = "Ads:<br><a id=\"adson\" href=\"#\">ON</a> / OFF";
3580 div.querySelector("#adson").addEventListener("click", adsOn);
3581 }
3582
3583 if(window.localStorage) {
3584 if(localStorage.ads === undefined || localStorage.ads === "true")
3585 adsOn();
3586 else
3587 noAds();
3588 } else {
3589 adsOn();
3590 }
3591})();*/