mpp-dev-but-mppnet/public/script.js

5287 lines
165 KiB
JavaScript
Raw Permalink Normal View History

2024-02-04 02:23:40 +01:00
const translation = window.i18nextify.init({
autorun: false,
});
if (location.host === "multiplayerpiano.com") {
const url = new URL("https://multiplayerpiano.net/" + location.search);
if (localStorage.token) url.searchParams.set("token", localStorage.token);
location.replace(url);
throw new Error("Redirecting to multiplayerpiano.net");
}
if (location.host === "multiplayerpiano.net") {
const url = new URL(location.href);
const token = url.searchParams.get("token");
if (token) {
localStorage.token = token;
url.searchParams.delete("token");
location.replace(url);
throw new Error("Finalizing redirect.");
}
}
// 钢琴
$(function () {
translation.start();
console.log(
"%cWelcome to MPP's developer console!",
"color:blue; font-size:20px;",
);
console.log(
"%cCheck out the source code: https://github.com/mppnet/frontend/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♭"],
};
// 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) * alpha;
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) * alpha;
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 ♥
////////////////////////////////////////////////////////////////
if (window.location.hostname === "localhost") {
var soundDomain = `http://${location.host}`;
} else {
var soundDomain = "https://multiplayerpiano.net";
}
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("<p>Current Sound: " + self.soundSelection + "</p>");
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";
pack.html.setAttribute("translated", "");
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/Emotional/",
"/sounds/Emotional_2.0/",
"/sounds/GreatAndSoftPiano/",
"/sounds/HardAndToughPiano/",
"/sounds/HardPiano/",
"/sounds/Harp/",
"/sounds/Harpsicord/",
"/sounds/LoudAndProudPiano/",
"/sounds/MLG/",
"/sounds/Music_Box/",
"/sounds/NewPiano/",
"/sounds/Orchestra/",
"/sounds/Piano2/",
"/sounds/PianoSounds/",
"/sounds/Rhodes_MK1/",
"/sounds/SoftPiano/",
"/sounds/Steinway_Grand/",
"/sounds/Untitled/",
"/sounds/Vintage_Upright/",
"/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";
}
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
var gClient = new Client("ws://"+window.location.hostname+":8443");
} else {
var gClient = new Client("wss://"+location.hostname);
}
if (loginInfo) {
gClient.setLoginInfo(loginInfo);
}
gClient.setChannel(channel_id);
gClient.on("disconnect", function (evt) {
//console.log(evt);
});
var tabIsActive = true;
var youreMentioned = false;
var youreReplied = false;
window.addEventListener("focus", function (event) {
tabIsActive = true;
youreMentioned = false;
youreReplied = 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(
'<span class="number" translated>' +
count +
"</span> " +
window.i18nextify.i18next.t("people are playing", { count }),
);
if (!tabIsActive) {
if (youreMentioned || youreReplied) {
return;
}
}
document.title = "Piano (" + count + ")";
} else {
document.title = "Multiplayer Piano";
}
});
})();
// Show moderator buttons
(function () {
let receivedHi = false;
gClient.on("hi", function (msg) {
if (receivedHi) return;
receivedHi = true;
if (!msg.motd)
msg.motd =
"This site makes a lot of sound! You may want to adjust the volume before continuing.";
document.getElementById("motd-text").innerHTML = msg.motd;
openModal("#motd");
$(document).off("keydown", modalHandleEsc);
var user_interact = function (evt) {
if (
(evt.path || (evt.composedPath && evt.composedPath())).includes(
document.getElementById("motd"),
) ||
evt.target === document.getElementById("motd")
) {
closeModal();
}
document.removeEventListener("click", user_interact);
gPiano.audio.resume();
};
document.addEventListener("click", user_interact);
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);
}
if (part.afk) {
var afkDiv = document.createElement("div");
afkDiv.className = "nametag";
afkDiv.textContent = "AFK";
afkDiv.style.backgroundColor = "#00000040";
afkDiv.style["margin-left"] = "5px";
afkDiv.style["margin-right"] = "0px";
afkDiv.style.float = "right";
afkDiv.id = "afktag-" + part._id;
part.nameDiv.appendChild(afkDiv);
}
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);
part.nameDiv.setAttribute("translated", "");
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) {
if (shouldHideUser(part)) return;
part.displayX = 150;
part.displayY = 50;
var tagText = typeof part.tag === "object" ? part.tag.text : part.tag;
// 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";
if (part.tag) {
var tagDiv = document.createElement("span");
tagDiv.className = "curtag";
tagDiv.textContent = tagText || "";
tagDiv.style.backgroundColor = tagColor(part.tag);
tagDiv.id = "nametag-" + part._id;
div.appendChild(tagDiv);
}
var namep = document.createElement("span");
namep.className = "nametext";
namep.textContent = part.name || "";
// namep.style.backgroundColor = part.color || "#777"
div.setAttribute("translated", "");
div.appendChild(namep);
part.cursorDiv.appendChild(div);
} else {
part.cursorDiv = undefined;
}
});
gClient.on("participant removed", function (part) {
if (shouldHideUser(part)) return;
// 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) {
if (shouldHideUser(part)) return;
var name = part.name || "";
var color = part.color || "#777";
setupParticipantDivs(part);
$(part.cursorDiv).find(".name .nametext").text(name);
$(part.cursorDiv).find(".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) {
if (shouldHideUser(part)) return;
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 (shouldHideUser(part)) return;
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 = $('<div id="crown"></div>').appendTo(document.body).hide();
var jqcountdown = $("<span></span>").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 .checkbox[name=noindex]").prop(
"checked",
settings.noindex,
);
$("#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",
),
noindex: $("#room-settings .checkbox[name=noindex]").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 += '<p>This room is set to "only the owner can play."</p>';
}
if (msg.ch.settings["no cussing"]) {
has_notice = true;
notice += '<p>This room is set to "no cussing."</p>';
}
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 == "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 gHideBotUsers = localStorage.hideBotUsers == "true";
var gSnowflakes =
new Date().getMonth() === 11 && localStorage.snowflakes !== "false";
// var gWarnOnLinks = localStorage.warnOnLinks ? loalStorage.warnOnLinks == "true" : true;
var gDisableMIDIDrumChannel = localStorage.disableMIDIDrumChannel
? localStorage.disableMIDIDrumChannel == "true"
: true;
function shouldShowSnowflakes() {
const snowflakes = document.querySelector(".snowflakes");
if (gSnowflakes) {
snowflakes.style.visibility = "visible";
} else {
snowflakes.style.visibility = "hidden";
}
}
shouldShowSnowflakes();
// This code is not written specficially for readibility, it is a heavily used function and performance matters.
// If someone finds this code and knows a more performant way to do this (with proof of it being more performant)
// it may be replaced with the more performant code.
// Returns true if we should hide the user, and returns false when we should not.
function shouldHideUser(user) {
if (gHideBotUsers) {
if (user) {
if (user.tag && user.tag.text === "BOT") {
return true;
} else {
return false;
}
}
} else {
return false;
}
}
// 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) {
chat.endDM();
chat.endDM();
}
});
//Replies
var gReplyParticipant;
var gIsReplying = false;
var gMessageId;
gClient.on(`participant removed`, (part) => {
if (gIsReplying && part._id === gReplyParticipant._id) {
MPP.chat.cancelReply();
}
});
// 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 = $('<div class="participant-menu"></div>');
$("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
$('<div class="info"></div>')
.appendTo(menu)
.text(part._id)
.on("mousedown touchstart", (evt) => {
navigator.clipboard.writeText(part._id);
evt.target.innerText = "Copied!";
setTimeout(() => {
evt.target.innerText = part._id;
}, 2500);
});
// add menu items
if (gPianoMutes.indexOf(part._id) == -1) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Mute Notes",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
gPianoMutes.push(part._id);
if (localStorage) localStorage.pianoMutes = gPianoMutes.join(",");
$(part.nameDiv).addClass("muted-notes");
});
} else {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Unmute Notes",
)}</div>`,
)
.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) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Mute Chat",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
gChatMutes.push(part._id);
if (localStorage) localStorage.chatMutes = gChatMutes.join(",");
$(part.nameDiv).addClass("muted-chat");
});
} else {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Unmute Chat",
)}</div>`,
)
.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)
) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Mute Completely",
)}</div>`,
)
.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
) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Unmute Completely",
)}</div>`,
)
.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) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"End Direct Message",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
chat.endDM();
});
} else {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Direct Message",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
if (!gKnowsHowToDm) {
localStorage.knowsHowToDm = true;
gKnowsHowToDm = true;
new Notification({
target: "#piano",
duration: 20000,
title: window.i18nextify.i18next.t("How to DM"),
text: window.i18nextify.i18next.t(
"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.",
),
});
}
chat.startDM(part);
});
}
if (gCursorHides.indexOf(part._id) == -1) {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Hide Cursor",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
gCursorHides.push(part._id);
if (localStorage) localStorage.cursorHides = gCursorHides.join(",");
$(part.cursorDiv).hide();
});
} else {
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Show Cursor",
)}</div>`,
)
.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();
});
}
$(
`<div class="menu-item">${window.i18nextify.i18next.t(
"Mention",
)}</div>`,
)
.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) {
$(
`<div class="menu-item give-crown">${window.i18nextify.i18next.t(
"Give Crown",
)}</div>`,
)
.appendTo(menu)
.on("mousedown touchstart", function (evt) {
if (confirm("Give room ownership to " + part.name + "?"))
gClient.sendArray([{ m: "chown", id: part.id }]);
});
}
$(
`<div class="menu-item kickban">${window.i18nextify.i18next.t(
"Kickban",
)}</div>`,
)
.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) {
$(
`<div class="menu-item site-ban">${window.i18nextify.i18next.t(
"Site Ban",
)}</div>`,
)
.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) {
$(
`<div class="menu-item set-color">${window.i18nextify.i18next.t(
"Set Color",
)}</div>`,
)
.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) {
$(
`<div class="menu-item set-name">${window.i18nextify.i18next.t(
"Set Name",
)}</div>`,
)
.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 = $(
'<div class="notification"><div class="notification-body"><div class="title"></div>' +
'<div class="text"></div></div><div class="x" translated>X</div></div>',
);
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: window.i18nextify.i18next.t("Did you know!?!"),
text: window.i18nextify.i18next.t(
"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").html(
window.i18nextify.i18next.t("Volume") +
"<span translated>: " +
Math.floor(gPiano.audio.volume * 100) +
"%</span>",
);
} else localStorage.volume = gPiano.audio.volume;
window.gHasBeenHereBefore = localStorage.gHasBeenHereBefore || false;
if (!gHasBeenHereBefore) {
/*new Notification({
title: "Important Info",
html: "If you were not on multiplayerpiano.net or mppclone.com previously, you are now! This is due to an issue with the owner of multiplayerpiano.com, who has added a bunch of things in the website's code that has affected the site negatively. Since they are using our servers, it's best that you use this website. If you have any issues, please join our <a href=\"https://discord.com/invite/338D2xMufC\">Discord</a> and let us know!",
duration: -1
});*/
}
localStorage.gHasBeenHereBefore = true;
}
// 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 = $('<div class="info"></div>');
info.attr("roomname", room._id);
info.attr("roomid", room.id);
$("#room .more").append(info);
}
info.attr("translated", "");
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: window.i18nextify.i18next.t("Playing alone"),
html:
window.i18nextify.i18next.t(
"You are playing alone in a room by yourself, but you can always invite friends by sending them the link.",
) +
'<br><a href="' +
location.href +
'">' +
location.href +
"</a>",
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: window.i18nextify.i18next.t("Created a Room"),
html:
window.i18nextify.i18next.t(
"You can invite friends to your room by sending them the link.",
) +
'<br><a href="' +
location.href +
'">' +
location.href +
"</a>",
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) {
location.replace(
encodeURI(
`https://discord.com/api/oauth2/authorize?client_id=926633278100877393&redirect_uri=${location.origin}/?callback=discord&response_type=code&scope=identify email`,
),
);
});
})();
// 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) {
chat.endDM();
}
if (gIsReplying) {
chat.cancelReply();
}
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")
}
}
}
});*/
var messageCache = [];
return {
startDM: function (part) {
gIsDming = true;
gDmParticipant = part;
$("#chat-input")[0].placeholder = "Direct messaging " + part.name + ".";
},
endDM: function () {
gIsDming = false;
$("#chat-input")[0].placeholder = window.i18nextify.i18next.t(
"You can chat with this thing.",
);
},
startReply: function (part, id) {
$(`#msg-${gMessageId}`).css({
"background-color": "unset",
border: "1px solid #00000000",
});
gIsReplying = true;
gReplyParticipant = part;
gMessageId = id;
$("#chat-input")[0].placeholder = `Replying to ${part.name}`;
},
startDmReply: function (part, id) {
$(`#msg-${gMessageId}`).css({
"background-color": "unset",
border: "1px solid #00000000",
});
gIsReplying = true;
gIsDming = true;
gMessageId = id;
gReplyParticipant = part;
gDmParticipant = part;
$("#chat-input")[0].placeholder = `Replying to ${part.name} in a DM.`;
},
cancelReply: function () {
if (gIsDming) gIsDming = false;
gIsReplying = false;
$(`#msg-${gMessageId}`).css({
"background-color": "unset",
border: "1px solid #00000000",
});
$("#chat-input")[0].placeholder = window.i18nextify.i18next.t(
`You can chat with this thing.`,
);
},
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 (gIsReplying) {
if (gIsDming) {
gClient.sendArray([
{
m: "dm",
reply_to: gMessageId,
_id: gReplyParticipant._id,
message,
},
]);
setTimeout(() => {
MPP.chat.cancelReply();
}, 100);
} else {
gClient.sendArray([
{
m: "a",
reply_to: gMessageId,
_id: gReplyParticipant._id,
message,
},
]);
setTimeout(() => {
MPP.chat.cancelReply();
}, 100);
}
} else {
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 = `<li id="msg-${msg.id}">`;
var isSpecialDm = false;
if (msg.m === "dm") {
if (
msg.sender._id === gClient.user._id ||
msg.recipient._id === gClient.user._id
) {
liString += `<span class="reply"/>`;
}
} else {
liString += `<span class="reply"/>`;
}
if (gShowTimestampsInChat) liString += '<span class="timestamp"/>';
if (msg.m === "dm") {
if (msg.sender._id === gClient.user._id) {
//sent dm
liString += '<span class="sentDm"/>';
} else if (msg.recipient._id === gClient.user._id) {
//received dm
liString += '<span class="receivedDm"/>';
} else {
//someone else's dm
liString += '<span class="otherDm"/>';
isSpecialDm = true;
}
}
if (isSpecialDm) {
if (gShowIdsInChat) liString += '<span class="id"/>';
liString += '<span class="name"/><span class="dmArrow"/>';
if (gShowIdsInChat) liString += '<span class="id2"/>';
liString += '<span class="name2"/><span class="message"/>';
} else {
if (gShowIdsInChat) liString += '<span class="id"/>';
liString += '<span class="name"/>';
if (msg.r) liString += `<span class="replyLink"/>`;
liString += '<span class="message"/>';
}
var li = $(liString);
li.find(`.reply`).text("➦");
if (msg.r) {
var repliedMsg = messageCache.find((e) => e.id === msg.r);
if (!tabIsActive) {
if (repliedMsg?.p?._id === gClient.user._id) {
document.title = `You have received a reply!`;
youreReplied = true;
}
}
if (repliedMsg) {
li.find(".replyLink").text(
`${
repliedMsg.m === "dm"
? repliedMsg.sender.name
: repliedMsg.p.name
}`,
);
li.find(".replyLink").css({
background: `${
(repliedMsg?.m === "dm"
? repliedMsg?.sender?.color
: repliedMsg?.p?.color) ?? "gray"
}`,
});
li.find(".replyLink").on("click", (evt) => {
$("#chat-input").focus();
document
.getElementById(`msg-${repliedMsg?.id}`)
.scrollIntoView({ behavior: "smooth" });
$(`#msg-${repliedMsg?.id}`).css({
border: `1px solid ${
repliedMsg?.m === "dm"
? repliedMsg.sender?.color
: repliedMsg.p?.color
}80`,
"background-color": `${
repliedMsg?.m === "dm"
? repliedMsg.sender?.color
: repliedMsg.p?.color
}20`,
});
setTimeout(() => {
$(`#msg-${repliedMsg?.id}`).css({
"background-color": "unset",
border: "1px solid #00000000",
});
}, 5000);
});
} else {
li.find(".replyLink").text("➥ Unknown Message");
li.find(".replyLink").css({ background: "gray" });
}
}
//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 = window.i18nextify.i18next.t(
"You were mentioned!",
);
}
return `<span class="mention" style="background-color: ${user.color};">${nick}</span>`;
} 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;
}
//Adds copying _ids on click in chat
li.find(".id").on("click", (evt) => {
if (msg.m === "dm") {
navigator.clipboard.writeText(
msg.sender._id === gClient.user._id
? msg.recipient._id
: msg.sender._id,
);
li.find(".id").text("Copied");
setTimeout(() => {
li.find(".id").text(
(msg.sender._id === gClient.user._id
? msg.recipient._id
: msg.sender._id
).substring(0, 6),
);
}, 2500);
} else {
navigator.clipboard.writeText(msg.p._id);
li.find(".id").text("Copied");
setTimeout(() => {
li.find(".id").text(msg.p._id.substring(0, 6));
}, 2500);
}
});
li.find(".id2").on("click", (evt) => {
navigator.clipboard.writeText(msg.recipient._id);
li.find(".id2").text("Copied");
setTimeout(() => {
li.find(".id2").text(msg.recipient._id.substring(0, 6));
}, 2500);
});
//Reply button click event listener
li.find(".reply").on("click", (evt) => {
if (msg.m !== "dm") {
MPP.chat.startReply(msg.p, msg.id, msg.a);
setTimeout(() => {
$(`#msg-${msg.id}`).css({
border: `1px solid ${
msg?.m === "dm" ? msg.sender?.color : msg.p?.color
}80`,
"background-color": `${
msg?.m === "dm" ? msg.sender?.color : msg.p?.color
}20`,
});
}, 100);
setTimeout(() => {
$("#chat-input").focus();
}, 100);
} else {
if (msg.m === "dm") {
const replyingTo =
msg.sender._id === gClient.user._id
? msg.recipient
: msg.sender;
if (gClient.ppl[replyingTo._id]) {
MPP.chat.startDmReply(replyingTo, msg.id);
setTimeout(() => {
$(`#msg-${msg.id}`).css({
border: `1px solid ${
msg?.m === "dm" ? msg.sender?.color : msg.p?.color
}80`,
"background-color": `${
msg?.m === "dm" ? msg.sender?.color : msg.p?.color
}20`,
});
}, 100);
setTimeout(() => {
$("#chat-input").focus();
}, 100);
} else {
new Notification({
target: "#piano",
title: "User not found.",
text: "The user who you are trying to reply to in a DM is not found, so a DM could not be started.",
});
}
}
}
});
//put list element in chat
$("#chat ul").append(li);
messageCache.push(msg);
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) {
messageCache.shift();
$(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];
if (gDisableMIDIDrumChannel && channel == 9) {
return;
}
//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)";
}
outputs_ul.setAttribute("translated", "");
inputs_ul.setAttribute("translated", "");
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 #<c> 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: window.i18nextify.i18next.t("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: window.i18nextify.i18next.t(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 = window.i18nextify.i18next.t(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 = "<br><br><br><br><center>this space intentionally left blank</center><br><br><br><br>";
//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);";
setting.setAttribute("translated", "");
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 = "<br><br><br><br><center>this space intentionally left blank</center><br><br><br><br>";
//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 = window.i18nextify.i18next.t(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,
true,
html,
() => {
gOutputOwnNotes = !gOutputOwnNotes;
localStorage.outputOwnNotes = gOutputOwnNotes;
},
);
createSetting(
"disable-midi-drum-channel",
"Disable MIDI Drum Channel (channel 10)",
gDisableMIDIDrumChannel,
true,
html,
() => {
gDisableMIDIDrumChannel = !gDisableMIDIDrumChannel;
localStorage.disableMIDIDrumChannel = gDisableMIDIDrumChannel;
},
);
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();
}
},
);
createSetting(
"hide-bot-users",
"Hide all bots",
gHideBotUsers,
true,
html,
() => {
gHideBotUsers = !gHideBotUsers;
localStorage.hideBotUsers = gHideBotUsers;
},
);
if (new Date().getMonth() === 11) {
createSetting(
"snowflakes",
"Enable snowflakes",
gSnowflakes,
true,
html,
() => {
gSnowflakes = !gSnowflakes;
localStorage.snowflakes = gSnowflakes;
shouldShowSnowflakes();
},
);
}
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://multiplayerpiano.net",
);
}
gClient.start();
});
// misc
////////////////////////////////////////////////////////////////
// non-ad-free experience
/*(function() {
function adsOn() {
if(window.localStorage) {
var div = document.querySelector("#inclinations");
div.innerHTML = "Ads:<br>ON / <a id=\"adsoff\" href=\"#\">OFF</a>";
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:<br><a id=\"adson\" href=\"#\">ON</a> / OFF";
div.querySelector("#adson").addEventListener("click", adsOn);
}
if(window.localStorage) {
if(localStorage.ads === undefined || localStorage.ads === "true")
adsOn();
else
noAds();
} else {
adsOn();
}
})();*/