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