// 钢琴 $(function () { console.log("%cWelcome to MPP's developer console!", "color:blue; font-size:20px;"); console.log("%cCheck out the source code: https://github.com/LapisHusky/mppclone/tree/main/client\nGuide for coders and bot developers: https://docs.google.com/document/d/1OrxwdLD1l1TE8iau6ToETVmnLuLXyGBhA0VfAY1Lf14/edit?usp=sharing", "color:gray; font-size:12px;"); var test_mode = (window.location.hash && window.location.hash.match(/^(?:#.+)*#test(?:#.+)*$/i)); var gSeeOwnCursor = (window.location.hash && window.location.hash.match(/^(?:#.+)*#seeowncursor(?:#.+)*$/i)); var gMidiVolumeTest = (window.location.hash && window.location.hash.match(/^(?:#.+)*#midivolumetest(?:#.+)*$/i)); var gMidiOutTest; if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (elt /*, from*/) { var len = this.length >>> 0; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) from += len; for (; from < len; from++) { if (from in this && this[from] === elt) return from; } return -1; }; } window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function (cb) { setTimeout(cb, 1000 / 30); }; var DEFAULT_VELOCITY = 0.5; var TIMING_TARGET = 1000; // Utility //////////////////////////////////////////////////////////////// var Rect = function (x, y, w, h) { this.x = x; this.y = y; this.w = w; this.h = h; this.x2 = x + w; this.y2 = y + h; }; Rect.prototype.contains = function (x, y) { return (x >= this.x && x <= this.x2 && y >= this.y && y <= this.y2); }; const BASIC_PIANO_SCALES = { // ty https://www.pianoscales.org/ // major keys "Notes in C Major" : ["C", "D", "E", "F", "G", "A", "B", "C"], "Notes in D Major" : ["D", "E", "G♭", "G", "A", "B", "D♭", "D"], "Notes in E Major" : ["E", "G♭", "A♭", "A", "B", "D♭", "E♭", "E"], "Notes in F Major" : ["F", "G", "A", "B♭", "C", "D", "E", "F"], "Notes in G Major" : ["G", "A", "B", "C", "D", "E", "G♭", "G"], "Notes in A Major" : ["A", "B", "D♭", "D", "E", "G♭", "A♭", "A"], "Notes in B Major" : ["B", "D♭", "E♭", "E", "G♭", "A♭", "B♭", "B"], "Notes in C# / Db Major" : ["D♭", "E♭", "F", "G♭", "A♭", "B♭", "C", "D♭"], "Notes in D# / Eb Major" : ["E♭", "F", "G", "A♭", "B♭", "C", "D", "E♭"], "Notes in F# / Gb Major" : ["G♭", "A♭", "B♭", "B", "D♭", "E♭", "F", "G♭"], "Notes in G# / Ab Major" : ["A♭", "B♭", "C", "D♭", "E♭", "F", "G", "A♭"], "Notes in A# / Bb Major" : ["B♭", "C", "D", "E♭", "F", "G", "A", "B♭"], // natural minor keys "Notes in A Minor" : ["A", "B", "C", "D", "E", "F", "G", "A"], "Notes in A# / Bb Minor" : ["B♭", "C", "D♭", "E♭", "F", "G♭", "A♭", "B♭"], "Notes in B Minor" : ["B", "D♭", "D", "E", "G♭", "G", "A", "B"], "Notes in C Minor" : ["C", "D", "E♭", "F", "G", "A♭", "B♭", "C"], "Notes in C# / Db Minor" : ["D♭", "E♭", "E", "G♭", "A♭", "A", "B", "D♭"], "Notes in D Minor" : ["D", "E", "F", "G", "A", "B♭", "C", "D"], "Notes in D# / Eb Minor" : ["E♭", "F", "G♭", "A♭", "B♭", "B", "D♭", "E♭"], "Notes in E Minor" : ["E", "G♭", "G", "A", "B", "C", "D", "E"], "Notes in F Minor" : ["F", "G", "A♭", "B♭", "C", "D♭", "E♭", "F"], "Notes in F# / Gb Minor" : ["G♭", "A♭", "A", "B", "D♭", "D", "E", "G♭"], "Notes in G Minor" : ["G", "A", "B♭", "C", "D", "E♭", "F", "G"], "Notes in G# / Ab Minor" : ["A♭", "B♭", "B", "D♭", "E♭", "E", "G♭", "A♭"], }; // performing translation //////////////////////////////////////////////////////////////// var Translation = (function () { var strings = { "people are playing": { "pt": "pessoas estão jogando", "es": "personas están jugando", "ru": "человек играет", "fr": "personnes jouent", "ja": "人が遊んでいる", "de": "Leute spielen", "zh": "人在玩", "nl": "mensen spelen", "pl": "osób grają", "hu": "ember játszik" }, "New Room...": { "pt": "Nova Sala ...", "es": "Nueva sala de...", "ru": "Новая комната...", "ja": "新しい部屋", "zh": "新房间", "nl": "nieuwe Kamer", "hu": "új szoba" }, "room name": { "pt": "nome da sala", "es": "sala de nombre", "ru": "название комнаты", "fr": "nom de la chambre", "ja": "ルーム名", "de": "Raumnamen", "zh": "房间名称", "nl": "kamernaam", "pl": "nazwa pokój", "hu": "szoba neve" }, "Visible (open to everyone)": { "pt": "Visível (aberto a todos)", "es": "Visible (abierto a todo el mundo)", "ru": "Visible (открытый для всех)", "fr": "Visible (ouvert à tous)", "ja": "目に見える(誰にでも開いている)", "de": "Sichtbar (offen für alle)", "zh": "可见(向所有人开放)", "nl": "Zichtbaar (open voor iedereen)", "pl": "Widoczne (otwarte dla wszystkich)", "hu": "Látható (nyitott mindenki számára)" }, "Enable Chat": { "pt": "Ativar bate-papo", "es": "Habilitar chat", "ru": "Включить чат", "fr": "Activer discuter", "ja": "チャットを有効にする", "de": "aktivieren Sie chatten", "zh": "启用聊天", "nl": "Chat inschakelen", "pl": "Włącz czat", "hu": "a csevegést" }, "Play Alone": { "pt": "Jogar Sozinho", "es": "Jugar Solo", "ru": "Играть в одиночку", "fr": "Jouez Seul", "ja": "一人でプレイ", "de": "Alleine Spielen", "zh": "独自玩耍", "nl": "Speel Alleen", "pl": "Zagraj sam", "hu": "Játssz egyedül" } // todo: it, tr, th, sv, ar, fi, nb, da, sv, he, cs, ko, ro, vi, id, nb, el, sk, bg, lt, sl, hr // todo: Connecting, Offline mode, input placeholder, Notifications }; var setLanguage = function (lang) { language = lang }; var getLanguage = function () { if (window.navigator && navigator.language && navigator.language.length >= 2) { return navigator.language.substr(0, 2).toLowerCase(); } else { return "en"; } }; var get = function (text, lang) { if (typeof lang === "undefined") lang = language; var row = strings[text]; if (row == undefined) return text; var string = row[lang]; if (string == undefined) return text; return string; }; var perform = function (lang) { if (typeof lang === "undefined") lang = language; $(".translate").each(function (i, ele) { var th = $(this); if (ele.tagName && ele.tagName.toLowerCase() == "input") { if (typeof ele.placeholder != "undefined") { th.attr("placeholder", get(th.attr("placeholder"), lang)) } } else { th.text(get(th.text(), lang)); } }); }; var language = getLanguage(); return { setLanguage: setLanguage, getLanguage: getLanguage, get: get, perform: perform }; })(); Translation.perform(); // AudioEngine classes //////////////////////////////////////////////////////////////// var AudioEngine = function () { }; AudioEngine.prototype.init = function (cb) { this.volume = 0.6; this.sounds = {}; this.paused = true; return this; }; AudioEngine.prototype.load = function (id, url, cb) { }; AudioEngine.prototype.play = function () { }; AudioEngine.prototype.stop = function () { }; AudioEngine.prototype.setVolume = function (vol) { this.volume = vol; }; AudioEngine.prototype.resume = function () { this.paused = false; }; AudioEngineWeb = function () { this.threshold = 0; this.worker = new Worker("/workerTimer.js"); var self = this; this.worker.onmessage = function (event) { if (event.data.args) if (event.data.args.action == 0) { self.actualPlay(event.data.args.id, event.data.args.vol, event.data.args.time, event.data.args.part_id); } else { self.actualStop(event.data.args.id, event.data.args.time, event.data.args.part_id); } } }; AudioEngineWeb.prototype = new AudioEngine(); AudioEngineWeb.prototype.init = function (cb) { AudioEngine.prototype.init.call(this); this.context = new AudioContext({ latencyHint: 'interactive' }); this.masterGain = this.context.createGain(); this.masterGain.connect(this.context.destination); this.masterGain.gain.value = this.volume; this.limiterNode = this.context.createDynamicsCompressor(); this.limiterNode.threshold.value = -10; this.limiterNode.knee.value = 0; this.limiterNode.ratio.value = 20; this.limiterNode.attack.value = 0; this.limiterNode.release.value = 0.1; this.limiterNode.connect(this.masterGain); // for synth mix this.pianoGain = this.context.createGain(); this.pianoGain.gain.value = 0.5; this.pianoGain.connect(this.limiterNode); this.synthGain = this.context.createGain(); this.synthGain.gain.value = 0.5; this.synthGain.connect(this.limiterNode); this.playings = {}; if (cb) setTimeout(cb, 0); return this; }; AudioEngineWeb.prototype.load = function (id, url, cb) { var audio = this; var req = new XMLHttpRequest(); req.open("GET", url); req.responseType = "arraybuffer"; req.addEventListener("readystatechange", function (evt) { if (req.readyState !== 4) return; try { audio.context.decodeAudioData(req.response, function (buffer) { audio.sounds[id] = buffer; if (cb) cb(); }); } catch (e) { /*throw new Error(e.message + " / id: " + id + " / url: " + url + " / status: " + req.status + " / ArrayBuffer: " + (req.response instanceof ArrayBuffer) + " / byteLength: " + (req.response && req.response.byteLength ? req.response.byteLength : "undefined"));*/ new Notification({ id: "audio-download-error", title: "Problem", text: "For some reason, an audio download failed with a status of " + req.status + ". ", target: "#piano", duration: 10000 }); } }); req.send(); }; AudioEngineWeb.prototype.actualPlay = function (id, vol, time, part_id) { //the old play(), but with time insted of delay_ms. if (this.paused) return; if (!this.sounds.hasOwnProperty(id)) return; var source = this.context.createBufferSource(); source.buffer = this.sounds[id]; var gain = this.context.createGain(); gain.gain.value = vol; source.connect(gain); gain.connect(this.pianoGain); source.start(time); // Patch from ste-art remedies stuttering under heavy load if (this.playings[id]) { var playing = this.playings[id]; playing.gain.gain.setValueAtTime(playing.gain.gain.value, time); playing.gain.gain.linearRampToValueAtTime(0.0, time + 0.2); playing.source.stop(time + 0.21); if (enableSynth && playing.voice) { playing.voice.stop(time); } } this.playings[id] = { "source": source, "gain": gain, "part_id": part_id }; if (enableSynth) { this.playings[id].voice = new synthVoice(id, time); } } AudioEngineWeb.prototype.play = function (id, vol, delay_ms, part_id) { if (!this.sounds.hasOwnProperty(id)) return; var time = this.context.currentTime + (delay_ms / 1000); //calculate time on note receive. var delay = delay_ms - this.threshold; if (delay <= 0) this.actualPlay(id, vol, time, part_id); else { 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. } } AudioEngineWeb.prototype.actualStop = function (id, time, part_id) { if (this.playings.hasOwnProperty(id) && this.playings[id] && this.playings[id].part_id === part_id) { var gain = this.playings[id].gain.gain; gain.setValueAtTime(gain.value, time); gain.linearRampToValueAtTime(gain.value * 0.1, time + 0.16); gain.linearRampToValueAtTime(0.0, time + 0.4); this.playings[id].source.stop(time + 0.41); if (this.playings[id].voice) { this.playings[id].voice.stop(time); } this.playings[id] = null; } }; AudioEngineWeb.prototype.stop = function (id, delay_ms, part_id) { var time = this.context.currentTime + (delay_ms / 1000); var delay = delay_ms - this.threshold; if (delay <= 0) this.actualStop(id, time, part_id); else { this.worker.postMessage({ delay: delay, args: { action: 1/*stop*/, id: id, time: time, part_id: part_id } }); } }; AudioEngineWeb.prototype.setVolume = function (vol) { AudioEngine.prototype.setVolume.call(this, vol); this.masterGain.gain.value = this.volume; }; AudioEngineWeb.prototype.resume = function () { this.paused = false; this.context.resume(); }; // Renderer classes //////////////////////////////////////////////////////////////// var Renderer = function () { }; Renderer.prototype.init = function (piano) { this.piano = piano; this.resize(); return this; }; Renderer.prototype.resize = function (width, height) { if (typeof width == "undefined") width = $(this.piano.rootElement).width(); if (typeof height == "undefined") height = Math.floor(width * 0.2); $(this.piano.rootElement).css({ "height": height + "px", marginTop: Math.floor($(window).height() / 2 - height / 2) + "px" }); this.width = width * window.devicePixelRatio; this.height = height * window.devicePixelRatio; }; Renderer.prototype.visualize = function (key, color) { }; var CanvasRenderer = function () { Renderer.call(this); }; CanvasRenderer.prototype = new Renderer(); CanvasRenderer.prototype.init = function (piano) { this.canvas = document.createElement("canvas"); this.ctx = this.canvas.getContext("2d"); piano.rootElement.appendChild(this.canvas); Renderer.prototype.init.call(this, piano); // calls resize() // create render loop var self = this; var render = function () { self.redraw(); requestAnimationFrame(render); }; requestAnimationFrame(render); // add event listeners var mouse_down = false; var last_key = null; $(piano.rootElement).mousedown(function (event) { mouse_down = true; //event.stopPropagation(); if (!gNoPreventDefault) event.preventDefault(); var pos = CanvasRenderer.translateMouseEvent(event); var hit = self.getHit(pos.x, pos.y); if (hit) { press(hit.key.note, hit.v); last_key = hit.key; } }); piano.rootElement.addEventListener("touchstart", function (event) { mouse_down = true; //event.stopPropagation(); if (!gNoPreventDefault) event.preventDefault(); for (var i in event.changedTouches) { var pos = CanvasRenderer.translateMouseEvent(event.changedTouches[i]); var hit = self.getHit(pos.x, pos.y); if (hit) { press(hit.key.note, hit.v); last_key = hit.key; } } }, false); $(window).mouseup(function (event) { if (last_key) { release(last_key.note); } mouse_down = false; last_key = null; }); /*$(piano.rootElement).mousemove(function(event) { if(!mouse_down) return; var pos = CanvasRenderer.translateMouseEvent(event); var hit = self.getHit(pos.x, pos.y); if(hit && hit.key != last_key) { press(hit.key.note, hit.v); last_key = hit.key; } });*/ return this; }; CanvasRenderer.prototype.resize = function (width, height) { Renderer.prototype.resize.call(this, width, height); if (this.width < 52 * 2) this.width = 52 * 2; if (this.height < this.width * 0.2) this.height = Math.floor(this.width * 0.2); this.canvas.width = this.width; this.canvas.height = this.height; this.canvas.style.width = this.width / window.devicePixelRatio + "px"; this.canvas.style.height = this.height / window.devicePixelRatio + "px"; // calculate key sizes this.whiteKeyWidth = Math.floor(this.width / 52); this.whiteKeyHeight = Math.floor(this.height * 0.9); this.blackKeyWidth = Math.floor(this.whiteKeyWidth * 0.75); this.blackKeyHeight = Math.floor(this.height * 0.5); this.blackKeyOffset = Math.floor(this.whiteKeyWidth - (this.blackKeyWidth / 2)); this.keyMovement = Math.floor(this.whiteKeyHeight * 0.015); this.whiteBlipWidth = Math.floor(this.whiteKeyWidth * 0.7); this.whiteBlipHeight = Math.floor(this.whiteBlipWidth * 0.8); this.whiteBlipX = Math.floor((this.whiteKeyWidth - this.whiteBlipWidth) / 2); this.whiteBlipY = Math.floor(this.whiteKeyHeight - this.whiteBlipHeight * 1.2); this.blackBlipWidth = Math.floor(this.blackKeyWidth * 0.7); this.blackBlipHeight = Math.floor(this.blackBlipWidth * 0.8); this.blackBlipY = Math.floor(this.blackKeyHeight - this.blackBlipHeight * 1.2); this.blackBlipX = Math.floor((this.blackKeyWidth - this.blackBlipWidth) / 2); // prerender white key this.whiteKeyRender = document.createElement("canvas"); this.whiteKeyRender.width = this.whiteKeyWidth; this.whiteKeyRender.height = this.height + 10; var ctx = this.whiteKeyRender.getContext("2d"); if (ctx.createLinearGradient) { var gradient = ctx.createLinearGradient(0, 0, 0, this.whiteKeyHeight); gradient.addColorStop(0, "#eee"); gradient.addColorStop(0.75, "#fff"); gradient.addColorStop(1, "#dad4d4"); ctx.fillStyle = gradient; } else { ctx.fillStyle = "#fff"; } ctx.strokeStyle = "#000"; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.lineWidth = 10; ctx.strokeRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth); ctx.lineWidth = 4; ctx.fillRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth); // prerender black key this.blackKeyRender = document.createElement("canvas"); this.blackKeyRender.width = this.blackKeyWidth + 10; this.blackKeyRender.height = this.blackKeyHeight + 10; var ctx = this.blackKeyRender.getContext("2d"); if (ctx.createLinearGradient) { var gradient = ctx.createLinearGradient(0, 0, 0, this.blackKeyHeight); gradient.addColorStop(0, "#000"); gradient.addColorStop(1, "#444"); ctx.fillStyle = gradient; } else { ctx.fillStyle = "#000"; } ctx.strokeStyle = "#222"; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.lineWidth = 8; ctx.strokeRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth); ctx.lineWidth = 4; ctx.fillRect(ctx.lineWidth / 2, ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth); // prerender shadows this.shadowRender = []; var y = -this.canvas.height * 2; for (var j = 0; j < 2; j++) { var canvas = document.createElement("canvas"); this.shadowRender[j] = canvas; canvas.width = this.canvas.width; canvas.height = this.canvas.height; var ctx = canvas.getContext("2d"); var sharp = j ? true : false; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.lineWidth = 1; ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; ctx.shadowBlur = this.keyMovement * 3; ctx.shadowOffsetY = -y + this.keyMovement; if (sharp) { ctx.shadowOffsetX = this.keyMovement; } else { ctx.shadowOffsetX = 0; ctx.shadowOffsetY = -y + this.keyMovement; } for (var i in this.piano.keys) { if (!this.piano.keys.hasOwnProperty(i)) continue; var key = this.piano.keys[i]; if (key.sharp != sharp) continue; if (key.sharp) { ctx.fillRect(this.blackKeyOffset + this.whiteKeyWidth * key.spatial + ctx.lineWidth / 2, y + ctx.lineWidth / 2, this.blackKeyWidth - ctx.lineWidth, this.blackKeyHeight - ctx.lineWidth); } else { ctx.fillRect(this.whiteKeyWidth * key.spatial + ctx.lineWidth / 2, y + ctx.lineWidth / 2, this.whiteKeyWidth - ctx.lineWidth, this.whiteKeyHeight - ctx.lineWidth); } } } // update key rects for (var i in this.piano.keys) { if (!this.piano.keys.hasOwnProperty(i)) continue; var key = this.piano.keys[i]; if (key.sharp) { key.rect = new Rect(this.blackKeyOffset + this.whiteKeyWidth * key.spatial, 0, this.blackKeyWidth, this.blackKeyHeight); } else { key.rect = new Rect(this.whiteKeyWidth * key.spatial, 0, this.whiteKeyWidth, this.whiteKeyHeight); } } }; CanvasRenderer.prototype.visualize = function (key, color) { key.timePlayed = Date.now(); key.blips.push({ "time": key.timePlayed, "color": color }); }; CanvasRenderer.prototype.redraw = function () { var now = Date.now(); var timeLoadedEnd = now - 1000; var timePlayedEnd = now - 100; var timeBlipEnd = now - 1000; this.ctx.save(); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // draw all keys for (var j = 0; j < 2; j++) { this.ctx.globalAlpha = 1.0; this.ctx.drawImage(this.shadowRender[j], 0, 0); var sharp = j ? true : false; for (var i in this.piano.keys) { if (!this.piano.keys.hasOwnProperty(i)) continue; var key = this.piano.keys[i]; if (key.sharp != sharp) continue; if (!key.loaded) { this.ctx.globalAlpha = 0.2; } else if (key.timeLoaded > timeLoadedEnd) { this.ctx.globalAlpha = ((now - key.timeLoaded) / 1000) * 0.8 + 0.2; } else { this.ctx.globalAlpha = 1.0; } var y = 0; if (key.timePlayed > timePlayedEnd) { y = Math.floor(this.keyMovement - (((now - key.timePlayed) / 100) * this.keyMovement)); } var x = Math.floor(key.sharp ? this.blackKeyOffset + this.whiteKeyWidth * key.spatial : this.whiteKeyWidth * key.spatial); var image = key.sharp ? this.blackKeyRender : this.whiteKeyRender; this.ctx.drawImage(image, x, y); var keyName = key.baseNote[0].toUpperCase(); if (sharp) keyName += "#"; keyName += key.octave + 1; if (gShowPianoNotes) { this.ctx.font = `${((key.sharp ? this.blackKeyWidth : this.whiteKeyWidth) / 2)}px Arial`; this.ctx.fillStyle = key.sharp ? "white" : "black"; this.ctx.textAlign = "center"; // do two passes to render both sharps and flat names. if (keyName.includes('#')) { this.ctx.fillText(keyName, x + ((key.sharp ? this.blackKeyWidth : this.whiteKeyWidth) / 2), y + (key.sharp ? this.blackKeyHeight : this.whiteKeyHeight) - 30 - this.ctx.lineWidth); } keyName = keyName.replace("C#", "D♭"); keyName = keyName.replace("D#", "E♭"); keyName = keyName.replace("F#", "G♭"); keyName = keyName.replace("G#", "A♭"); keyName = keyName.replace("A#", "B♭"); this.ctx.fillText(keyName, x + ((key.sharp ? this.blackKeyWidth : this.whiteKeyWidth) / 2), y + (key.sharp ? this.blackKeyHeight : this.whiteKeyHeight) - 10 - this.ctx.lineWidth); } const highlightScale = BASIC_PIANO_SCALES[gHighlightScaleNotes]; if (highlightScale && key.loaded) { keyName = keyName.replace("C#", "D♭"); keyName = keyName.replace("D#", "E♭"); keyName = keyName.replace("F#", "G♭"); keyName = keyName.replace("G#", "A♭"); keyName = keyName.replace("A#", "B♭"); const keynameNoOctave = keyName.slice(0, -1); if (highlightScale.includes(keynameNoOctave)) { const prev = this.ctx.globalAlpha; this.ctx.globalAlpha = 0.3; this.ctx.fillStyle = "#0f0"; if (key.sharp) { this.ctx.fillRect(x, y, this.blackKeyWidth, this.blackKeyHeight); } else { this.ctx.fillRect(x, y, this.whiteKeyWidth, this.whiteKeyHeight); } this.ctx.globalAlpha = prev; } } // render blips if (key.blips.length) { var alpha = this.ctx.globalAlpha; var w, h; if (key.sharp) { x += this.blackBlipX; y = this.blackBlipY; w = this.blackBlipWidth; h = this.blackBlipHeight; } else { x += this.whiteBlipX; y = this.whiteBlipY; w = this.whiteBlipWidth; h = this.whiteBlipHeight; } for (var b = 0; b < key.blips.length; b++) { var blip = key.blips[b]; if (blip.time > timeBlipEnd) { this.ctx.fillStyle = blip.color; this.ctx.globalAlpha = alpha - ((now - blip.time) / 1000); this.ctx.fillRect(x, y, w, h); } else { key.blips.splice(b, 1); --b; } y -= Math.floor(h * 1.1); } } } } this.ctx.restore(); }; CanvasRenderer.prototype.renderNoteLyrics = function () { // render lyric for (var part_id in this.noteLyrics) { if (!this.noteLyrics.hasOwnProperty(i)) continue; var lyric = this.noteLyrics[part_id]; var lyric_x = x; var lyric_y = this.whiteKeyHeight + 1; this.ctx.fillStyle = key.lyric.color; var alpha = this.ctx.globalAlpha; this.ctx.globalAlpha = alpha - ((now - key.lyric.time) / 1000); this.ctx.fillRect(x, y, 10, 10); } }; CanvasRenderer.prototype.getHit = function (x, y) { for (var j = 0; j < 2; j++) { var sharp = j ? false : true; // black keys first for (var i in this.piano.keys) { if (!this.piano.keys.hasOwnProperty(i)) continue; var key = this.piano.keys[i]; if (key.sharp != sharp) continue; if (key.rect.contains(x, y)) { var v = y / (key.sharp ? this.blackKeyHeight : this.whiteKeyHeight); v += 0.25; v *= DEFAULT_VELOCITY; if (v > 1.0) v = 1.0; return { "key": key, "v": v }; } } } return null; }; CanvasRenderer.isSupported = function () { var canvas = document.createElement("canvas"); return !!(canvas.getContext && canvas.getContext("2d")); }; CanvasRenderer.translateMouseEvent = function (evt) { var element = evt.target; var offx = 0; var offy = 0; do { if (!element) break; // wtf, wtf? offx += element.offsetLeft; offy += element.offsetTop; } while (element = element.offsetParent); return { x: (evt.pageX - offx) * window.devicePixelRatio, y: (evt.pageY - offy) * window.devicePixelRatio } }; // Soundpack Stuff by electrashave ♥ //////////////////////////////////////////////////////////////// var soundDomain = `http://${location.host}`; function SoundSelector(piano) { this.initialized = false; this.keys = piano.keys; this.loading = {}; this.notification; this.packs = []; this.piano = piano; this.soundSelection = localStorage.soundSelection ? localStorage.soundSelection : "mppclassic"; this.addPack({ name: "MPP Classic", keys: Object.keys(this.piano.keys), ext: ".mp3", url: "/sounds/mppclassic/" }); } SoundSelector.prototype.addPack = function (pack, load) { var self = this; self.loading[pack.url || pack] = true; function add(obj) { var added = false; for (var i = 0; self.packs.length > i; i++) { if (obj.name == self.packs[i].name) { added = true; break; } } if (added) return console.warn("Sounds already added!!"); //no adding soundpacks twice D:< if (obj.url.substr(obj.url.length - 1) != "/") obj.url = obj.url + "/"; var html = document.createElement("li"); html.classList = "pack"; html.innerText = obj.name + " (" + obj.keys.length + " keys)"; html.onclick = function () { self.loadPack(obj.name); self.notification.close(); }; obj.html = html; self.packs.push(obj); self.packs.sort(function (a, b) { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; }); if (load) self.loadPack(obj.name); delete self.loading[obj.url]; } if (typeof pack == "string") { let useDomain = true; if (pack.match(/^(http|https):\/\//i)) useDomain = false; $.getJSON((useDomain ? soundDomain : '') + pack + "/info.json").done(function (json) { json.url = pack; add(json); }); } else add(pack); //validate packs?? }; SoundSelector.prototype.addPacks = function (packs) { for (var i = 0; packs.length > i; i++) this.addPack(packs[i]); }; SoundSelector.prototype.init = function () { var self = this; if (self.initialized) return console.warn("Sound selector already initialized!"); if (!!Object.keys(self.loading).length) return setTimeout(function () { self.init(); }, 250); $("#sound-btn").on("click", function () { if (document.getElementById("Notification-Sound-Selector") != null) return self.notification.close(); var html = document.createElement("ul"); //$(html).append("

