Compare commits

..

No commits in common. "main" and "multiplayer-test" have entirely different histories.

2 changed files with 30 additions and 28 deletions

View File

@ -1,9 +1,12 @@
# CW Generator # CW Generator
A simple application for generating self-made morse / CW audio files. All it features is a button as A simple application for generating self-made morse / CW audio files. All it features is a button as
a morse key and allows you to export this as a WAV. Optionally, a MIDI keyboard can be used as a a morse key and allows you to export this as a WAV. Optionally, a MIDI keyboard can be used as a
morse key (since godot 4.4 even on web). morse key.
## Known Limitations / TODO ## Known Limitations / TODO
MIDI support only works locally for now.
* webmidi might be an option: https://gist.github.com/srejv/b7198e25587e2d8e0a66860781b56852
Currently we can only export wav, as Godot itself doesn't include any encoders. Having the ability Currently we can only export wav, as Godot itself doesn't include any encoders. Having the ability
to export as mp3, ogg or opus would be nice, but external libraries are kind of a hassle. opus would to export as mp3, ogg or opus would be nice, but external libraries are kind of a hassle. opus would
be the nicest format, but doesn't seem to work with older Iphones. be the nicest format, but doesn't seem to work with older Iphones.

View File

@ -1,20 +1,18 @@
import asyncio import asyncio
import datetime
import json import json
import logging import logging
import re import re
from websockets.asyncio.connection import broadcast
from websockets.asyncio.server import serve from websockets.asyncio.server import serve
__VERSION__ = "0.0.1" __VERSION__ = "0.0.1"
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s" format="%(asctime)s %(name)s %(levelname)s %(message)s"
) )
LOG = logging.getLogger()
class Client: class Client:
freqs = {} freqs = {}
@ -25,7 +23,7 @@ class Client:
self.curr_freq = None self.curr_freq = None
async def handle(self): async def handle(self):
LOG.info(">>> New client %s connected", self.client) print(f" >>> New client {self.client} connected")
exc = None exc = None
try: try:
await self._handle_client() await self._handle_client()
@ -33,7 +31,7 @@ class Client:
exc = e exc = e
finally: finally:
# FIXME: basically handle disconnect / leave from room # FIXME: basically handle disconnect / leave from room
LOG.info("<<< Client %s id %s disconnected: %s", self.client, self.id, exc) print(f" <<< Client {self.client} id {self.id} disconnected: {exc}")
if self.curr_freq: if self.curr_freq:
await self._leave_room() await self._leave_room()
@ -51,19 +49,18 @@ class Client:
async def _handle_client(self): async def _handle_client(self):
await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__) await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
async for data in self.websocket: async for data in self.websocket:
print(f" <-- client {self.client} sent {repr(data)}")
try: try:
data = json.loads(data) data = json.loads(data)
except json.JSONDecodeError: except json.JSONDecodeError:
self._send_error("Could not decode message, invalid json") self._send_error("Could not decode message, invalid json")
LOG.error("client %s sent broken data %s", self.client, repr(data))
continue continue
if not isinstance(data, dict) or "cmd" not in data: if not isinstance(data, dict) or "cmd" not in data:
await self._send_error("Invalid format in json") await self._send_error("Invalid format in json")
LOG.error("client %s sent broken data (no cmd key in data) %s", self.client, repr(data))
continue continue
LOG.info("client %s wrote: %s", self.client, data) print(f"{datetime.datetime.now()} {self.client} wrote:", data)
match data["cmd"]: match data["cmd"]:
case "quit": case "quit":
@ -127,7 +124,7 @@ class Client:
self.curr_freq = freq self.curr_freq = freq
self.freqs[freq].append(self) self.freqs[freq].append(self)
# FIXME: do we need locking here? # FIXME: do we need locking here?
LOG.debug("FREQ %s %s %s", self.curr_freq, freq, self.freqs) print("FREQ", self.curr_freq, freq, self.freqs)
await self._send(type="join", freq=self.curr_freq, self_id=self.id, await self._send(type="join", freq=self.curr_freq, self_id=self.id,
other_players=[c.id for c in self._others(freq)]) other_players=[c.id for c in self._others(freq)])
await self._send_to_group(self._others(freq), type="player-joined", player=self.id) await self._send_to_group(self._others(freq), type="player-joined", player=self.id)
@ -145,18 +142,19 @@ class Client:
type="morse-state", state=data["state"], from_player=self.id) type="morse-state", state=data["state"], from_player=self.id)
async def _leave_room(self): async def _leave_room(self):
if self.curr_freq: if not self.curr_freq:
await self._send_to_group(self._others(self.curr_freq), self._send_error("You are not on a frequency")
type="player-left", player=self.id) return
try:
self.freqs[self.curr_freq].remove(self) await self._send_to_group(self._others(self.curr_freq),
except ValueError: type="player-left", player=self.id)
LOG.warning("Player %s was not in freq %s", self.id, self.curr_freq) try:
if not self.freqs[self.curr_freq]: self.freqs[self.curr_freq].remove(self)
del self.freqs[self.curr_freq] except ValueError:
self.curr_freq = None print(f"Warning: Player {self.id} was not in freq {self.curr_freq}")
else: if not self.freqs[self.curr_freq]:
LOG.warning("Client %s is not on a frequency, sending a 'leave' nontheless", self.client) del self.freqs[self.curr_freq]
self.curr_freq = None
try: try:
await self._send(type="leave") await self._send(type="leave")
@ -168,17 +166,18 @@ class Client:
async def _send(self, ignore_exceptions=False, **kwargs): async def _send(self, ignore_exceptions=False, **kwargs):
data = json.dumps(kwargs).encode() data = json.dumps(kwargs).encode()
LOG.debug("--> sending out to %s: %s", self.client, data) print(f" --> sending out to {self.client}: {data}")
try: try:
await self.websocket.send(json.dumps(kwargs).encode() + b"\n") await self.websocket.send(json.dumps(kwargs).encode() + b"\n")
except Exception as e: except Exception as e:
LOG.error("Error sending data to %s: %s", self.client, e) print(f"Error sending data to {self.client}: {e}")
if not ignore_exceptions: if not ignore_exceptions:
raise raise
async def _send_to_group(self, group, **kwargs): async def _send_to_group(self, group, **kwargs):
LOG.info("broadcast() to %s clients: %s", len(group), kwargs) async with asyncio.TaskGroup() as tg:
broadcast([c.websocket for c in group], json.dumps(kwargs).encode() + b"\n") for member in group:
tg.create_task(member._send(ignore_exceptions=True, **kwargs))
async def _send_error(self, msg: str): async def _send_error(self, msg: str):
await self._send(type="error", message=msg) await self._send(type="error", message=msg)
@ -201,5 +200,5 @@ async def main():
if __name__ == "__main__": if __name__ == "__main__":
LOG.info("Starting server") print("Starting server")
asyncio.run(main()) asyncio.run(main())