VisNotes MPP

Visualization of notes (based on Chacha-26 script)

  1. // ==UserScript==
  2. // @name VisNotes MPP
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.9.3
  5. // @description Visualization of notes (based on Chacha-26 script)
  6. // @author Hustandant#1917
  7. // @match *://multiplayerpiano.net/*
  8. // @icon https://github.com/Hustoroff/mpp/blob/main/icon.png?raw=true
  9. // @grant none
  10. // @license MIT
  11. // @run-at document-end
  12. // ==/UserScript==
  13. window.addEventListener('load', (event) => {
  14. //++++++++++++++++++ You can change this (Hot keys) ++++++++++++++++++++
  15.  
  16. const OnOff = "113"; //F2
  17. const FirstKey = "3"; //3
  18. const SecondKey = "Tab"; //Tab
  19.  
  20. //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  21.  
  22. var notes = 0, nps = 0, fps = 0;
  23.  
  24. async function ping() {
  25. let start = Date.now();
  26. try { await fetch(`${window.location.protocol}${MPP.client.uri.slice(4)}`) }
  27. catch(err) {}
  28. return (Date.now() - start);
  29. };
  30.  
  31. function fpsCount() { fps += 1; requestAnimationFrame(fpsCount) };
  32.  
  33. requestAnimationFrame(fpsCount);
  34.  
  35. if(!localStorage.getItem("speed")) localStorage.setItem("speed", 60);
  36. var noteSpeed = localStorage.getItem("speed"); //default 60 per sec
  37.  
  38. MPP.client.on("a", function(msg) {
  39. let message = msg.a.split(" ");
  40. if(message[0] == "speed" && msg.p.id == MPP.client.participantId && !isNaN(Number(message[1]))) {
  41. noteSpeed = (Number(message[1]) > 1000) ? 1000 : (Number(message[1]) <= 0) ? 1 : Number(message[1]);
  42. localStorage.setItem("speed", noteSpeed);
  43. document.getElementById("nspd").innerText = noteSpeed;
  44. }
  45. });
  46.  
  47. MPP.client.emit("notification", {
  48. title: "VisNotes MPP script (by Hustandant#1917)",
  49. id:"Script_notification",
  50. duration:20000,
  51. target:"#piano",
  52. html:`<p><h3><font id="f2" color="">F2</font> - show/hide notes window</h3></br></p><p><h3><font id="3d" color="">Tab+3</font> - on/off darkly window</h3></br></p><p><h4><font color="limegreen">${noteSpeed}</font> - current speed (<span style="background-color: black"><font color="red">to chat "speed" [min - 1 max - 1000]</font></span>)</h4></br></p><p><h5><span style="background-color: black">Example: "speed 60"</span></h5></br></p> Join our discord server: <a target="_blank" href="https://discord.gg/A3SDgxS2Q2">https://discord.gg/A3SDgxS2Q2<a>`
  53. });
  54.  
  55. const stat = document.createElement("div");
  56. stat.id = "stat_notes";
  57. stat.style.opacity = "1";
  58. stat.style.position = "fixed";
  59. stat.style["z-index"] = 150;
  60. stat.style.display = "block";
  61. stat.style.float = "right";
  62. stat.style.margin = "auto";
  63. stat.style.top = `${document.getElementById("piano").height}px`;
  64. stat.style["background-color"] = "rgba(137, 137, 137, 0.414)";
  65. stat.style["backdrop-filter"] = "blur(1px)";
  66. stat.style["font-size"] = "21px"
  67. stat.innerHTML = `Notes: <span id="notes">0</span> NPS: <span id="nps">0</span> Speed: <span id="nspd">${localStorage.getItem("speed")}</span> NQ: <span id="nquota">${MPP.noteQuota.points}</span> Ping: <span id="ping"></span> FPS: <span id="fps"></span>`;
  68. stat.style.marginLeft = `${String(document.getElementById("piano").offsetLeft + document.getElementById("piano").getElementsByTagName("canvas")[0].offsetLeft)}px`;
  69.  
  70.  
  71. const canvas = document.createElement("canvas");
  72. canvas.height = parseInt(document.getElementById("piano").style["margin-top"]);
  73. canvas.width = Math.round(MPP.piano.renderer.width - (MPP.piano.renderer.width - MPP.piano.keys.c7.rect.x2));
  74. canvas.id = "track_of_notes";
  75. canvas.style.opacity = "1";
  76. canvas.style.top = "0";
  77. canvas.style.display = "block";
  78. canvas.style.float = "right";
  79. canvas.style.position = "fixed";
  80. canvas.style.margin = "auto";
  81. canvas.style.marginLeft = `${String(document.getElementById("piano").offsetLeft + document.getElementById("piano").getElementsByTagName("canvas")[0].offsetLeft)}px`;
  82. canvas.style["z-index"] = 150;
  83.  
  84. const ctx = window.ctx = canvas.getContext("2d");
  85. const pixel = window.pixel = ctx.createImageData(document.getElementById("piano").querySelector("canvas").width,canvas.height);
  86. pixel.data.fill(0);
  87. let lastUpdate = 0, onlyevery = 4, counter = 0, prevTime = Date.now();
  88. const noteDB = {};
  89.  
  90. Object.keys(MPP.piano.keys).forEach(key => noteDB[key] = MPP.piano.keys[key].rect.x);
  91.  
  92. window.redraw = function() {
  93. if (lastUpdate <= canvas.height && counter++ % 4 == 0) {
  94. const currentTime = Date.now();
  95. const deltaTime = currentTime - prevTime;
  96.  
  97. ctx.globalCompositeOperation = "copy";
  98. ctx.drawImage(ctx.canvas, 0, -Math.ceil(deltaTime * (noteSpeed / 1000)));
  99. ctx.globalCompositeOperation = "source-over";
  100. ctx.putImageData(pixel, 0, canvas.height - 1);
  101.  
  102. prevTime = currentTime;
  103.  
  104. if (lastUpdate++ == 0) {
  105. pixel.data.fill();
  106. }
  107. }
  108. requestAnimationFrame(redraw);
  109. };
  110.  
  111. redraw();
  112. redraw(); // :)
  113. redraw(); // :)
  114. redraw(); // :)
  115.  
  116. window.showNote = function(note, col, ch = 0) {
  117. if (note in noteDB) {
  118. lastUpdate = 0;
  119. const idx = (noteDB[note]) * 4;
  120. if(note.split("").includes("s")) {
  121. let otS = ((MPP.piano.keys[note].rect.w - Math.round(MPP.piano.renderer.blackBlipWidth))/2)*4;
  122. for(let i=0; i<(MPP.piano.renderer.blackBlipWidth)*4; i+=4){
  123. pixel.data[idx + otS + i] = col[0];
  124. pixel.data[idx + otS + i + 1] = col[1];
  125. pixel.data[idx + otS + i + 2] = col[2];
  126. pixel.data[idx + otS + i + 3] = 255;
  127. }
  128. } else {
  129. let ot = (Math.round((MPP.piano.keys[note].rect.w - Math.round(MPP.piano.renderer.whiteBlipWidth))/2))*4;
  130. for(let i=0; i<(MPP.piano.renderer.whiteBlipWidth)*4; i+=4){
  131. pixel.data[idx + ot + i] = col[0];
  132. pixel.data[idx + ot + i + 1] = col[1];
  133. pixel.data[idx + ot + i + 2] = col[2];
  134. pixel.data[idx + ot + i + 3] = 255;
  135. }
  136. }
  137. }
  138. }
  139.  
  140. document.body.append(canvas);
  141. document.body.append(stat);
  142.  
  143. window.addEventListener('resize', resize);
  144.  
  145. function resize() {
  146. canvas.width = canvas.width;
  147. canvas.height = canvas.height;
  148. canvas.style.width = `${canvas.width / window.devicePixelRatio}px`;
  149. canvas.style.height = `${canvas.height / window.devicePixelRatio}px`;
  150. canvas.style.marginLeft = `${String(document.getElementById("piano").offsetLeft + document.getElementById("piano").getElementsByTagName("canvas")[0].offsetLeft)}px`;
  151. };
  152.  
  153. window.onload = () => {
  154. if(!localStorage.getItem("display")) localStorage.setItem("display", document.getElementById("track_of_notes").style.display);
  155. if(!localStorage.getItem("theme")) localStorage.setItem("theme", document.getElementById("track_of_notes").style["background-color"]);
  156.  
  157. document.getElementById("track_of_notes").style.display = localStorage.getItem("display");
  158. document.getElementById("track_of_notes").style["background-color"] = localStorage.getItem("theme");
  159. document.getElementById("3d").color = (localStorage.getItem("theme") == "rgb(16, 0, 0)") ? "limegreen" : "firebrick";
  160. document.getElementById("f2").color = (localStorage.getItem("display") == "block") ? "limegreen" : "firebrick";
  161. noteSpeed = (localStorage.getItem("speed")) ? localStorage.getItem("speed") : 60;
  162. };
  163.  
  164. window.addEventListener("keydown", function(key) {
  165. if(key.keyCode == OnOff) {
  166. document.getElementById("track_of_notes").style.display = (document.getElementById("track_of_notes").style.display == "block") ? "none" : "block";
  167. localStorage.setItem("display", document.getElementById("track_of_notes").style.display);
  168. document.getElementById("f2").color = (localStorage.getItem("display") == "block") ? "limegreen" : "firebrick";
  169. return;
  170. }
  171. });
  172.  
  173. function runOnKeys(func, ...codes) {
  174. let pressed = new Set();
  175.  
  176. document.addEventListener('keydown', function(event) {
  177. pressed.add(event.key);
  178.  
  179. for (let code of codes) if (!pressed.has(code)) return;
  180. pressed.clear();
  181. func();
  182. });
  183.  
  184. document.addEventListener('keyup', function(event) { pressed.delete(event.key) });
  185. };
  186.  
  187. runOnKeys(() => {
  188. document.getElementById("track_of_notes").style["background-color"] = (document.getElementById("track_of_notes").style["background-color"] == "rgb(16, 0, 0)") ? "" : "rgb(16, 0, 0)";
  189. localStorage.setItem("theme", document.getElementById("track_of_notes").style["background-color"]);
  190. document.getElementById("3d").color = (localStorage.getItem("theme") == "rgb(16, 0, 0)") ? "limegreen" : "firebrick";
  191. },
  192. FirstKey,
  193. SecondKey
  194. );
  195. function stats() { document.getElementById("notes").innerText = notes; document.getElementById("nquota").innerText = MPP.noteQuota.points };
  196.  
  197. function grad(nq, nqmax) { document.getElementById("nquota").style.color = `rgb(255, ${Math.round((nq/nqmax)*255)}, ${Math.round((nq/nqmax)*255)})` };
  198.  
  199. setInterval(async () => {
  200. document.getElementById("nps").innerText = nps;
  201. document.getElementById("nquota").innerText = MPP.noteQuota.points;
  202. document.getElementById("ping").innerText = await ping();
  203. document.getElementById("fps").innerText = fps;
  204. fps = nps = 0;
  205. grad(MPP.noteQuota.points, MPP.noteQuota.max);
  206. }, 1000);
  207.  
  208. const colcache = Object.create(null);
  209. MPP.piano.renderer.__proto__.vis = MPP.piano.renderer.__proto__.visualize;
  210. MPP.piano.renderer.__proto__.visualize = function (n, c, ch) {
  211. notes += 1;
  212. nps += 1;
  213. stats();
  214. grad(MPP.noteQuota.points, MPP.noteQuota.max);
  215. this.vis(n,c,ch);
  216. let co = c in colcache ? colcache[c] : Object.freeze(colcache[c] = [c[1]+c[2], c[3]+c[4], c[5]+c[6]].map(x => parseInt(x, 16)));
  217. showNote(n.note, co);
  218. };
  219. });