Current Sound: " + self.soundSelection + "

"); for (var i = 0; self.packs.length > i; i++) { var pack = self.packs[i]; if (pack.name == self.soundSelection) pack.html.classList = "pack enabled"; else pack.html.classList = "pack"; html.appendChild(pack.html); } self.notification = new Notification({ title: "Sound Selector", html: html, id: "Sound-Selector", duration: -1, target: "#sound-btn" }); }); self.initialized = true; self.loadPack(self.soundSelection, true); }; SoundSelector.prototype.loadPack = function (pack, f) { for (var i = 0; this.packs.length > i; i++) { var p = this.packs[i]; if (p.name == pack) { pack = p; break; } } if (typeof pack == "string") { console.warn("Sound pack does not exist! Loading default pack..."); return this.loadPack("MPP Classic"); } if (pack.name == this.soundSelection && !f) return; if (pack.keys.length != Object.keys(this.piano.keys).length) { this.piano.keys = {}; for (var i = 0; pack.keys.length > i; i++) this.piano.keys[pack.keys[i]] = this.keys[pack.keys[i]]; this.piano.renderer.resize(); } var self = this; for (var i in this.piano.keys) { if (!this.piano.keys.hasOwnProperty(i)) continue; (function () { var key = self.piano.keys[i]; key.loaded = false; let useDomain = true; if (pack.url.match(/^(http|https):\/\//i)) useDomain = false self.piano.audio.load(key.note, (useDomain ? soundDomain : '') + pack.url + key.note + pack.ext, function () { key.loaded = true; key.timeLoaded = Date.now(); }); })(); } if (localStorage) localStorage.soundSelection = pack.name; this.soundSelection = pack.name; }; SoundSelector.prototype.removePack = function (name) { var found = false; for (var i = 0; this.packs.length > i; i++) { var pack = this.packs[i]; if (pack.name == name) { this.packs.splice(i, 1); if (pack.name == this.soundSelection) this.loadPack(this.packs[0].name); //add mpp default if none? break; } } if (!found) console.warn("Sound pack not found!"); }; // Pianoctor //////////////////////////////////////////////////////////////// var PianoKey = function (note, octave) { this.note = note + octave; this.baseNote = note; this.octave = octave; this.sharp = note.indexOf("s") != -1; this.loaded = false; this.timeLoaded = 0; this.domElement = null; this.timePlayed = 0; this.blips = []; }; var Piano = function (rootElement) { var piano = this; piano.rootElement = rootElement; piano.keys = {}; var white_spatial = 0; var black_spatial = 0; var black_it = 0; var black_lut = [2, 1, 2, 1, 1]; var addKey = function (note, octave) { var key = new PianoKey(note, octave); piano.keys[key.note] = key; if (key.sharp) { key.spatial = black_spatial; black_spatial += black_lut[black_it % 5]; ++black_it; } else { key.spatial = white_spatial; ++white_spatial; } } if (test_mode) { addKey("c", 2); } else { addKey("a", -1); addKey("as", -1); addKey("b", -1); var notes = "c cs d ds e f fs g gs a as b".split(" "); for (var oct = 0; oct < 7; oct++) { for (var i in notes) { addKey(notes[i], oct); } } addKey("c", 7); } this.renderer = new CanvasRenderer().init(this); window.addEventListener("resize", function () { piano.renderer.resize(); }); window.AudioContext = window.AudioContext || window.webkitAudioContext || undefined; var audio_engine = AudioEngineWeb; this.audio = new audio_engine().init(); }; Piano.prototype.play = function (note, vol, participant, delay_ms, lyric) { if (!this.keys.hasOwnProperty(note) || !participant) return; var key = this.keys[note]; if (key.loaded) this.audio.play(key.note, vol, delay_ms, participant.id); if (gMidiOutTest) gMidiOutTest(key.note, vol * 100, delay_ms, participant.id); var self = this; setTimeout(function () { self.renderer.visualize(key, participant.color); if (lyric) { } var jq_namediv = $(participant.nameDiv); jq_namediv.addClass("play"); setTimeout(function () { jq_namediv.removeClass("play"); }, 30); }, delay_ms || 0); }; Piano.prototype.stop = function (note, participant, delay_ms) { if (!this.keys.hasOwnProperty(note)) return; var key = this.keys[note]; if (key.loaded) this.audio.stop(key.note, delay_ms, participant.id); if (gMidiOutTest) gMidiOutTest(key.note, 0, delay_ms, participant.id); }; var gPiano = new Piano(document.getElementById("piano")); var gSoundSelector = new SoundSelector(gPiano); gSoundSelector.addPacks(["/sounds/Vintage_Upright_Soft/"]); //gSoundSelector.addPacks(["/sounds/Emotional_2.0/", "/sounds/Harp/", "/sounds/Music_Box/", "/sounds/Vintage_Upright/", "/sounds/Steinway_Grand/", "/sounds/Emotional/", "/sounds/Untitled/"]); gSoundSelector.init(); var gAutoSustain = false; var gSustain = false; var gHeldNotes = {}; var gSustainedNotes = {}; function press(id, vol) { if (!gClient.preventsPlaying() && gNoteQuota.spend(1)) { gHeldNotes[id] = true; gSustainedNotes[id] = true; gPiano.play(id, vol !== undefined ? vol : DEFAULT_VELOCITY, gClient.getOwnParticipant(), 0); gClient.startNote(id, vol); } } function release(id) { if (gHeldNotes[id]) { gHeldNotes[id] = false; if ((gAutoSustain || gSustain) && !enableSynth) { gSustainedNotes[id] = true; } else { if (gNoteQuota.spend(1)) { gPiano.stop(id, gClient.getOwnParticipant(), 0); gClient.stopNote(id); gSustainedNotes[id] = false; } } } } function pressSustain() { gSustain = true; } function releaseSustain() { gSustain = false; if (!gAutoSustain) { for (var id in gSustainedNotes) { if (gSustainedNotes.hasOwnProperty(id) && gSustainedNotes[id] && !gHeldNotes[id]) { gSustainedNotes[id] = false; if (gNoteQuota.spend(1)) { gPiano.stop(id, gClient.getOwnParticipant(), 0); gClient.stopNote(id); } } } } } function getParameterByName(name, url = window.location.href) { name = name.replace(/[\[\]]/g, '\\$&'); var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); } //html/css overrides for multiplayerpiano.com if (window.location.hostname === 'multiplayerpiano.com') { //disable autocomplete $('#chat-input')[0].autocomplete = 'off'; //add rules button let aElement = document.createElement("a"); aElement.href = "https://docs.google.com/document/d/1wQvGwQdaI8PuEjSWxKDDThVIoAlCYIxQOyfyi4o6HcM/edit?usp=sharing"; aElement.title = "Multiplayer Piano Rules"; aElement.target = "_blank"; let buttonElement = document.createElement("button"); buttonElement.style = "height: 24px; font-size: 12px; background: #111; border: 1px solid #444; padding: 5px; cursor: pointer; line-height: 12px; border-radius: 2px; -webkit-border-radius: 2px; -moz-border-radius: 2px; overflow: hidden; white-space: nowrap; color: #fff; position: absolute; right: 6px; top: 0px; z-index: 20001;" buttonElement.innerText = "Rules" aElement.appendChild(buttonElement); document.body.appendChild(aElement); } function getRoomNameFromURL() { var channel_id = decodeURIComponent(window.location.pathname); if (channel_id.substr(0, 1) == "/") channel_id = channel_id.substr(1); if (!channel_id) { channel_id = getParameterByName("c"); } if (!channel_id) channel_id = "lobby"; return channel_id; } // internet science //////////////////////////////////////////////////////////////// var channel_id = getRoomNameFromURL(); var loginInfo; if (getParameterByName("callback") === "discord") { var code = getParameterByName("code"); if (code) { loginInfo = { type: "discord", code }; } history.pushState({ "name": "lobby" }, "Piano > lobby", "/"); channel_id = "lobby"; } var wssport = 8080; var isSecure = location.protocol == 'https:'; var gClient = new Client(`${isSecure ? 'wss' : 'ws'}://${location.host}`); if (loginInfo) { gClient.setLoginInfo(loginInfo); } gClient.setChannel(channel_id); gClient.on("disconnect", function (evt) { //console.log(evt); }); var tabIsActive = true; var youreMentioned = false; window.addEventListener('focus', function (event) { tabIsActive = true; youreMentioned = false; var count = Object.keys(MPP.client.ppl).length; if (count > 0) { document.title = "Piano (" + count + ")"; } else { document.title = "Multiplayer Piano"; } }); window.addEventListener('blur', function (event) { tabIsActive = false; }); // Setting status (function () { gClient.on("status", function (status) { $("#status").text(status); }); gClient.on("count", function (count) { if (count > 0) { $("#status").html('' + count + ' ' + (count == 1 ? 'person is' : 'people are') + ' playing'); if (!tabIsActive && youreMentioned) return; document.title = "Piano (" + count + ")"; } else { document.title = "Multiplayer Piano"; } }); })(); // Show moderator buttons (function () { gClient.on("hi", function (msg) { if (gClient.permissions.clearChat) { $("#clearchat-btn").show(); } if (gClient.permissions.vanish) { $("#vanish-btn").show(); } else { $("#vanish-btn").hide(); } }); })(); var participantTouchhandler; //declare this outside of the smaller functions so it can be used below and setup later // Handle changes to participants (function () { function setupParticipantDivs(part) { var hadNameDiv = Boolean(part.nameDiv); var nameDiv; if (hadNameDiv) { nameDiv = part.nameDiv; $(nameDiv).empty(); } else { nameDiv = document.createElement("div"); nameDiv.addEventListener("mousedown", e => participantTouchhandler(e, nameDiv)); nameDiv.addEventListener("touchstart", e => participantTouchhandler(e, nameDiv)); nameDiv.style.display = "none"; $(nameDiv).fadeIn(2000); nameDiv.id = 'namediv-' + part._id; nameDiv.className = "name"; nameDiv.participantId = part.id; $("#names")[0].appendChild(nameDiv); part.nameDiv = nameDiv; } nameDiv.style.backgroundColor = part.color || "#777"; var tagText = typeof part.tag === 'object' ? part.tag.text : part.tag; if (tagText === 'BOT') nameDiv.title = 'This is an authorized bot.'; if (tagText === 'MOD') nameDiv.title = 'This user is an official moderator of the site.'; if (tagText === 'ADMIN') nameDiv.title = 'This user is an official administrator of the site.'; if (tagText === 'OWNER') nameDiv.title = 'This user is the owner of the site.'; if (tagText === 'MEDIA') nameDiv.title = 'This is a well known person on Twitch, Youtube, or another platform.'; updateLabels(part); var hasOtherDiv = false; if (part.vanished) { hasOtherDiv = true; var vanishDiv = document.createElement("div"); vanishDiv.className = "nametag"; vanishDiv.textContent = 'VANISH'; vanishDiv.style.backgroundColor = '#00ffcc'; vanishDiv.id = 'namevanish-' + part._id; part.nameDiv.appendChild(vanishDiv); } if (part.tag) { hasOtherDiv = true; var tagDiv = document.createElement("div"); tagDiv.className = "nametag"; tagDiv.textContent = tagText || ""; tagDiv.style.backgroundColor = tagColor(part.tag); tagDiv.id = 'nametag-' + part._id; part.nameDiv.appendChild(tagDiv); } var textDiv = document.createElement("div"); textDiv.className = "nametext"; textDiv.textContent = part.name || ""; textDiv.id = 'nametext-' + part._id; if (hasOtherDiv) textDiv.style.float = 'left'; part.nameDiv.appendChild(textDiv); var arr = $("#names .name"); arr.sort(function (a, b) { if (a.id > b.id) return 1; else if (a.id < b.id) return -1; else return 0; }); $("#names").html(arr); } gClient.on("participant added", function (part) { part.displayX = 150; part.displayY = 50; // add nameDiv setupParticipantDivs(part); // add cursorDiv if ((gClient.participantId !== part.id || gSeeOwnCursor) && !gCursorHides.includes(part.id) && !gHideAllCursors) { var div = document.createElement("div"); div.className = "cursor"; div.style.display = "none"; part.cursorDiv = $("#cursors")[0].appendChild(div); $(part.cursorDiv).fadeIn(2000); var div = document.createElement("div"); div.className = "name"; div.style.backgroundColor = part.color || "#777" div.textContent = part.name || ""; part.cursorDiv.appendChild(div); } else { part.cursorDiv = undefined; } }); gClient.on("participant removed", function (part) { // remove nameDiv var nd = $(part.nameDiv); var cd = $(part.cursorDiv); cd.fadeOut(2000); nd.fadeOut(2000, function () { nd.remove(); cd.remove(); part.nameDiv = undefined; part.cursorDiv = undefined; }); }); gClient.on("participant update", function (part) { var name = part.name || ""; var color = part.color || "#777"; setupParticipantDivs(part); $(part.cursorDiv) .find(".name") .text(name) .css("background-color", color); }); gClient.on("ch", function (msg) { for (var id in gClient.ppl) { if (gClient.ppl.hasOwnProperty(id)) { var part = gClient.ppl[id]; updateLabels(part); } } }); gClient.on("participant added", function (part) { updateLabels(part); }); function updateLabels(part) { if (part.id === gClient.participantId) { $(part.nameDiv).addClass("me"); } else { $(part.nameDiv).removeClass("me"); } if (gClient.channel.crown && gClient.channel.crown.participantId === part.id) { $(part.nameDiv).addClass("owner"); $(part.cursorDiv).addClass("owner"); } else { $(part.nameDiv).removeClass("owner"); $(part.cursorDiv).removeClass("owner"); } if (gPianoMutes.indexOf(part._id) !== -1) { $(part.nameDiv).addClass("muted-notes"); } else { $(part.nameDiv).removeClass("muted-notes"); } if (gChatMutes.indexOf(part._id) !== -1) { $(part.nameDiv).addClass("muted-chat"); } else { $(part.nameDiv).removeClass("muted-chat"); } } function tagColor(tag) { if (typeof tag === 'object') return tag.color; if (tag === 'BOT') return '#55f'; if (tag === 'OWNER') return '#a00'; if (tag === 'ADMIN') return '#f55'; if (tag === 'MOD') return '#0a0'; if (tag === 'MEDIA') return '#f5f'; return '#777'; } function updateCursor(msg) { const part = gClient.ppl[msg.id]; if (part && part.cursorDiv) { if (gSmoothCursor) { part.cursorDiv.style.transform = 'translate3d(' + msg.x + 'vw, ' + msg.y + 'vh, 0)'; } else { part.cursorDiv.style.left = msg.x + "%"; part.cursorDiv.style.top = msg.y + "%"; } } } gClient.on("m", updateCursor); gClient.on("participant added", updateCursor); })(); // Handle changes to crown (function () { var jqcrown = $('
').appendTo(document.body).hide(); var jqcountdown = $('').appendTo(jqcrown); var countdown_interval; jqcrown.click(function () { gClient.sendArray([{ m: "chown", id: gClient.participantId }]); }); gClient.on("ch", function (msg) { if (msg.ch.crown) { var crown = msg.ch.crown; if (!crown.participantId || !gClient.ppl[crown.participantId]) { var land_time = crown.time + 2000 - gClient.serverTimeOffset; var avail_time = crown.time + 15000 - gClient.serverTimeOffset; jqcountdown.text(""); jqcrown.show(); if (land_time - Date.now() <= 0) { jqcrown.css({ "left": crown.endPos.x + "%", "top": crown.endPos.y + "%" }); } else { jqcrown.css({ "left": crown.startPos.x + "%", "top": crown.startPos.y + "%" }); jqcrown.addClass("spin"); jqcrown.animate({ "left": crown.endPos.x + "%", "top": crown.endPos.y + "%" }, 2000, "linear", function () { jqcrown.removeClass("spin"); }); } clearInterval(countdown_interval); countdown_interval = setInterval(function () { var time = Date.now(); if (time >= land_time) { var ms = avail_time - time; if (ms > 0) { jqcountdown.text(Math.ceil(ms / 1000) + "s"); } else { jqcountdown.text(""); clearInterval(countdown_interval); } } }, 1000); } else { jqcrown.hide(); } } else { jqcrown.hide(); } }); gClient.on("disconnect", function () { jqcrown.fadeOut(2000); }); })(); // Playing notes gClient.on("n", function (msg) { var t = msg.t - gClient.serverTimeOffset + TIMING_TARGET - Date.now(); var participant = gClient.findParticipantById(msg.p); if (gPianoMutes.indexOf(participant._id) !== -1) return; for (var i = 0; i < msg.n.length; i++) { var note = msg.n[i]; var ms = t + (note.d || 0); if (ms < 0) { ms = 0; } else if (ms > 10000) continue; if (note.s) { gPiano.stop(note.n, participant, ms); } else { var vel = (typeof note.v !== "undefined") ? parseFloat(note.v) : DEFAULT_VELOCITY; if (!vel) vel = 0; else if (vel < 0) vel = 0; else if (vel > 1) vel = 1; gPiano.play(note.n, vel, participant, ms); if (enableSynth) { gPiano.stop(note.n, participant, ms + 1000); } } } }); // Send cursor updates var mx = 0, last_mx = -10, my = 0, last_my = -10; setInterval(function () { if (Math.abs(mx - last_mx) > 0.1 || Math.abs(my - last_my) > 0.1) { last_mx = mx; last_my = my; gClient.sendArray([{ m: "m", x: mx, y: my }]); if (gSeeOwnCursor) { gClient.emit("m", { m: "m", id: gClient.participantId, x: mx, y: my }); } var part = gClient.getOwnParticipant(); if (part) { part.x = mx; part.y = my; } } }, 50); $(document).mousemove(function (event) { mx = ((event.pageX / $(window).width()) * 100).toFixed(2); my = ((event.pageY / $(window).height()) * 100).toFixed(2); }); // Room settings button (function () { gClient.on("ch", function (msg) { if (gClient.isOwner() || gClient.permissions.chsetAnywhere) { $("#room-settings-btn").show(); } else { $("#room-settings-btn").hide(); } if (!gClient.channel.settings.lobby && (gClient.permissions.chownAnywhere || gClient.channel.settings.owner_id === gClient.user._id)) { $("#getcrown-btn").show(); } else { $("#getcrown-btn").hide(); } }); $("#room-settings-btn").click(function (evt) { if (gClient.channel && (gClient.isOwner() || gClient.permissions.chsetAnywhere)) { var settings = gClient.channel.settings; openModal("#room-settings"); setTimeout(function () { $("#room-settings .checkbox[name=visible]").prop("checked", settings.visible); $("#room-settings .checkbox[name=chat]").prop("checked", settings.chat); $("#room-settings .checkbox[name=crownsolo]").prop("checked", settings.crownsolo); $("#room-settings .checkbox[name=nocussing]").prop("checked", settings["no cussing"]); $("#room-settings input[name=color]").val(settings.color); $("#room-settings input[name=color2]").val(settings.color2); $("#room-settings input[name=limit]").val(settings.limit); }, 100); } }); $("#room-settings .submit").click(function () { var settings = { visible: $("#room-settings .checkbox[name=visible]").is(":checked"), chat: $("#room-settings .checkbox[name=chat]").is(":checked"), crownsolo: $("#room-settings .checkbox[name=crownsolo]").is(":checked"), "no cussing": $("#room-settings .checkbox[name=nocussing]").is(":checked"), color: $("#room-settings input[name=color]").val(), color2: $("#room-settings input[name=color2]").val(), limit: $("#room-settings input[name=limit]").val(), }; gClient.setChannelSettings(settings); closeModal(); }); $("#room-settings .drop-crown").click(function () { closeModal(); if (confirm("This will drop the crown...!")) gClient.sendArray([{ m: "chown" }]); }); })(); // Clear chat button $("#clearchat-btn").click(function (evt) { if (confirm("Are you sure you want to clear chat?")) gClient.sendArray([{ m: 'clearchat' }]); }); // Get crown button $("#getcrown-btn").click(function (evt) { gClient.sendArray([{ m: 'chown', id: MPP.client.getOwnParticipant().id }]); }); // Vanish or unvanish button $("#vanish-btn").click(function (evt) { gClient.sendArray([{ m: 'v', vanish: !gClient.getOwnParticipant().vanished }]); }); gClient.on('participant update', part => { if (part._id === gClient.getOwnParticipant()._id) { if (part.vanished) { $("#vanish-btn").text('Unvanish'); } else { $("#vanish-btn").text('Vanish'); } } }); gClient.on('participant added', part => { if (part._id === gClient.getOwnParticipant()._id) { if (part.vanished) { $("#vanish-btn").text('Unvanish'); } else { $("#vanish-btn").text('Vanish'); } } }); // Handle notifications gClient.on("notification", function (msg) { new Notification(msg); }); // Don't foget spin gClient.on("ch", function (msg) { var chidlo = msg.ch._id.toLowerCase(); if (chidlo === "spin" || chidlo.substr(-5) === "/spin") { $("#piano").addClass("spin"); } else { $("#piano").removeClass("spin"); } }); /*function eb() { if(gClient.channel && gClient.channel._id.toLowerCase() === "test/fishing") { ebsprite.start(gClient); } else { ebsprite.stop(); } } if(ebsprite) { gClient.on("ch", eb); eb(); }*/ // Crownsolo notice gClient.on("ch", function (msg) { let notice = ""; let has_notice = false; if (msg.ch.settings.crownsolo) { has_notice = true; notice += '

