Compare commits

...

4 Commits

7 changed files with 185 additions and 76 deletions

View File

@ -10,45 +10,66 @@ import websockets
from .mmelodies_pb2 import UpdateRequest, Update, Parameter
CONNECTIONS = []
CONNECTIONS = {}
def connections(*paths):
if not paths:
paths = CONNECTIONS.keys()
clients = []
for path in paths:
clients.extend(CONNECTIONS.get(path, []))
return clients
async def request_handler(ws, writer):
print(f"connection on {ws.path}")
CONNECTIONS.append(ws)
if ws.path not in CONNECTIONS.keys():
CONNECTIONS[ws.path] = []
CONNECTIONS[ws.path].append(ws)
while True:
try:
message = await ws.recv()
request = UpdateRequest()
request.ParseFromString(message)
print(request)
if request.scope_samples:
writer.write("scope;".encode())
writer.write(b"scope;")
if request.param_refresh:
writer.write(b"params;")
for param in request.param_changes:
writer.write(f"set {param.id} {param.value};".encode())
writer.write("params;\n".encode())
writer.write(b"\n")
await writer.drain()
except TypeError as e:
print(e)
print(repr(e))
except websockets.ConnectionClosedOK:
CONNECTIONS.remove(ws)
CONNECTIONS[ws.path].remove(ws)
break
except websockets.ConnectionClosedError as e:
print(e)
CONNECTIONS.remove(ws)
CONNECTIONS[ws.path].remove(ws)
break
async def update_handler(reader):
print("pd connected!")
while True:
message = await reader.readline()
if not message:
raise Exception("Received an empty message, connection likely closed.")
try:
message = (await reader.readline()).decode().strip(";\n")
message = message.decode().strip(";\n")
update = Update()
parts = message.split(" ")
@ -57,30 +78,57 @@ async def update_handler(reader):
param = update.params.add()
param.id = param_id
param.value = int(value)
clients = connections()
elif parts[0] == "scope":
update.scope_samples.extend([float(part) for part in parts[1:]])
clients = connections("/api/display")
elif parts[0] == "change":
param = update.params.add()
param.id = int(parts[1])
param.value = int(parts[2])
clients = connections()
else:
print(parts[0])
continue
if parts[0] == "scope":
update.scope_samples.extend([int(part) for part in parts[1:]])
print(repr(update))
websockets.broadcast(CONNECTIONS, update.SerializeToString())
websockets.broadcast(clients, update.SerializeToString())
except Exception as e:
print(e)
print(repr(e))
async def _main(listen_port, pd_port):
if pd_port[0] == "unix":
path = pd_port[1]
print(f"connecting to Pd on {path.absolute()}")
pd_reader, pd_writer = await asyncio.open_unix_connection(
path=path, happy_eyeballs_delay=0.25
)
for _ in range(5):
try:
print(f"connecting to Pd on {path.absolute()}")
pd_reader, pd_writer = await asyncio.open_unix_connection(
path=path, happy_eyeballs_delay=0.25
)
except OSError as e:
print(e)
await asyncio.sleep(5)
else:
break
else:
raise Exception("Could not connect to pd!")
if pd_port[0] == "tcp":
host, port = pd_port[1]
print(f"connecting to Pd on {host}:{port}")
pd_reader, pd_writer = await asyncio.open_connection(
host=host, port=port, happy_eyeballs_delay=0.25
)
for _ in range(5):
try:
print(f"connecting to Pd on {host}:{port}")
pd_reader, pd_writer = await asyncio.open_connection(
host=host, port=port, happy_eyeballs_delay=0.25
)
except OSError as e:
print(e)
await asyncio.sleep(5)
else:
break
else:
raise Exception("Could not connect to pd!")
ws_handler = partial(request_handler, writer=pd_writer)
@ -93,8 +141,9 @@ async def _main(listen_port, pd_port):
print(f"listening on {host}:{port}")
ws = websockets.serve(ws_handler, host=host, port=port)
async with ws:
await update_handler(pd_reader)
async with ws:
while True:
await update_handler(pd_reader)
def port(arg: str):
@ -111,6 +160,8 @@ def port(arg: str):
assert path.parent.exists()
return kind, path
raise ValueError(f"Unknown port type {kind}")
def main():
parser = argparse.ArgumentParser()

View File

