Compare commits

...

18 Commits

Author SHA1 Message Date
lamp 840490b362 ip as default nick 2022-01-17 22:30:17 -06:00
lamp 7c8741eb62 🤦‍♂️ 2022-01-17 22:28:52 -06:00
lamp e99fa4cbab fix 2022-01-13 22:27:50 -06:00
lamp d2e24df6f4 Update 'readme.md' 2021-12-03 15:52:11 -06:00
lamp 3e5296ac25 Merge branch 'master' of gitea.moe:lamp/piano 2021-12-03 01:35:30 -06:00
lamp 8112a65204 fix 2021-12-03 01:34:41 -06:00
lamp 3537634d17 in case more than 256 connections 2021-12-02 23:34:17 -08:00
lamp 42eb01257b Merge branch 'master' of gitea.moe:lamp/piano 2021-12-03 01:23:36 -06:00
lamp 761a33e540 fix id generation 2021-12-02 23:23:26 -08:00
lamp c13ba1839d front not ignore self note 2021-12-03 01:03:59 -06:00
lamp 6b8e0e2171 Add 'readme.md' 2021-12-02 22:59:14 -06:00
lamp cdd0552c78 reorganize files 2021-12-02 20:28:16 -08:00
lamp 171309f42c fix 2021-12-02 20:24:20 -08:00
lamp 24e15b12b5 fix 2021-12-02 20:23:10 -08:00
lamp 470e8e5937 env port & addr 2021-12-02 20:19:24 -08:00
lamp e2e3f1d35e xff 2021-12-02 20:15:12 -08:00
lamp 239a65a241 fix notes, chat log 2021-12-02 19:56:31 -08:00
lamp 33033e150c nick work 2021-12-02 19:21:33 -08:00
7 changed files with 88 additions and 73 deletions
-62
View File
@@ -1,62 +0,0 @@
var express = require("express");
var qs = require("qs");
var {WebSocketServer} = require("ws");
var app = express();
var server = app.listen(924);
app.get('*', express.static("../frontend"));
var wss = new WebSocketServer({
server,
clientTracking: true
});
wss.broadcast = function (msg) {
if (typeof msg == "object") msg = JSON.stringify(msg);
for (let ws of wss.clients) ws.send(msg);
}
wss.chatlog = [];
wss.on("connection", (ws, req) => {
req.query = qs.parse(req.url.substr(req.url.indexOf('?')));
ws.user = {
id: wss.clients.size,
uid: req.socket.remoteAddress,
nick: req.query.nick || "potato-chan",
color: [Math.floor(Math.random()*255),Math.floor(Math.random()*255),Math.floor(Math.random()*255)]
}
ws.send(JSON.stringify({
type: "load",
id: ws.user.id,
uid: ws.user.uid,
users: Array.from(wss.clients).map(x => x.user),
chatlog: []
}));
wss.broadcast({type: "chat", message: {type: "join", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick}});
ws.on("close", () => wss.broadcast({type: "chat", message: {type: "leave", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick}}));
ws.on("message", (msg, isBinary) => {
if (isBinary) {
wss.broadcast(Buffer.concat([msg, Buffer.from([ws.user.id])]));
} else {
msg = msg.toString();
try {
msg = JSON.parse(msg);
} catch (error) { return }
console.log(msg);
switch (msg.type) {
case "chat":
let message = {type: "message", content: msg.message, user: {uid: ws.user.uid, nick: ws.user.nick, color: ws.user.color}};
wss.broadcast({type: "chat", message});
wss.chatlog.push(message);
break;
case "nick":
break;
}
}
});
});
+1 -1
View File
@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>Piano | DayDun</title>
<title>Piano</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet">
<link href="style.css" rel="stylesheet">
<script src="main.js"></script>
+1 -7
View File
@@ -592,7 +592,7 @@ class User {
}
class Networker {
constructor(url = `ws://${location.host}`) {
constructor(url = location.origin.replace("http", "ws")) {
if (localStorage.nick) url += "?nick=" + encodeURIComponent(localStorage.nick);
this.ws = new WebSocket(url);
@@ -678,28 +678,22 @@ class Networker {
let key = dv.getUint8(1);
let velocity = dv.getUint8(2) / 255;
let id = dv.getUint8(3);
if (id != this.id) {
piano.keyDown(key, velocity, id);
}
break;
}
case 1: { // Key up
let key = dv.getUint8(1);
let sustain = dv.getUint8(2);
let id = dv.getUint8(3);
if (id != this.id) {
piano.keyUp(key, sustain, id);
}
break;
}
case 2: { // Sustain release
let id = dv.getUint8(1);
if (id != this.id) {
piano.releaseSustain(id);
}
}
}
}
keyDown(key, velocity) {
let buffer = new ArrayBuffer(3);
View File
+5
View File
@@ -0,0 +1,5 @@
The frontend was stolen from [DayDun](https://daydun.com/piano/) and the backend re-implemented from scratch. Rooms and rate limit functionality was stripped out, each connection is a different user, and raw IP addresses are used for user ids.
The notable thing about this and DayDun's piano is that note events are simply, individually, immediately broadcasted in a minimal binary format, and clients _instantly_ play notes they receive; unlike Brandon Lockaby's Multiplayer Piano which buffers note events with timing data into JSON that's sent every 200ms and then played exactly _one second_ after they actually happened. (PianoRhythm does something similar.) Doing this preserves the exact note timing regardless of networking quality (unless it takes longer than a second for the data to get through), which is fine for listening to other players, but a problem when playing together. With this piano, notes go directly through as fast as possible, which is perfect for local networks, and reveals the true networking quality and latency over the internet.
It's also super simple to run if you do want to use it on your local network; just download, `npm install`, `node server.js` and connect to it (default port is 924).
+78
View File
@@ -0,0 +1,78 @@
var express = require("express");
var qs = require("qs");
var proxyaddr = require("proxy-addr");
var {WebSocketServer} = require("ws");
var app = express();
app.set("trust proxy", "loopback");
app.use(express.static("frontend"));
var server = app.listen(process.env.PORT || 924, process.env.ADDRESS);
var wss = new WebSocketServer({server, clientTracking: true});
var chatlog = [];
wss.on("connection", (ws, req) => {
req.ip = proxyaddr(req, app.get("trust proxy"));
req.query = qs.parse(req.url.substr(req.url.indexOf('?')+1));
function broadcast(msg, excludeSelf) {
if (typeof msg == "object" && !(msg instanceof Buffer)) msg = JSON.stringify(msg);
for (let ows of wss.clients) if (!(ows == ws && excludeSelf)) ows.send(msg);
}
function broadcastChat(message) {
broadcast({type: "chat", message});
chatlog.push(message);
if (chatlog.length >= 100) chatlog.shift();
}
ws.user = {
uid: req.ip,
nick: req.query.nick || req.ip,
color: [Math.floor(Math.random()*256),Math.floor(Math.random()*256),Math.floor(Math.random()*256)]
}
let t = Array.from(wss.clients).map(ws => ws.user.id);
for (let i = 0; i < 256; i++) if (!t.includes(i)) { ws.user.id = i; break; }
if (ws.user.id == null) return ws.close();
console.log("join", ws.user);
ws.send(JSON.stringify({
type: "load",
id: ws.user.id,
uid: ws.user.uid,
users: Array.from(wss.clients).map(x => x.user),
chatlog
}));
broadcast({type: "join", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick, color: ws.user.color}, true);
broadcastChat({type: "join", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick});
ws.on("close", () => {
console.log("leave", ws.user);
broadcast({type: "leave", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick, color: ws.user.color}, true);
broadcastChat({type: "leave", id: ws.user.id, uid: ws.user.uid, nick: ws.user.nick})
});
ws.on("message", (msg, isBinary) => {
if (isBinary) {
broadcast(Buffer.concat([msg, Buffer.from([ws.user.id])]), true);
} else {
msg = msg.toString();
try {
msg = JSON.parse(msg);
} catch (error) { return }
console.log(msg);
switch (msg.type) {
case "chat":
broadcastChat({type: "message", content: msg.message, user: ws.user});
break;
case "nick":
ws.user.nick = msg.nick;
broadcast({type: "nick", nick: ws.user.nick, id: ws.user.id, uid: ws.user.uid});
broadcastChat({type: "nick", nick: ws.user.nick, id: ws.user.id});
break;
}
}
});
});