This room is set to "only the owner can play."

'; } if (msg.ch.settings['no cussing']) { has_notice = true; notice += '

This room is set to "no cussing."

'; } let notice_div = $("#room-notice"); if (has_notice) { notice_div.html(notice); if (notice_div.is(':hidden')) notice_div.fadeIn(1000); } else { if (notice_div.is(':visible')) notice_div.fadeOut(1000); } }); gClient.on("disconnect", function () { $("#room-notice").fadeOut(1000); }); var gPianoMutes = (localStorage.pianoMutes ? localStorage.pianoMutes : "").split(',').filter(v => v); var gChatMutes = (localStorage.chatMutes ? localStorage.chatMutes : "").split(',').filter(v => v); var gShowIdsInChat = localStorage.showIdsInChat == "true"; var gShowTimestampsInChat = localStorage.showTimestampsInChat == "true"; var gNoChatColors = localStorage.noChatColors == "true"; var gNoBackgroundColor = localStorage.noBackgroundColor == "true"; var gOutputOwnNotes = localStorage.outputOwnNotes ? localStorage.outputOwnNotes == "true" : true; var gVirtualPianoLayout = localStorage.virtualPianoLayout == "true"; var gSmoothCursor = localStorage.smoothCursor == "true"; var gShowChatTooltips = localStorage.showChatTooltips ? localStorage.showChatTooltips == "true" : true; var gShowPianoNotes = localStorage.showPianoNotes == "true"; var gHighlightScaleNotes = localStorage.highlightScaleNotes; var gCursorHides = (localStorage.cursorHides ? localStorage.cursorHides : "").split(',').filter(v => v); var gHideAllCursors = localStorage.hideAllCursors == "true"; var gHidePiano = localStorage.hidePiano == "true"; var gHideChat = localStorage.hideChat == "true"; var gNoPreventDefault = localStorage.noPreventDefault == "true"; // var gWarnOnLinks = localStorage.warnOnLinks ? loalStorage.warnOnLinks == "true" : true; // Hide piano attribute if (gHidePiano) { $("#piano").hide(); } else { $("#piano").show(); } // Hide chat attribute if (gHideChat) { $("#chat").hide(); } else { $("#chat").show(); } // smooth cursor attribute if (gSmoothCursor) { $("#cursors").attr('smooth-cursors', ''); } else { $("#cursors").removeAttr('smooth-cursors'); } // Background color (function () { var old_color1 = new Color("#000000"); var old_color2 = new Color("#000000"); function setColor(hex, hex2) { var color1 = new Color(hex); var color2 = new Color(hex2 || hex); if (!hex2) color2.add(-0x40, -0x40, -0x40); var bottom = document.getElementById("bottom"); var duration = 500; var step = 0; var steps = 30; var step_ms = duration / steps; var difference = new Color(color1.r, color1.g, color1.b); difference.r -= old_color1.r; difference.g -= old_color1.g; difference.b -= old_color1.b; var inc1 = new Color(difference.r / steps, difference.g / steps, difference.b / steps); difference = new Color(color2.r, color2.g, color2.b); difference.r -= old_color2.r; difference.g -= old_color2.g; difference.b -= old_color2.b; var inc2 = new Color(difference.r / steps, difference.g / steps, difference.b / steps); var iv; iv = setInterval(function () { old_color1.add(inc1.r, inc1.g, inc1.b); old_color2.add(inc2.r, inc2.g, inc2.b); document.body.style.background = "radial-gradient(ellipse at center, " + old_color1.toHexa() + " 0%," + old_color2.toHexa() + " 100%)"; bottom.style.background = old_color2.toHexa(); if (++step >= steps) { clearInterval(iv); old_color1 = color1; old_color2 = color2; document.body.style.background = "radial-gradient(ellipse at center, " + color1.toHexa() + " 0%," + color2.toHexa() + " 100%)"; bottom.style.background = color2.toHexa(); } }, step_ms); } function setColorToDefault() { setColor("#220022", "#000022"); } window.setBackgroundColor = setColor; window.setBackgroundColorToDefault = setColorToDefault; setColorToDefault(); gClient.on("ch", function (ch) { if (gNoBackgroundColor) { setColorToDefault(); return; } if (ch.ch.settings) { if (ch.ch.settings.color) { setColor(ch.ch.settings.color, ch.ch.settings.color2); } else { setColorToDefault(); } } }); })(); var volume_slider = document.getElementById("volume-slider"); volume_slider.value = gPiano.audio.volume; $("#volume-label").text("Volume: " + Math.floor(gPiano.audio.volume * 100) + "%"); volume_slider.addEventListener("input", function (evt) { var v = +volume_slider.value; gPiano.audio.setVolume(v); if (window.localStorage) localStorage.volume = v; $("#volume-label").text("Volume: " + Math.floor(v * 100) + "%"); }); var Note = function (note, octave) { this.note = note; this.octave = octave || 0; }; var n = function (a, b) { return { note: new Note(a, b), held: false }; }; var layouts = { MPP: { 65: n("gs"), 90: n("a"), 83: n("as"), 88: n("b"), 67: n("c", 1), 70: n("cs", 1), 86: n("d", 1), 71: n("ds", 1), 66: n("e", 1), 78: n("f", 1), 74: n("fs", 1), 77: n("g", 1), 75: n("gs", 1), 188: n("a", 1), 76: n("as", 1), 190: n("b", 1), 191: n("c", 2), 222: n("cs", 2), 49: n("gs", 1), 81: n("a", 1), 50: n("as", 1), 87: n("b", 1), 69: n("c", 2), 52: n("cs", 2), 82: n("d", 2), 53: n("ds", 2), 84: n("e", 2), 89: n("f", 2), 55: n("fs", 2), 85: n("g", 2), 56: n("gs", 2), 73: n("a", 2), 57: n("as", 2), 79: n("b", 2), 80: n("c", 3), 189: n("cs", 3), 173: n("cs", 3), // firefox why 219: n("d", 3), 187: n("ds", 3), 61: n("ds", 3), // firefox why 221: n("e", 3) }, VP: { 112: n("c", -1), 113: n("d", -1), 114: n("e", -1), 115: n("f", -1), 116: n("g", -1), 117: n("a", -1), 118: n("b", -1), 49: n("c"), 50: n("d"), 51: n("e"), 52: n("f"), 53: n("g"), 54: n("a"), 55: n("b"), 56: n("c", 1), 57: n("d", 1), 48: n("e", 1), 81: n("f", 1), 87: n("g", 1), 69: n("a", 1), 82: n("b", 1), 84: n("c", 2), 89: n("d", 2), 85: n("e", 2), 73: n("f", 2), 79: n("g", 2), 80: n("a", 2), 65: n("b", 2), 83: n("c", 3), 68: n("d", 3), 70: n("e", 3), 71: n("f", 3), 72: n("g", 3), 74: n("a", 3), 75: n("b", 3), 76: n("c", 4), 90: n("d", 4), 88: n("e", 4), 67: n("f", 4), 86: n("g", 4), 66: n("a", 4), 78: n("b", 4), 77: n("c", 5), } } var key_binding = gVirtualPianoLayout ? layouts.VP : layouts.MPP; var capsLockKey = false; var transpose = 0; function handleKeyDown(evt) { if(evt.target.type) return; //console.log(evt); var code = parseInt(evt.keyCode); if (key_binding[code] !== undefined) { var binding = key_binding[code]; if (!binding.held) { binding.held = true; var note = binding.note; var octave = 1 + note.octave; if (!gVirtualPianoLayout) { if (evt.shiftKey) ++octave; else if (capsLockKey || evt.ctrlKey) --octave; else if (evt.altKey) octave += 2; } note = note.note + octave; var index = Object.keys(gPiano.keys).indexOf(note); if (gVirtualPianoLayout && evt.shiftKey) { note = Object.keys(gPiano.keys)[index + transpose + 1]; } else note = Object.keys(gPiano.keys)[index + transpose]; if (note === undefined) return; var vol = velocityFromMouseY(); press(note, vol); } if (++gKeyboardSeq == 3) { gKnowsYouCanUseKeyboard = true; if (window.gKnowsYouCanUseKeyboardTimeout) clearTimeout(gKnowsYouCanUseKeyboardTimeout); if (localStorage) localStorage.knowsYouCanUseKeyboard = true; if (window.gKnowsYouCanUseKeyboardNotification) gKnowsYouCanUseKeyboardNotification.close(); } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); return false; } else if (code == 20) { // Caps Lock capsLockKey = true; if (!gNoPreventDefault) evt.preventDefault(); } else if (code === 0x20) { // Space Bar pressSustain(); if (!gNoPreventDefault) evt.preventDefault(); } else if (code === 38 && transpose <= 100) { transpose += 12; sendTransposeNotif(); } else if (code === 40 && transpose >= -100) { transpose -= 12; sendTransposeNotif(); } else if (code === 39 && transpose < 100) { transpose++; sendTransposeNotif(); } else if (code === 37 && transpose > -100) { transpose--; sendTransposeNotif(); } else if (code == 9) { // Tab (don't tab away from the piano) if (!gNoPreventDefault) evt.preventDefault(); } else if (code == 8) { // Backspace (don't navigate Back) gAutoSustain = !gAutoSustain; if (!gNoPreventDefault) evt.preventDefault(); } }; function sendTransposeNotif() { new Notification({ title: 'Transposing', html: 'Transpose level: ' + transpose, target: '#midi-btn', duration: 1500 }); } function handleKeyUp(evt) { if(evt.target.type) return; var code = parseInt(evt.keyCode); if (key_binding[code] !== undefined) { var binding = key_binding[code]; if (binding.held) { binding.held = false; var note = binding.note; var octave = 1 + note.octave; if (!gVirtualPianoLayout) { if (evt.shiftKey) ++octave; else if (capsLockKey || evt.ctrlKey) --octave; else if (evt.altKey) octave += 2; } note = note.note + octave; var index = Object.keys(gPiano.keys).indexOf(note); if (gVirtualPianoLayout && evt.shiftKey) { note = Object.keys(gPiano.keys)[index + transpose + 1]; } else note = Object.keys(gPiano.keys)[index + transpose]; if (note === undefined) return; release(note); } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); return false; } else if (code == 20) { // Caps Lock capsLockKey = false; if (!gNoPreventDefault) evt.preventDefault(); } else if (code === 0x20) { // Space Bar releaseSustain(); if (!gNoPreventDefault) evt.preventDefault(); } }; function handleKeyPress(evt) { if(evt.target.type) return; if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); if (evt.keyCode == 27 || evt.keyCode == 13) { //$("#chat input").focus(); } return false; }; var recapListener = function (evt) { captureKeyboard(); }; var capturingKeyboard = false; function captureKeyboard() { if (!capturingKeyboard) { capturingKeyboard = true; $("#piano").off("mousedown", recapListener); $("#piano").off("touchstart", recapListener); $(document).on("keydown", handleKeyDown); $(document).on("keyup", handleKeyUp); $(window).on("keypress", handleKeyPress); } }; function releaseKeyboard() { if (capturingKeyboard) { capturingKeyboard = false; $(document).off("keydown", handleKeyDown); $(document).off("keyup", handleKeyUp); $(window).off("keypress", handleKeyPress); $("#piano").on("mousedown", recapListener); $("#piano").on("touchstart", recapListener); } }; captureKeyboard(); var velocityFromMouseY = function () { return 0.1 + (my / 100) * 0.6; }; // NoteQuota var gNoteQuota = (function () { var last_rat = 0; var nqjq = $("#quota .value"); setInterval(function () { gNoteQuota.tick(); }, 2000); return new NoteQuota(function (points) { // update UI var rat = (points / this.max) * 100; if (rat <= last_rat) nqjq.stop(true, true).css("width", rat.toFixed(0) + "%"); else nqjq.stop(true, true).animate({ "width": rat.toFixed(0) + "%" }, 2000, "linear"); last_rat = rat; }); })(); gClient.on("nq", function (nq_params) { gNoteQuota.setParams(nq_params); }); gClient.on("disconnect", function () { gNoteQuota.setParams(NoteQuota.PARAMS_OFFLINE); }); //DMs var gDmParticipant; var gIsDming = false; var gKnowsHowToDm = localStorage.knowsHowToDm === "true"; gClient.on('participant removed', part => { if (gIsDming && part._id === gDmParticipant._id) { gIsDming = false; $('#chat-input')[0].placeholder = 'You can chat with this thing.'; } }); // click participant names (function () { participantTouchhandler = function (e, ele) { var target = ele; var target_jq = $(target); if (!target_jq) return; if (target_jq.hasClass("name")) { target_jq.addClass("play"); var id = target.participantId; if (id == gClient.participantId) { openModal("#rename", "input[name=name]"); setTimeout(function () { $("#rename input[name=name]").val(gClient.ppl[gClient.participantId].name); $("#rename input[name=color]").val(gClient.ppl[gClient.participantId].color); }, 100); } else if (id) { var part = gClient.ppl[id] || null; if (part) { participantMenu(part); e.stopPropagation(); } } } }; var releasehandler = function (e) { $("#names .name").removeClass("play"); }; document.body.addEventListener("mouseup", releasehandler); document.body.addEventListener("touchend", releasehandler); var removeParticipantMenus = function () { $(".participant-menu").remove(); $(".participantSpotlight").hide(); document.removeEventListener("mousedown", removeParticipantMenus); document.removeEventListener("touchstart", removeParticipantMenus); }; var participantMenu = function (part) { if (!part) return; removeParticipantMenus(); document.addEventListener("mousedown", removeParticipantMenus); document.addEventListener("touchstart", removeParticipantMenus); $("#" + part.id).find(".enemySpotlight").show(); var menu = $('
'); $("body").append(menu); // move menu to name position var jq_nd = $(part.nameDiv); var pos = jq_nd.position(); menu.css({ "top": pos.top + jq_nd.height() + 15, "left": pos.left + 6, "background": part.color || "black" }); menu.on("mousedown touchstart", function (evt) { evt.stopPropagation(); var target = $(evt.target); if (target.hasClass("menu-item")) { target.addClass("clicked"); menu.fadeOut(200, function () { removeParticipantMenus(); }); } }); // this spaces stuff out but also can be used for informational $('
').appendTo(menu).text(part._id).on("mousedown touchstart", evt => { navigator.clipboard.writeText(part._id); evt.target.innerText = "Copied!"; }); // add menu items if (gPianoMutes.indexOf(part._id) == -1) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { gPianoMutes.push(part._id); if (localStorage) localStorage.pianoMutes = gPianoMutes.join(','); $(part.nameDiv).addClass("muted-notes"); }); } else { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var i; while ((i = gPianoMutes.indexOf(part._id)) != -1) gPianoMutes.splice(i, 1); if (localStorage) localStorage.pianoMutes = gPianoMutes.join(','); $(part.nameDiv).removeClass("muted-notes"); }); } if (gChatMutes.indexOf(part._id) == -1) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { gChatMutes.push(part._id); if (localStorage) localStorage.chatMutes = gChatMutes.join(','); $(part.nameDiv).addClass("muted-chat"); }); } else { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var i; while ((i = gChatMutes.indexOf(part._id)) != -1) gChatMutes.splice(i, 1); if (localStorage) localStorage.chatMutes = gChatMutes.join(','); $(part.nameDiv).removeClass("muted-chat"); }); } if (!(gPianoMutes.indexOf(part._id) >= 0) || !(gChatMutes.indexOf(part._id) >= 0)) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { gPianoMutes.push(part._id); if (localStorage) localStorage.pianoMutes = gPianoMutes.join(','); gChatMutes.push(part._id); if (localStorage) localStorage.chatMutes = gChatMutes.join(','); $(part.nameDiv).addClass("muted-notes"); $(part.nameDiv).addClass("muted-chat"); }); } if ((gPianoMutes.indexOf(part._id) >= 0) || (gChatMutes.indexOf(part._id) >= 0)) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var i; while ((i = gPianoMutes.indexOf(part._id)) != -1) gPianoMutes.splice(i, 1); while ((i = gChatMutes.indexOf(part._id)) != -1) gChatMutes.splice(i, 1); if (localStorage) localStorage.pianoMutes = gPianoMutes.join(','); if (localStorage) localStorage.chatMutes = gChatMutes.join(','); $(part.nameDiv).removeClass("muted-notes"); $(part.nameDiv).removeClass("muted-chat"); }); } if (gIsDming && gDmParticipant._id === part._id) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { gIsDming = false; $('#chat-input')[0].placeholder = 'You can chat with this thing.'; }); } else { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { if (!gKnowsHowToDm) { localStorage.knowsHowToDm = true; gKnowsHowToDm = true; new Notification({ target: '#piano', duration: 20000, title: 'How to DM', text: 'After you click the button to direct message someone, future chat messages will be sent to them instead of to everyone. To go back to talking in public chat, send a blank chat message, or click the button again.', }); } gIsDming = true; gDmParticipant = part; $('#chat-input')[0].placeholder = 'Direct messaging ' + part.name + '.'; }); } if (gCursorHides.indexOf(part._id) == -1) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { gCursorHides.push(part._id); if (localStorage) localStorage.cursorHides = gCursorHides.join(','); $(part.cursorDiv).hide(); }); } else { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var i; while ((i = gCursorHides.indexOf(part._id)) != -1) gCursorHides.splice(i, 1); if (localStorage) localStorage.cursorHides = gCursorHides.join(','); $(part.cursorDiv).show(); }); } $('').appendTo(menu) .on("mousedown touchstart", function (evt) { $('#chat-input')[0].value += '@' + part.id + ' '; setTimeout(() => { $('#chat-input').focus(); }, 1); }); if (gClient.isOwner() || gClient.permissions.chownAnywhere) { if (!gClient.channel.settings.lobby) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { if (confirm("Give room ownership to " + part.name + "?")) gClient.sendArray([{ m: "chown", id: part.id }]); }); } $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var minutes = prompt("How many minutes? (0-300)", "30"); if (minutes === null) return; minutes = parseFloat(minutes) || 0; var ms = minutes * 60 * 1000; gClient.sendArray([{ m: "kickban", _id: part._id, ms: ms }]); }); } if (gClient.permissions.siteBan) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { openModal("#siteban"); setTimeout(function () { $("#siteban input[name=id]").val(part._id); $("#siteban input[name=reasonText]").val("Discrimination against others"); $("#siteban input[name=reasonText]").attr("disabled", true); $("#siteban select[name=reasonSelect]").val("Discrimination against others"); $("#siteban input[name=durationNumber]").val(5); $("#siteban input[name=durationNumber]").attr("disabled", false); $("#siteban select[name=durationUnit]").val("hours"); $("#siteban textarea[name=note]").val(""); $("#siteban p[name=errorText]").text("") if (gClient.permissions.siteBanAnyReason) { $("#siteban select[name=reasonSelect] option[value=custom]").attr("disabled", false); } else { $("#siteban select[name=reasonSelect] option[value=custom]").attr("disabled", true); } }, 100); }); } if (gClient.permissions.usersetOthers) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var color = prompt("What color?", part.color); if (color === null) return; gClient.sendArray([{ m: "setcolor", _id: part._id, color: color }]); }); } if (gClient.permissions.usersetOthers) { $('').appendTo(menu) .on("mousedown touchstart", function (evt) { var name = prompt("What name?", part.name); if (name === null) return; gClient.sendArray([{ m: "setname", _id: part._id, name: name }]); }); } menu.fadeIn(100); }; })(); // Notification class //////////////////////////////////////////////////////////////// var Notification = function (par) { if (this instanceof Notification === false) throw ("yeet"); EventEmitter.call(this); var par = par || {}; this.id = "Notification-" + (par.id || Math.random()); this.title = par.title || ""; this.text = par.text || ""; this.html = par.html || ""; this.target = $(par.target || "#piano"); this.duration = par.duration || 30000; this["class"] = par["class"] || "classic"; var self = this; var eles = $("#" + this.id); if (eles.length > 0) { eles.remove(); } this.domElement = $('
' + '
X
'); this.domElement[0].id = this.id; this.domElement.addClass(this["class"]); this.domElement.find(".title").text(this.title); if (this.text.length > 0) { this.domElement.find(".text").text(this.text); } else if (this.html instanceof HTMLElement) { this.domElement.find(".text")[0].appendChild(this.html); } else if (this.html.length > 0) { this.domElement.find(".text").html(this.html); } document.body.appendChild(this.domElement.get(0)); this.position(); this.onresize = function () { self.position(); }; window.addEventListener("resize", this.onresize); this.domElement.find(".x").click(function () { self.close(); }); if (this.duration > 0) { setTimeout(function () { self.close(); }, this.duration); } return this; } mixin(Notification.prototype, EventEmitter.prototype); Notification.prototype.constructor = Notification; Notification.prototype.position = function () { var pos = this.target.offset(); var x = pos.left - (this.domElement.width() / 2) + (this.target.width() / 4); var y = pos.top - this.domElement.height() - 8; var width = this.domElement.width(); if (x + width > $("body").width()) { x -= ((x + width) - $("body").width()); } if (x < 0) x = 0; this.domElement.offset({ left: x, top: y }); }; Notification.prototype.close = function () { var self = this; window.removeEventListener("resize", this.onresize); this.domElement.fadeOut(500, function () { self.domElement.remove(); self.emit("close"); }); }; // set variables from settings or set settings //////////////////////////////////////////////////////////////// var gKeyboardSeq = 0; var gKnowsYouCanUseKeyboard = false; if (localStorage && localStorage.knowsYouCanUseKeyboard) gKnowsYouCanUseKeyboard = true; if (!gKnowsYouCanUseKeyboard) { window.gKnowsYouCanUseKeyboardTimeout = setTimeout(function () { window.gKnowsYouCanUseKeyboardNotification = new Notification({ title: "Did you know!?!", text: "You can play the piano with your keyboard, too. Try it!", target: "#piano", duration: 10000 }); }, 30000); } if (window.localStorage) { if (localStorage.volume) { volume_slider.value = localStorage.volume; gPiano.audio.setVolume(localStorage.volume); $("#volume-label").text("Volume: " + Math.floor(gPiano.audio.volume * 100) + "%"); } else localStorage.volume = gPiano.audio.volume; window.gHasBeenHereBefore = (localStorage.gHasBeenHereBefore || false); if (gHasBeenHereBefore) { } localStorage.gHasBeenHereBefore = true; } // warn user about loud noises before starting sound (no autoplay) openModal("#sound-warning"); $(document).off("keydown", modalHandleEsc); var user_interact = function (evt) { if ((evt.path || (evt.composedPath && evt.composedPath())).includes(document.getElementById('sound-warning')) || evt.target === document.getElementById('sound-warning')) { closeModal(); } document.removeEventListener("click", user_interact); gPiano.audio.resume(); } document.addEventListener("click", user_interact); // New room, change room //////////////////////////////////////////////////////////////// $("#room > .info").text("--"); gClient.on("ch", function (msg) { var channel = msg.ch; var info = $("#room > .info"); info.text(channel._id); if (channel.settings.lobby) info.addClass("lobby"); else info.removeClass("lobby"); if (!channel.settings.chat) info.addClass("no-chat"); else info.removeClass("no-chat"); if (channel.settings.crownsolo) info.addClass("crownsolo"); else info.removeClass("crownsolo"); if (channel.settings['no cussing']) info.addClass("no-cussing"); else info.removeClass("no-cussing"); if (!channel.settings.visible) info.addClass("not-visible"); else info.removeClass("not-visible"); }); gClient.on("ls", function (ls) { for (var i in ls.u) { if (!ls.u.hasOwnProperty(i)) continue; var room = ls.u[i]; var info = $("#room .info[roomid=\"" + (room.id + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0') + "\"]"); if (info.length == 0) { info = $("
"); info.attr("roomname", room._id); info.attr("roomid", room.id); $("#room .more").append(info); } info.text(room.count + '/' + ('limit' in room.settings ? room.settings.limit : 20) + " " + room._id); if (room.settings.lobby) info.addClass("lobby"); else info.removeClass("lobby"); if (!room.settings.chat) info.addClass("no-chat"); else info.removeClass("no-chat"); if (room.settings.crownsolo) info.addClass("crownsolo"); else info.removeClass("crownsolo"); if (room.settings['no cussing']) info.addClass("no-cussing"); else info.removeClass("no-cussing"); if (!room.settings.visible) info.addClass("not-visible"); else info.removeClass("not-visible"); if (room.banned) info.addClass("banned"); else info.removeClass("banned"); } }); $("#room").on("click", function (evt) { evt.stopPropagation(); // clicks on a new room if ($(evt.target).hasClass("info") && $(evt.target).parents(".more").length) { $("#room .more").fadeOut(250); var selected_name = $(evt.target).attr("roomname"); if (typeof selected_name != "undefined") { changeRoom(selected_name, "right"); } return false; } // clicks on "New Room..." else if ($(evt.target).hasClass("new")) { openModal("#new-room", "input[name=name]"); } // all other clicks var doc_click = function (evt) { if ($(evt.target).is("#room .more")) return; $(document).off("mousedown", doc_click); $("#room .more").fadeOut(250); gClient.sendArray([{ m: "-ls" }]); } $(document).on("mousedown", doc_click); $("#room .more .info").remove(); $("#room .more").show(); gClient.sendArray([{ m: "+ls" }]); }); $("#new-room-btn").on("click", function (evt) { evt.stopPropagation(); openModal("#new-room", "input[name=name]"); }); $("#play-alone-btn").on("click", function (evt) { evt.stopPropagation(); var room_name = "Room" + Math.floor(Math.random() * 1000000000000); changeRoom(room_name, "right", { "visible": false }); setTimeout(function () { new Notification({ id: "share", title: "Playing alone", html: 'You are playing alone in a room by yourself, but you can always invite friends by sending them the link.
' + location.href + '', duration: 25000 }); }, 1000); }); //Account button $("#account-btn").on("click", function (evt) { evt.stopPropagation(); openModal("#account"); if (gClient.accountInfo) { $("#account #account-info").show() if (gClient.accountInfo.type === "discord") { $("#account #avatar-image").prop("src", gClient.accountInfo.avatar) $("#account #logged-in-user-text").text(gClient.accountInfo.username + "#" + gClient.accountInfo.discriminator) } } else { $("#account #account-info").hide() } }); var gModal; function modalHandleEsc(evt) { if (evt.keyCode == 27) { closeModal(); if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); } }; function openModal(selector, focus) { if (chat) chat.blur(); releaseKeyboard(); $(document).on("keydown", modalHandleEsc); $("#modal #modals > *").hide(); $("#modal").fadeIn(250); $(selector).show(); setTimeout(function () { $(selector).find(focus).focus(); }, 100); gModal = selector; }; function closeModal() { $(document).off("keydown", modalHandleEsc); $("#modal").fadeOut(100); $("#modal #modals > *").hide(); captureKeyboard(); gModal = null; }; var modal_bg = $("#modal .bg")[0]; $(modal_bg).on("click", function (evt) { if (evt.target != modal_bg) return; closeModal(); }); (function () { function submit() { var name = $("#new-room .text[name=name]").val(); var settings = { visible: $("#new-room .checkbox[name=visible]").is(":checked"), chat: true }; $("#new-room .text[name=name]").val(""); closeModal(); changeRoom(name, "right", settings); setTimeout(function () { new Notification({ id: "share", title: "Created a Room", html: 'You can invite friends to your room by sending them the link.
' + location.href + '', duration: 25000 }); }, 1000); }; $("#new-room .submit").click(function (evt) { submit(); }); $("#new-room .text[name=name]").keypress(function (evt) { if (evt.keyCode == 13) { submit(); } else if (evt.keyCode == 27) { closeModal(); } else { return; } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); return false; }); })(); function changeRoom(name, direction, settings, push) { if (!settings) settings = {}; if (!direction) direction = "right"; if (typeof push == "undefined") push = true; var opposite = direction == "left" ? "right" : "left"; if (name == "") name = "lobby"; if (gClient.channel && gClient.channel._id === name) return; if (push) { var url = "/?c=" + encodeURIComponent(name).replace("'", "%27"); if (window.history && history.pushState) { history.pushState({ "depth": gHistoryDepth += 1, "name": name }, "Piano > " + name, url); } else { window.location = url; return; } } gClient.setChannel(name, settings); var t = 0, d = 100; $("#piano").addClass("ease-out").addClass("slide-" + opposite); setTimeout(function () { $("#piano").removeClass("ease-out").removeClass("slide-" + opposite).addClass("slide-" + direction); }, t += d); setTimeout(function () { $("#piano").addClass("ease-in").removeClass("slide-" + direction); }, t += d); setTimeout(function () { $("#piano").removeClass("ease-in"); }, t += d); }; var gHistoryDepth = 0; $(window).on("popstate", function (evt) { var depth = evt.state ? evt.state.depth : 0; //if (depth == gHistoryDepth) return; // <-- forgot why I did that though... //yeah brandon idk why you did that either, but it's stopping the back button from changing rooms after 1 click so I commented it out var direction = depth <= gHistoryDepth ? "left" : "right"; gHistoryDepth = depth; var name = getRoomNameFromURL(); changeRoom(name, direction, null, false); }); // Rename //////////////////////////////////////////////////////////////// (function () { function submit() { var set = { name: $("#rename input[name=name]").val(), color: $("#rename input[name=color]").val() }; //$("#rename .text[name=name]").val(""); closeModal(); gClient.sendArray([{ m: "userset", set: set }]); }; $("#rename .submit").click(function (evt) { submit(); }); $("#rename .text[name=name]").keypress(function (evt) { if (evt.keyCode == 13) { submit(); } else if (evt.keyCode == 27) { closeModal(); } else { return; } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); return false; }); })(); //site-wide bans (function () { function submit() { var msg = { m: "siteban" }; msg.id = $("#siteban .text[name=id]").val(); var durationUnit = $("#siteban select[name=durationUnit]").val(); if (durationUnit === "permanent") { if (!gClient.permissions.siteBanAnyDuration) { $("#siteban p[name=errorText]").text("You don't have permission to ban longer than 1 month. Contact a higher staff to ban the user for longer."); return; } msg.permanent = true; } else { var factor = 0; switch (durationUnit) { case "seconds": factor = 1000; break; case "minutes": factor = 1000 * 60; break; case "hours": factor = 1000 * 60 * 60; break; case "days": factor = 1000 * 60 * 60 * 24; break; case "weeks": factor = 1000 * 60 * 60 * 24 * 7; break; case "months": factor = 1000 * 60 * 60 * 24 * 30; break; case "years": factor = 1000 * 60 * 60 * 24 * 365; break; } var duration = factor * parseFloat($("#siteban input[name=durationNumber]").val()); if (duration < 0) { $("#siteban p[name=errorText]").text("Invalid duration."); return; } if (duration > 1000 * 60 * 60 * 24 * 30 && !gClient.permissions.siteBanAnyDuration) { $("#siteban p[name=errorText]").text("You don't have permission to ban longer than 1 month. Contact a higher staff to ban the user for longer."); return; } msg.duration = duration; } var reason; if ($("#siteban select[name=reasonSelect]").val() === "custom") { reason = $("#siteban .text[name=reasonText]").val(); if (reason.length === 0) { $("#siteban p[name=errorText]").text("Please provide a reason."); return; } } else { reason = $("#siteban select[name=reasonSelect]").val(); } msg.reason = reason; var note = $("#siteban textarea[name=note]").val(); if (note) { msg.note = note; } closeModal(); gClient.sendArray([msg]); }; $("#siteban .submit").click(function (evt) { submit(); }); $("#siteban select[name=reasonSelect]").change(function (evt) { if (this.value === "custom") { $("#siteban .text[name=reasonText]").attr("disabled", false); $("#siteban .text[name=reasonText]").val(""); } else { $("#siteban .text[name=reasonText]").attr("disabled", true); $("#siteban .text[name=reasonText]").val(this.value); } }); $("#siteban select[name=durationUnit]").change(function (evt) { if (this.value === "permanent") { $("#siteban .text[name=durationNumber]").attr("disabled", true); } else { $("#siteban .text[name=durationNumber]").attr("disabled", false); } }); $("#siteban .text[name=id]").keypress(textKeypressEvent); $("#siteban .text[name=reasonText]").keypress(textKeypressEvent); $("#siteban .text[name=note]").keypress(textKeypressEvent); function textKeypressEvent(evt) { if (evt.keyCode == 13) { submit(); } else if (evt.keyCode == 27) { closeModal(); } else { return; } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); return false; } })(); //Accounts (function () { function logout() { delete localStorage.token; gClient.stop(); gClient.start(); closeModal(); } $("#account .logout-btn").click(function (evt) { logout(); }); $("#account .login-discord").click(function (evt) { if (location.hostname === "localhost") { location.replace("https://discord.com/api/oauth2/authorize?client_id=926633278100877393&redirect_uri=http%3A%2F%2Flocalhost%2F%3Fcallback%3Ddiscord&response_type=code&scope=identify"); } else { location.replace("https://discord.com/api/oauth2/authorize?client_id=926633278100877393&redirect_uri=https%3A%2F%2Fmppclone.com%2F%3Fcallback%3Ddiscord&response_type=code&scope=identify"); } }); })(); // chatctor //////////////////////////////////////////////////////////////// var chat = (function () { gClient.on("ch", function (msg) { if (msg.ch.settings.chat) { chat.show(); } else { chat.hide(); } }); gClient.on("disconnect", function (msg) { }); gClient.on("c", function (msg) { chat.clear(); if (msg.c) { for (var i = 0; i < msg.c.length; i++) { chat.receive(msg.c[i]); } } }); gClient.on("a", function (msg) { chat.receive(msg); }); gClient.on("dm", function (msg) { chat.receive(msg); }); $("#chat input").on("focus", function (evt) { releaseKeyboard(); $("#chat").addClass("chatting"); chat.scrollToBottom(); }); /*$("#chat input").on("blur", function(evt) { captureKeyboard(); $("#chat").removeClass("chatting"); chat.scrollToBottom(); });*/ $(document).mousedown(function (evt) { if (!$("#chat").has(evt.target).length > 0) { chat.blur(); } }); document.addEventListener("touchstart", function (event) { for (var i in event.changedTouches) { var touch = event.changedTouches[i]; if (!$("#chat").has(touch.target).length > 0) { chat.blur(); } } }); $(document).on("keydown", function (evt) { if ($("#chat").hasClass("chatting")) { if (evt.keyCode == 27) { chat.blur(); if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); } else if (evt.keyCode == 13) { $("#chat input").focus(); } } else if (!gModal && (evt.keyCode == 27 || evt.keyCode == 13)) { $("#chat input").focus(); } }); $("#chat input").on("keydown", function (evt) { if (evt.keyCode == 13) { if (MPP.client.isConnected()) { var message = $(this).val(); if (message.length == 0) { if (gIsDming) { gIsDming = false; $('#chat-input')[0].placeholder = 'You can chat with this thing.'; } setTimeout(function () { chat.blur(); }, 100); } else { chat.send(message); $(this).val(""); setTimeout(function () { chat.blur(); }, 100); } } if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); } else if (evt.keyCode == 27) { chat.blur(); if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); } else if (evt.keyCode == 9) { if (!gNoPreventDefault) evt.preventDefault(); evt.stopPropagation(); } }); // Optionally show a warning when clicking links /*$("#chat ul").on("click", ".chatLink", function(ev) { var $s = $(this); if(gWarnOnLinks) { if(!$s.hasClass("clickedOnce")) { $s.addClass("clickedOnce"); var id = setTimeout(() => $s.removeClass("clickedOnce"), 2000); $s.data("clickTimeout", id) return false; } else { console.log("a") $s.removeClass("clickedOnce"); var id = $s.data("clickTimeout") if(id !== void 0) { clearTimeout(id) $s.removeData("clickTimeout") } } } });*/ return { show: function () { $("#chat").fadeIn(); }, hide: function () { $("#chat").fadeOut(); }, clear: function () { $("#chat li").remove(); }, scrollToBottom: function () { var ele = $("#chat ul").get(0); ele.scrollTop = ele.scrollHeight - ele.clientHeight; }, blur: function () { if ($("#chat").hasClass("chatting")) { $("#chat input").get(0).blur(); $("#chat").removeClass("chatting"); chat.scrollToBottom(); captureKeyboard(); } }, send: function (message) { if (gIsDming) { gClient.sendArray([{ m: 'dm', _id: gDmParticipant._id, message }]); } else { gClient.sendArray([{ m: "a", message }]); } }, receive: function (msg) { if (msg.m === 'dm') { if (gChatMutes.indexOf(msg.sender._id) != -1) return; } else { if (gChatMutes.indexOf(msg.p._id) != -1) return; } //construct string for creating list element var liString = '
  • '; var isSpecialDm = false; if (gShowTimestampsInChat) liString += ''; if (msg.m === 'dm') { if (msg.sender._id === gClient.user._id) { //sent dm liString += ''; } else if (msg.recipient._id === gClient.user._id) { //received dm liString += ''; } else { //someone else's dm liString += ''; isSpecialDm = true; } } if (isSpecialDm) { if (gShowIdsInChat) liString += ''; liString += ''; if (gShowIdsInChat) liString += ''; liString += ''; } else { if (gShowIdsInChat) liString += ''; liString += ''; } var li = $(liString); //prefix before dms so people know it's a dm if (msg.m === 'dm') { if (msg.sender._id === gClient.user._id) { //sent dm li.find(".sentDm").text('To'); li.find(".sentDm").css("color", '#ff55ff'); } else if (msg.recipient._id === gClient.user._id) { //received dm li.find(".receivedDm").text('From'); li.find(".receivedDm").css("color", '#ff55ff'); } else { //someone else's dm li.find(".otherDm").text('DM'); li.find(".otherDm").css("color", '#ff55ff'); li.find(".dmArrow").text('->'); li.find(".dmArrow").css("color", '#ff55ff'); } } if (gShowTimestampsInChat) { li.find(".timestamp").text(new Date(msg.t).toLocaleTimeString()); } const message = parseMarkdown(parseContent(msg.a), parseUrl).replace(/@([\da-f]{24})/g, (match, id) => { const user = gClient.ppl[id]; if (user) { const nick = parseContent(user.name); if (user.id === gClient.getOwnParticipant().id) { if (!tabIsActive) { youreMentioned = true; document.title = 'You were mentioned!'; } return `${nick}`; } else return `@${nick}`; } else return match; }); //apply names, colors, ids li.find(".message").html(message); if (msg.m === 'dm') { if (!gNoChatColors) li.find(".message").css("color", msg.sender.color || "white"); if (gShowIdsInChat) { if (msg.sender._id === gClient.user._id) { li.find(".id").text(msg.recipient._id.substring(0, 6)); } else { li.find(".id").text(msg.sender._id.substring(0, 6)); } } if (msg.sender._id === gClient.user._id) { //sent dm if (!gNoChatColors) li.find(".name").css("color", msg.recipient.color || "white"); li.find(".name").text(msg.recipient.name + ":"); if (gShowChatTooltips) li[0].title = msg.recipient._id; } else if (msg.recipient._id === gClient.user._id) { //received dm if (!gNoChatColors) li.find(".name").css("color", msg.sender.color || "white"); li.find(".name").text(msg.sender.name + ":"); if (gShowChatTooltips) li[0].title = msg.sender._id; } else { //someone else's dm if (!gNoChatColors) li.find(".name").css("color", msg.sender.color || "white"); if (!gNoChatColors) li.find(".name2").css("color", msg.recipient.color || "white"); li.find(".name").text(msg.sender.name); li.find(".name2").text(msg.recipient.name + ":"); if (gShowIdsInChat) li.find(".id").text(msg.sender._id.substring(0, 6)); if (gShowIdsInChat) li.find(".id2").text(msg.recipient._id.substring(0, 6)); if (gShowChatTooltips) li[0].title = msg.sender._id; } } else { if (!gNoChatColors) li.find(".message").css("color", msg.p.color || "white"); if (!gNoChatColors) li.find(".name").css("color", msg.p.color || "white"); li.find(".name").text(msg.p.name + ":"); if (!gNoChatColors) li.find(".message").css("color", msg.p.color || "white"); if (gShowIdsInChat) li.find(".id").text(msg.p._id.substring(0, 6)); if (gShowChatTooltips) li[0].title = msg.p._id; } //put list element in chat $("#chat ul").append(li); var eles = $("#chat ul li").get(); for (var i = 1; i <= 50 && i <= eles.length; i++) { eles[eles.length - i].style.opacity = 1.0 - (i * 0.03); } if (eles.length > 50) { eles[0].style.display = "none"; } if (eles.length > 256) { $(eles[0]).remove(); } // scroll to bottom if not "chatting" or if not scrolled up if (!$("#chat").hasClass("chatting")) { chat.scrollToBottom(); } else { var ele = $("#chat ul").get(0); if (ele.scrollTop > ele.scrollHeight - ele.offsetHeight - 50) chat.scrollToBottom(); } } }; })(); // MIDI //////////////////////////////////////////////////////////////// var MIDI_TRANSPOSE = -12; var MIDI_KEY_NAMES = ["a-1", "as-1", "b-1"]; var bare_notes = "c cs d ds e f fs g gs a as b".split(" "); for (var oct = 0; oct < 7; oct++) { for (var i in bare_notes) { MIDI_KEY_NAMES.push(bare_notes[i] + oct); } } MIDI_KEY_NAMES.push("c7"); var devices_json = "[]"; function sendDevices() { gClient.sendArray([{ "m": "devices", "list": JSON.parse(devices_json) }]); } gClient.on("connect", sendDevices); var pitchBends = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0 }; (function () { if (navigator.requestMIDIAccess) { navigator.requestMIDIAccess().then( function (midi) { //console.log(midi); function midimessagehandler(evt) { if (!evt.target.enabled) return; //console.log(evt); var channel = evt.data[0] & 0xf; var cmd = evt.data[0] >> 4; var note_number = evt.data[1]; var vel = evt.data[2]; //console.log(channel, cmd, note_number, vel); if (cmd == 8 || (cmd == 9 && vel == 0)) { // NOTE_OFF release(MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE + transpose + pitchBends[channel]]); } else if (cmd == 9) { // NOTE_ON if (evt.target.volume !== undefined) vel *= evt.target.volume; press(MIDI_KEY_NAMES[note_number - 9 + MIDI_TRANSPOSE + transpose + pitchBends[channel]], vel / 127); } else if (cmd == 11) { // CONTROL_CHANGE if (!gAutoSustain) { if (note_number == 64) { if (vel > 20) { pressSustain(); } else { releaseSustain(); } } } } else if (cmd == 14) { var pitchMod = evt.data[1] + (evt.data[2] << 7) - 0x2000; pitchMod = Math.round(pitchMod / 1000); pitchBends[channel] = pitchMod; } } function deviceInfo(dev) { return { type: dev.type, //id: dev.id, manufacturer: dev.manufacturer, name: dev.name, version: dev.version, //connection: dev.connection, //state: dev.state, enabled: dev.enabled, volume: dev.volume }; } function updateDevices() { var list = []; if (midi.inputs.size > 0) { var inputs = midi.inputs.values(); for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) { var input = input_it.value; list.push(deviceInfo(input)); } } if (midi.outputs.size > 0) { var outputs = midi.outputs.values(); for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) { var output = output_it.value; list.push(deviceInfo(output)); } } var new_json = JSON.stringify(list); if (new_json !== devices_json) { devices_json = new_json; sendDevices(); } } function plug() { if (midi.inputs.size > 0) { var inputs = midi.inputs.values(); for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) { var input = input_it.value; //input.removeEventListener("midimessage", midimessagehandler); //input.addEventListener("midimessage", midimessagehandler); input.onmidimessage = midimessagehandler; if (input.enabled !== false) { input.enabled = true; } if (typeof input.volume === "undefined") { input.volume = 1.0; } //console.log("input", input); } } if (midi.outputs.size > 0) { var outputs = midi.outputs.values(); for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) { var output = output_it.value; //output.enabled = false; // edit: don't touch if (typeof output.volume === "undefined") { output.volume = 1.0; } //console.log("output", output); } gMidiOutTest = function (note_name, vel, delay_ms, participantId) { if (!gOutputOwnNotes && participantId === gClient.participantId) return; var note_number = MIDI_KEY_NAMES.indexOf(note_name); if (note_number == -1) return; note_number = note_number + 9 - MIDI_TRANSPOSE; var outputs = midi.outputs.values(); for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) { var output = output_it.value; if (output.enabled) { var v = vel; if (output.volume !== undefined) v *= output.volume; output.send([0x90, note_number, v], window.performance.now() + delay_ms); } } } } showConnections(false); updateDevices(); } midi.addEventListener("statechange", function (evt) { if (evt instanceof MIDIConnectionEvent) { plug(); } }); plug(); var connectionsNotification; function showConnections(sticky) { //if(document.getElementById("Notification-MIDI-Connections")) //sticky = 1; // todo: instead, var inputs_ul = document.createElement("ul"); if (midi.inputs.size > 0) { var inputs = midi.inputs.values(); for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) { var input = input_it.value; var li = document.createElement("li"); li.connectionId = input.id; li.classList.add("connection"); if (input.enabled) li.classList.add("enabled"); li.textContent = input.name; li.addEventListener("click", function (evt) { var inputs = midi.inputs.values(); for (var input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) { var input = input_it.value; if (input.id === evt.target.connectionId) { input.enabled = !input.enabled; evt.target.classList.toggle("enabled"); //console.log("click", input); updateDevices(); return; } } }); if (gMidiVolumeTest) { var knob = document.createElement("canvas"); mixin(knob, { width: 16 * window.devicePixelRatio, height: 16 * window.devicePixelRatio, className: "knob" }); li.appendChild(knob); knob = new Knob(knob, 0, 2, 0.01, input.volume, "volume"); knob.canvas.style.width = "16px"; knob.canvas.style.height = "16px"; knob.canvas.style.float = "right"; knob.on("change", function (k) { input.volume = k.value; }); knob.emit("change", knob); } inputs_ul.appendChild(li); } } else { inputs_ul.textContent = "(none)"; } var outputs_ul = document.createElement("ul"); if (midi.outputs.size > 0) { var outputs = midi.outputs.values(); for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) { var output = output_it.value; var li = document.createElement("li"); li.connectionId = output.id; li.classList.add("connection"); if (output.enabled) li.classList.add("enabled"); li.textContent = output.name; li.addEventListener("click", function (evt) { var outputs = midi.outputs.values(); for (var output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) { var output = output_it.value; if (output.id === evt.target.connectionId) { output.enabled = !output.enabled; evt.target.classList.toggle("enabled"); //console.log("click", output); updateDevices(); return; } } }); if (gMidiVolumeTest) { var knob = document.createElement("canvas"); mixin(knob, { width: 16 * window.devicePixelRatio, height: 16 * window.devicePixelRatio, className: "knob" }); li.appendChild(knob); knob = new Knob(knob, 0, 2, 0.01, output.volume, "volume"); knob.canvas.style.width = "16px"; knob.canvas.style.height = "16px"; knob.canvas.style.float = "right"; knob.on("change", function (k) { output.volume = k.value; }); knob.emit("change", knob); } outputs_ul.appendChild(li); } } else { outputs_ul.textContent = "(none)"; } var div = document.createElement("div"); var h1 = document.createElement("h1"); h1.textContent = "Inputs"; div.appendChild(h1); div.appendChild(inputs_ul); h1 = document.createElement("h1"); h1.textContent = "Outputs"; div.appendChild(h1); div.appendChild(outputs_ul); connectionsNotification = new Notification({ "id": "MIDI-Connections", "title": "MIDI Connections", "duration": sticky ? "-1" : "4500", "html": div, "target": "#midi-btn" }); } document.getElementById("midi-btn").addEventListener("click", function (evt) { if (!document.getElementById("Notification-MIDI-Connections")) showConnections(true); else { connectionsNotification.close(); } }); }, function (err) { //console.log(err); }); } })(); // bug supply //////////////////////////////////////////////////////////////// window.onerror = function (message, url, line) { /*var url = url || "(no url)"; var line = line || "(no line)"; // errors in socket.io if(url.indexOf("socket.io.js") !== -1) { if(message.indexOf("INVALID_STATE_ERR") !== -1) return; if(message.indexOf("InvalidStateError") !== -1) return; if(message.indexOf("DOM Exception 11") !== -1) return; if(message.indexOf("Property 'open' of object # is not a function") !== -1) return; if(message.indexOf("Cannot call method 'close' of undefined") !== -1) return; if(message.indexOf("Cannot call method 'close' of null") !== -1) return; if(message.indexOf("Cannot call method 'onClose' of null") !== -1) return; if(message.indexOf("Cannot call method 'payload' of null") !== -1) return; if(message.indexOf("Unable to get value of the property 'close'") !== -1) return; if(message.indexOf("NS_ERROR_NOT_CONNECTED") !== -1) return; if(message.indexOf("Unable to get property 'close' of undefined or null reference") !== -1) return; if(message.indexOf("Unable to get value of the property 'close': object is null or undefined") !== -1) return; if(message.indexOf("this.transport is null") !== -1) return; } // errors in soundmanager2 if(url.indexOf("soundmanager2.js") !== -1) { // operation disabled in safe mode? if(message.indexOf("Could not complete the operation due to error c00d36ef") !== -1) return; if(message.indexOf("_s.o._setVolume is not a function") !== -1) return; } // errors in midibridge if(url.indexOf("midibridge") !== -1) { if(message.indexOf("Error calling method on NPObject") !== -1) return; } // too many failing extensions injected in my html if(url.indexOf(".js") !== url.length - 3) return; // extensions inject cross-domain embeds too if(url.toLowerCase().indexOf("multiplayerpiano.com") == -1) return; // errors in my code if(url.indexOf("script.js") !== -1) { if(message.indexOf("Object [object Object] has no method 'on'") !== -1) return; if(message.indexOf("Object [object Object] has no method 'off'") !== -1) return; if(message.indexOf("Property '$' of object [object Object] is not a function") !== -1) return; } var enc = "/bugreport/" + (message ? encodeURIComponent(message) : "") + "/" + (url ? encodeURIComponent(url) : "") + "/" + (line ? encodeURIComponent(line) : ""); var img = new Image(); img.src = enc;*/ }; // API window.MPP = { get press() { return press }, set press(func) { press = func }, get release() { return release }, set release(func) { release = func }, get pressSustain() { return pressSustain }, set pressSustain(func) { pressSustain = func }, get releaseSustain() { return releaseSustain }, set releaseSustain(func) { releaseSustain = func }, piano: gPiano, client: gClient, chat: chat, noteQuota: gNoteQuota, soundSelector: gSoundSelector, Notification: Notification }; // synth var enableSynth = false; var audio = gPiano.audio; var context = gPiano.audio.context; var synth_gain = context.createGain(); synth_gain.gain.value = 0.05; synth_gain.connect(audio.synthGain); var osc_types = ["sine", "square", "sawtooth", "triangle"]; var osc_type_index = 1; var osc1_type = "square"; var osc1_attack = 0; var osc1_decay = 0.2; var osc1_sustain = 0.5; var osc1_release = 2.0; function synthVoice(note_name, time) { var note_number = MIDI_KEY_NAMES.indexOf(note_name); note_number = note_number + 9 - MIDI_TRANSPOSE; var freq = Math.pow(2, (note_number - 69) / 12) * 440.0; this.osc = context.createOscillator(); this.osc.type = osc1_type; this.osc.frequency.value = freq; this.gain = context.createGain(); this.gain.gain.value = 0; this.osc.connect(this.gain); this.gain.connect(synth_gain); this.osc.start(time); this.gain.gain.setValueAtTime(0, time); this.gain.gain.linearRampToValueAtTime(1, time + osc1_attack); this.gain.gain.linearRampToValueAtTime(osc1_sustain, time + osc1_attack + osc1_decay); } synthVoice.prototype.stop = function (time) { //this.gain.gain.setValueAtTime(osc1_sustain, time); this.gain.gain.linearRampToValueAtTime(0, time + osc1_release); this.osc.stop(time + osc1_release); }; (function () { var button = document.getElementById("synth-btn"); var notification; button.addEventListener("click", function () { if (notification) { notification.close(); } else { showSynth(); } }); function showSynth() { var html = document.createElement("div"); // on/off button (function () { var button = document.createElement("input"); mixin(button, { type: "button", value: "ON/OFF", className: enableSynth ? "switched-on" : "switched-off" }); button.addEventListener("click", function (evt) { enableSynth = !enableSynth; button.className = enableSynth ? "switched-on" : "switched-off"; if (!enableSynth) { // stop all for (var i in audio.playings) { if (!audio.playings.hasOwnProperty(i)) continue; var playing = audio.playings[i]; if (playing && playing.voice) { playing.voice.osc.stop(); playing.voice = undefined; } } } }); html.appendChild(button); })(); // mix var knob = document.createElement("canvas"); mixin(knob, { width: 32 * window.devicePixelRatio, height: 32 * window.devicePixelRatio, className: "knob" }); html.appendChild(knob); knob = new Knob(knob, 0, 100, 0.1, 50, "mix", "%"); knob.canvas.style.width = "32px"; knob.canvas.style.height = "32px"; knob.on("change", function (k) { var mix = k.value / 100; audio.pianoGain.gain.value = 1 - mix; audio.synthGain.gain.value = mix; }); knob.emit("change", knob); // osc1 type (function () { osc1_type = osc_types[osc_type_index]; var button = document.createElement("input"); mixin(button, { type: "button", value: osc_types[osc_type_index] }); button.addEventListener("click", function (evt) { if (++osc_type_index >= osc_types.length) osc_type_index = 0; osc1_type = osc_types[osc_type_index]; button.value = osc1_type; }); html.appendChild(button); })(); // osc1 attack var knob = document.createElement("canvas"); mixin(knob, { width: 32 * window.devicePixelRatio, height: 32 * window.devicePixelRatio, className: "knob" }); html.appendChild(knob); knob = new Knob(knob, 0, 1, 0.001, osc1_attack, "osc1 attack", "s"); knob.canvas.style.width = "32px"; knob.canvas.style.height = "32px"; knob.on("change", function (k) { osc1_attack = k.value; }); knob.emit("change", knob); // osc1 decay var knob = document.createElement("canvas"); mixin(knob, { width: 32 * window.devicePixelRatio, height: 32 * window.devicePixelRatio, className: "knob" }); html.appendChild(knob); knob = new Knob(knob, 0, 2, 0.001, osc1_decay, "osc1 decay", "s"); knob.canvas.style.width = "32px"; knob.canvas.style.height = "32px"; knob.on("change", function (k) { osc1_decay = k.value; }); knob.emit("change", knob); var knob = document.createElement("canvas"); mixin(knob, { width: 32 * window.devicePixelRatio, height: 32 * window.devicePixelRatio, className: "knob" }); html.appendChild(knob); knob = new Knob(knob, 0, 1, 0.001, osc1_sustain, "osc1 sustain", "x"); knob.canvas.style.width = "32px"; knob.canvas.style.height = "32px"; knob.on("change", function (k) { osc1_sustain = k.value; }); knob.emit("change", knob); // osc1 release var knob = document.createElement("canvas"); mixin(knob, { width: 32 * window.devicePixelRatio, height: 32 * window.devicePixelRatio, className: "knob" }); html.appendChild(knob); knob = new Knob(knob, 0, 2, 0.001, osc1_release, "osc1 release", "s"); knob.canvas.style.width = "32px"; knob.canvas.style.height = "32px"; knob.on("change", function (k) { osc1_release = k.value; }); knob.emit("change", knob); //useless blank space //var div = document.createElement("div"); //div.innerHTML = "



    this space intentionally left blank




    "; //html.appendChild(div); // notification notification = new Notification({ title: "Synthesize", html: html, duration: -1, target: "#synth-btn" }); notification.on("close", function () { var tip = document.getElementById("tooltip"); if (tip) tip.parentNode.removeChild(tip); notification = null; }); } })(); (function () { if (window.location.hostname === "multiplayerpiano.com") { var button = document.getElementById("client-settings-btn"); var notification; button.addEventListener("click", function () { if (notification) { notification.close(); } else { showSynth(); } }); function showSynth() { var html = document.createElement("div"); // show ids in chat (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Show user IDs in chat"; if (gShowIdsInChat) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.showIdsInChat = setting.classList.contains("enabled"); gShowIdsInChat = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); // show timestamps in chat (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Timestamps in chat"; if (gShowTimestampsInChat) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.showTimestampsInChat = setting.classList.contains("enabled"); gShowTimestampsInChat = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); // no chat colors (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "No chat colors"; if (gNoChatColors) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.noChatColors = setting.classList.contains("enabled"); gNoChatColors = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); // no background color (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Force dark background"; if (gNoBackgroundColor) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.noBackgroundColor = setting.classList.contains("enabled"); gNoBackgroundColor = setting.classList.contains("enabled"); if (gClient.channel.settings.color && !gNoBackgroundColor) { setBackgroundColor(gClient.channel.settings.color, gClient.channel.settings.color2); } else { setBackgroundColorToDefault(); } }; html.appendChild(setting); })(); // output own notes (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Output own notes to MIDI"; if (gOutputOwnNotes) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.outputOwnNotes = setting.classList.contains("enabled"); gOutputOwnNotes = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); // virtual piano layout (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Virtual Piano layout"; if (gVirtualPianoLayout) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.virtualPianoLayout = setting.classList.contains("enabled"); gVirtualPianoLayout = setting.classList.contains("enabled"); key_binding = gVirtualPianoLayout ? layouts.VP : layouts.MPP; }; html.appendChild(setting); })(); // gShowChatTooltips // Show chat tooltips for _ids. (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Show _id tooltips"; if (gShowChatTooltips) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.showChatTooltips = setting.classList.contains("enabled"); gShowChatTooltips = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Show Piano Notes"; if (gShowPianoNotes) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.showPianoNotes = setting.classList.contains("enabled"); gShowPianoNotes = setting.classList.contains("enabled"); }; html.appendChild(setting); })(); // Enable smooth cursors. (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Enable smooth cursors"; if (gSmoothCursor) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.smoothCursor = setting.classList.contains("enabled"); gSmoothCursor = setting.classList.contains("enabled"); if (gSmoothCursor) { $("#cursors").attr('smooth-cursors', ''); } else { $("#cursors").removeAttr('smooth-cursors'); } if (gSmoothCursor) { Object.values(gClient.ppl).forEach(function (participant) { if (participant.cursorDiv) { participant.cursorDiv.style.left = ''; participant.cursorDiv.style.top = ''; participant.cursorDiv.style.transform = 'translate3d(' + participant.x + 'vw, ' + participant.y + 'vh, 0)'; } }); } else { Object.values(gClient.ppl).forEach(function (participant) { if (participant.cursorDiv) { participant.cursorDiv.style.left = participant.x + "%"; participant.cursorDiv.style.top = participant.y + "%"; participant.cursorDiv.style.transform = ''; } }); } }; html.appendChild(setting); })(); (function () { var setting = document.createElement("select"); setting.classList = "setting"; setting.style = "color: inherit; width: calc(100% - 2px);" const keys = Object.keys(BASIC_PIANO_SCALES); // lol const option = document.createElement('option'); option.value = option.innerText = "No highlighted notes"; option.selected = !gHighlightScaleNotes; setting.appendChild(option); for (const key of keys) { const option = document.createElement('option'); option.value = key; option.innerText = key; option.selected = key === gHighlightScaleNotes; setting.appendChild(option); } if (gHighlightScaleNotes) { setting.value = gHighlightScaleNotes; } setting.onchange = function () { localStorage.highlightScaleNotes = setting.value; gHighlightScaleNotes = setting.value; }; html.appendChild(setting); })(); (function () { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Hide all cursors"; if (gHideAllCursors) { setting.classList.toggle("enabled"); } setting.onclick = function () { setting.classList.toggle("enabled"); localStorage.hideAllCursors = setting.classList.contains("enabled"); gHideAllCursors = setting.classList.contains("enabled"); if (gHideAllCursors) { $("#cursors").hide(); } else { $("#cursors").show(); } }; html.appendChild(setting); })(); // warn on links /*(function() { var setting = document.createElement("div"); setting.classList = "setting"; setting.innerText = "Warn when clicking links"; if (gWarnOnLinks) { setting.classList.toggle("enabled"); } setting.onclick = function() { setting.classList.toggle("enabled"); localStorage.warnOnLinks = setting.classList.contains("enabled"); gWarnOnLinks = setting.classList.contains("enabled"); }; html.appendChild(setting); })();*/ //useless blank space //var div = document.createElement("div"); //div.innerHTML = "



    this space intentionally left blank




    "; //html.appendChild(div); // notification notification = new Notification({ title: "Client Settings", html: html, duration: -1, target: "#client-settings-btn" }); notification.on("close", function () { var tip = document.getElementById("tooltip"); if (tip) tip.parentNode.removeChild(tip); notification = null; }); } } else { var button = document.getElementById("client-settings-btn"); var content = document.getElementById("client-settings-content"); var tablinks = document.getElementsByClassName("client-settings-tablink"); var okButton = document.getElementById("client-settings-ok-btn"); button.addEventListener("click", (evt) => { evt.stopPropagation(); openModal("#client-settings"); }); okButton.addEventListener("click", (evt) => { evt.stopPropagation(); closeModal(); }); function createSetting(id, labelText, isChecked, addBr, html, onclickFunc) { const setting = document.createElement("input"); setting.type = "checkbox"; setting.id = id; setting.checked = isChecked; setting.onclick = onclickFunc; const label = document.createElement("label"); label.innerText = labelText + ": "; label.appendChild(setting); html.appendChild(label); if (addBr) html.appendChild(document.createElement("br")); } window.changeClientSettingsTab = (evt, tabName) => { content.innerHTML = ""; for (let index = 0; index < tablinks.length; index++) { tablinks[index].className = tablinks[index].className.replace(" active", ""); } evt.currentTarget.className += " active"; switch (tabName.toLowerCase()) { case "chat": var html = document.createElement("div"); createSetting("show-timestamps-in-chat", "Show timestamps in chat", gShowTimestampsInChat, true, html, () => { gShowTimestampsInChat = !gShowTimestampsInChat; localStorage.showTimestampsInChat = gShowTimestampsInChat; }); createSetting("show-user-ids-in-chat", "Show user IDs in chat", gShowIdsInChat, true, html, () => { gShowIdsInChat = !gShowIdsInChat; localStorage.showIdsInChat = gShowIdsInChat; }); createSetting("show-id-tooltips", "Show ID tooltips", gShowChatTooltips, true, html, () => { gShowChatTooltips = !gShowChatTooltips; localStorage.showChatTooltips = gShowChatTooltips; }); createSetting("no-chat-colors", "No chat colors", gNoChatColors, true, html, () => { gNoChatColors = !gNoChatColors; localStorage.noChatColors = gNoChatColors; }); createSetting("hide-chat", "Hide chat", gHideChat, false, html, () => { gHideChat = !gHideChat; localStorage.hideChat = gHideChat; if (gHideChat) { $("#chat").hide(); } else { $("#chat").show(); } }); content.appendChild(html); break; case "midi": var html = document.createElement("div"); createSetting("output-own-notes-to-midi", "Output own notes to MIDI", gOutputOwnNotes, false, html, () => { gOutputOwnNotes = !gOutputOwnNotes; localStorage.outputOwnNotes = gOutputOwnNotes; }); content.appendChild(html); break; case "piano": var html = document.createElement("div"); createSetting("virtual-piano-layout", "Virtual Piano layout", gVirtualPianoLayout, true, html, () => { gVirtualPianoLayout = !gVirtualPianoLayout; localStorage.virtualPianoLayout = gVirtualPianoLayout; key_binding = gVirtualPianoLayout ? layouts.VP : layouts.MPP; }); createSetting("show-piano-notes", "Show piano notes", gShowPianoNotes, true, html, () => { gShowPianoNotes = !gShowPianoNotes; localStorage.showPianoNotes = gShowPianoNotes; }); createSetting("hide-piano", "Hide piano", gHidePiano, true, html, () => { gHidePiano = !gHidePiano; localStorage.hidePiano = gHidePiano; if (gHidePiano) { $("#piano").hide(); } else { $("#piano").show(); } }); var setting = document.createElement("select"); setting.classList = "setting"; setting.style = "width: calc(58.7% - 2px);" setting.onchange = () => { localStorage.highlightScaleNotes = setting.value; gHighlightScaleNotes = setting.value; } const keys = Object.keys(BASIC_PIANO_SCALES); // lol const option = document.createElement('option'); option.value = option.innerText = "None"; option.selected = !gHighlightScaleNotes; setting.appendChild(option); for (const key of keys) { const option = document.createElement('option'); option.value = key; option.innerText = key; option.selected = key === gHighlightScaleNotes; setting.appendChild(option); } if (gHighlightScaleNotes) { setting.value = gHighlightScaleNotes; } var label = document.createElement("label"); label.setAttribute("for", setting.id); label.innerText = "Highlighted notes: "; html.appendChild(label); html.appendChild(setting); content.appendChild(html); break; case "misc": var html = document.createElement("div"); createSetting("dont-use-prevent-default", "Don't use prevent default", gNoChatColors, true, html, () => { gNoPreventDefault = !gNoPreventDefault; localStorage.noPreventDefault = noPreventDefault; }); createSetting("force-dark-background", "Force dark background", gNoBackgroundColor, true, html, () => { gNoBackgroundColor = !gNoBackgroundColor; localStorage.noBackgroundColor = gNoBackgroundColor; if (gClient.channel.settings.color && !gNoBackgroundColor) { setBackgroundColor(gClient.channel.settings.color, gClient.channel.settings.color2); } else { setBackgroundColorToDefault(); } }); createSetting("enable-smooth-cursors", "Enable smooth cursors", gSmoothCursor, true, html, () => { gSmoothCursor = !gSmoothCursor; localStorage.smoothCursor = gSmoothCursor; if (gSmoothCursor) { $("#cursors").attr("smooth-cursors", ""); Object.values(gClient.ppl).forEach(function (participant) { if (participant.cursorDiv) { participant.cursorDiv.style.left = ''; participant.cursorDiv.style.top = ''; participant.cursorDiv.style.transform = 'translate3d(' + participant.x + 'vw, ' + participant.y + 'vh, 0)'; } }); } else { $("#cursors").removeAttr("smooth-cursors"); Object.values(gClient.ppl).forEach(function (participant) { if (participant.cursorDiv) { participant.cursorDiv.style.left = participant.x + "%"; participant.cursorDiv.style.top = participant.y + "%"; participant.cursorDiv.style.transform = ''; } }); } }); createSetting("hide-all-cursors", "Hide all cursors", gHideAllCursors, true, html, () => { gHideAllCursors = !gHideAllCursors; localStorage.hideAllCursors = gHideAllCursors; if (gHideAllCursors) { $("#cursors").hide(); } else { $("#cursors").show(); } }); content.appendChild(html); break; } } changeClientSettingsTab({currentTarget: document.getElementsByClassName("client-settings-tablink")[0]}, "Chat"); } })(); //confetti, to be removed after the 10th anniversary //source: https://www.cssscript.com/confetti-falling-animation/ var maxParticleCount = 500; //set max confetti count var particleSpeed = 2; //set the particle animation speed var startConfetti; //call to start confetti animation var stopConfetti; //call to stop adding confetti var toggleConfetti; //call to start or stop the confetti animation depending on whether it's already running var removeConfetti; //call to stop the confetti animation and remove all confetti immediately (function () { startConfetti = startConfettiInner; stopConfetti = stopConfettiInner; toggleConfetti = toggleConfettiInner; removeConfetti = removeConfettiInner; var colors = ["DodgerBlue", "OliveDrab", "Gold", "Pink", "SlateBlue", "LightBlue", "Violet", "PaleGreen", "SteelBlue", "SandyBrown", "Chocolate", "Crimson"] var streamingConfetti = false; var animationTimer = null; var particles = []; var waveAngle = 0; function resetParticle(particle, width, height) { particle.color = colors[(Math.random() * colors.length) | 0]; particle.x = Math.random() * width; particle.y = Math.random() * height - height; particle.diameter = Math.random() * 10 + 5; particle.tilt = Math.random() * 10 - 10; particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05; particle.tiltAngle = 0; return particle; } function startConfettiInner() { var width = window.innerWidth; var height = window.innerHeight; window.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { return window.setTimeout(callback, 16.6666667); }; })(); var canvas = document.getElementById("confetti-canvas"); if (canvas === null) { canvas = document.createElement("canvas"); canvas.setAttribute("id", "confetti-canvas"); canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:absolute;top:0;left:0"); document.body.appendChild(canvas); canvas.width = width; canvas.height = height; window.addEventListener("resize", function () { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }, true); } var context = canvas.getContext("2d"); while (particles.length < maxParticleCount) particles.push(resetParticle({}, width, height)); streamingConfetti = true; if (animationTimer === null) { (function runAnimation() { context.clearRect(0, 0, window.innerWidth, window.innerHeight); if (particles.length === 0) animationTimer = null; else { updateParticles(); drawParticles(context); animationTimer = requestAnimFrame(runAnimation); } })(); } } function stopConfettiInner() { streamingConfetti = false; } function removeConfettiInner() { stopConfetti(); particles = []; } function toggleConfettiInner() { if (streamingConfetti) stopConfettiInner(); else startConfettiInner(); } function drawParticles(context) { var particle; var x; for (var i = 0; i < particles.length; i++) { particle = particles[i]; context.beginPath(); context.lineWidth = particle.diameter; context.strokeStyle = particle.color; context.shadowColor = 'rgba(0, 0, 0, .3)'; context.shadowBlur = 4; context.shadowOffsetY = 2; context.shadowOffsetX = 0; x = particle.x + particle.tilt; context.moveTo(x + particle.diameter / 2, particle.y); context.lineTo(x, particle.y + particle.tilt + particle.diameter / 2); context.stroke(); } } function updateParticles() { var width = window.innerWidth; var height = window.innerHeight; var particle; waveAngle += 0.01; for (var i = 0; i < particles.length; i++) { particle = particles[i]; if (!streamingConfetti && particle.y < -15) particle.y = height + 100; else { particle.tiltAngle += particle.tiltAngleIncrement; particle.x += Math.sin(waveAngle); particle.y += (Math.cos(waveAngle) + particle.diameter + particleSpeed) * 0.5; particle.tilt = Math.sin(particle.tiltAngle) * 15; } if (particle.x > width + 20 || particle.x < -20 || particle.y > height) { if (streamingConfetti && particles.length <= maxParticleCount) resetParticle(particle, width, height); else { particles.splice(i, 1); i--; } } } } })(); if (window !== top) { //alert("Hey, it looks like you're visiting our site through another website. Consider playing Multiplayer Piano directly at https://mppclone.com") } gClient.start(); }); // misc //////////////////////////////////////////////////////////////// // non-ad-free experience /*(function() { function adsOn() { if(window.localStorage) { var div = document.querySelector("#inclinations"); div.innerHTML = "Ads:
    ON / OFF"; div.querySelector("#adsoff").addEventListener("click", adsOff); localStorage.ads = true; } // adsterra var script = document.createElement("script"); script.src = "//pl132070.puhtml.com/68/7a/97/687a978dd26d579c788cb41e352f5a41.js"; document.head.appendChild(script); } function adsOff() { if(window.localStorage) localStorage.ads = false; document.location.reload(true); } function noAds() { var div = document.querySelector("#inclinations"); div.innerHTML = "Ads:
    ON / OFF"; div.querySelector("#adson").addEventListener("click", adsOn); } if(window.localStorage) { if(localStorage.ads === undefined || localStorage.ads === "true") adsOn(); else noAds(); } else { adsOn(); } })();*/