@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fmmelodies.proto\"&\n\tParameter\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\r\"`\n\rUpdateRequest\x12\x1a\n\rscope_samples\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12!\n\rparam_changes\x18\x02 \x03(\x0b\x32\n.ParameterB\x10\n\x0e_scope_samples\";\n\x06Update\x12\x15\n\rscope_samples\x18\x01 \x03(\x11\x12\x1a\n\x06params\x18\x02 \x03(\x0b\x32\n.Parameterb\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fmmelodies.proto\"&\n\tParameter\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\r\"\x8e\x01\n\rUpdateRequest\x12\x1a\n\rscope_samples\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x1a\n\rparam_refresh\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12!\n\rparam_changes\x18\x02 \x03(\x0b\x32\n.ParameterB\x10\n\x0e_scope_samplesB\x10\n\x0e_param_refresh\";\n\x06Update\x12\x15\n\rscope_samples\x18\x01 \x03(\x02\x12\x1a\n\x06params\x18\x02 \x03(\x0b\x32\n.Parameterb\x06proto3')
@ -47,8 +47,8 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_PARAMETER._serialized_start=19
_PARAMETER._serialized_end=57
_UPDATEREQUEST._serialized_start=59
_UPDATEREQUEST._serialized_end=155
_UPDATE._serialized_start=157
_UPDATE._serialized_end=216
_UPDATEREQUEST._serialized_start=60
_UPDATEREQUEST._serialized_end=202
_UPDATE._serialized_start=204
_UPDATE._serialized_end=263
# @@protoc_insertion_point(module_scope)

View File

@ -24,8 +24,7 @@
Group = "nginx";
Type = "simple";
ExecStart = "${pkgs.mmelodies.backend}/bin/mmelodies -l unix:/run/mmelodies/backend.sock";
StandardOutput = "journal";
StandardError = "journal";
Restart = "always";
};
};

View File

@ -7,10 +7,11 @@ message Parameter {
message UpdateRequest {
optional bool scope_samples = 1;
optional bool param_refresh = 3;
repeated Parameter param_changes = 2;
}
message Update {
repeated sint32 scope_samples = 1;
repeated float scope_samples = 1;
repeated Parameter params = 2;
}

View File

@ -1,28 +1,88 @@
import { UpdateRequest, Update } from "/proto/mmelodies.proto";
import { Update, UpdateRequest } from "/proto/mmelodies.proto";
// Pre-init protobuf request buffer
const message = UpdateRequest.create({ scopeSamples: true });
const request = UpdateRequest.encode(message).finish();
const dest = document.querySelector("audio#stream");
const canvas = document.getElementById("scope");
const canvasCtx = canvas.getContext("2d");
const url = new URL("/api", location.href);
const send = document.querySelector("#send");
url.protocol = "ws";
if (location.protocol.startsWith("https")) {
url.protocol = "wss";
let socket = undefined;
function draw(samples: Float32Array) {
canvasCtx.fillStyle = "rgb(200, 200, 200)";
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0, 0, 0)";
canvasCtx.beginPath();
const sliceWidth = (canvas.width * 1.0) / samples.length;
let x = 0;
for (let i = 0; i < samples.length; i++) {
const v = samples[i] + 0.5;
const y = v * canvas.height;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(canvas.width, canvas.height / 2);
canvasCtx.stroke();
}
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
// Listen for messages
socket.addEventListener("message", (event) => {
send.value = "Pong";
const message = Update.decode(new Uint8Array(event.data));
console.log(message.scopeSamples);
});
send.addEventListener("click", function () {
const message = UpdateRequest.create({scopeSamples: true});
console.log(message);
const buffer = UpdateRequest.encode(message).finish();
console.log(buffer);
send.value = "Ping";
socket.send(buffer);
// Subscribe to the audioprocess event
function connect_ws() {
const url = new URL("/api/display", location.href);
url.protocol = "ws";
if (location.protocol.startsWith("https")) {
url.protocol = "wss";
}
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
ws.addEventListener("open", (event) => {
socket.send(request);
});
ws.addEventListener("error", (event) => {
console.log(event);
});
ws.addEventListener("close", () => {
setTimeout(() => {
socket = connect_ws();
}, 2500);
});
// Listen for messages
ws.addEventListener("message", (event) => {
send.hidden = true;
const message = Update.decode(new Uint8Array(event.data));
const samples = new Float32Array(message.scopeSamples);
if (samples.length !== 0) {
draw(samples);
}
});
return ws;
}
send.addEventListener("click", function (event) {
socket = connect_ws();
send.disabled = true;
});

View File

@ -1,17 +1,15 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Andisco</title>
<link rel="stylesheet" href="/shared.css" />
<link rel="manifest" href="/andisco.json" />
</head>
<body>
<h1>Andisco display</h1>
<input type="button" id="send" value="Ping">
</body>
</html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Andisco</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Andisco display</h1>
<input type="button" id="send" value="Start">
<div id="waveform"></div>
<canvas id="scope" height=256 width=400></canvas>
</body>
</html>

View File

@ -44,11 +44,11 @@ module.exports = (env) => {
],
devServer: {
proxy: {
'/api': {
target: 'ws://localhost:8001',
ws: true // important
"/api": {
target: "ws://localhost:8001",
ws: true, // important
},
}
},
},
module: {
rules: [