Compare commits
4 Commits
74b8b377c8
...
a8c29e9090
Author | SHA1 | Date |
---|---|---|
Bailey Stevens | a8c29e9090 | |
Bailey Stevens | 86e91c04b4 | |
Bailey Stevens | 029c3be981 | |
Bailey Stevens | b6ae2bbe33 |
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: [
|
||||
|
|
Loading…
Reference in New Issue