Rithmos / app.py
Ikaros
refactor: add comprehensive error logging to server
0bd57b6
import asyncio
import websockets
import json
import numpy as np
import traceback # Import the traceback module
from music_generator import MusicGenerator
# --- WebSocket Server Setup ---
# In-memory storage for connected clients
clients = {
"webapp": set()
}
audio_source = None
# --- Main Application Logic ---
def initialize_dependencies():
"""Loads all necessary files and initializes objects."""
global notes, generator
print("Initializing dependencies...")
try:
# It's better to load files relative to the script's location
import os
dir_path = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(dir_path, 'consonance_matrix.json')) as f:
consonance_matrix = np.array(json.load(f))
notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
generator = MusicGenerator(len(notes))
print("Dependencies initialized successfully.")
return True
except Exception as e:
print("--- CRITICAL: FAILED TO INITIALIZE DEPENDENCIES ---")
print(traceback.format_exc())
print("----------------------------------------------------")
return False
async def broadcast_to_webapps(message):
"""Sends a message to all connected webapp clients."""
if clients["webapp"]:
tasks = [client.send(message) for client in clients["webapp"]]
await asyncio.gather(*tasks, return_exceptions=True)
async def handle_audio_data(data):
"""Mocks the audio analysis."""
import random
detected_chord = random.choice(notes)
predicted_chord = random.choice(notes)
key = "C Major"
analysis_result = {
"type": "analysis_update",
"current_chord": detected_chord,
"predicted_chord": predicted_chord,
"musical_key": key
}
await broadcast_to_webapps(json.dumps(analysis_result))
# --- WebSocket Connection Management ---
async def connection_handler(websocket, path):
"""Handles incoming WebSocket connections with robust error logging."""
global audio_source
print(f"New client connected: {websocket.remote_address}")
try:
initial_message = await websocket.recv()
message_data = json.loads(initial_message)
client_type = message_data.get("type")
if client_type == "extension_hello":
if audio_source is not None:
await audio_source.close(code=1012, reason="New extension connected.")
audio_source = websocket
print("Audio capture extension connected.")
await websocket.send(json.dumps({"status": "connected", "role": "audio_source"}))
await broadcast_to_webapps(json.dumps({"type": "status_update", "message": "Audio source connected."}))
elif client_type == "webapp_hello":
clients["webapp"].add(websocket)
print("Web app client connected.")
await websocket.send(json.dumps({"status": "connected", "role": "viewer"}))
status_msg = "Audio source connected." if audio_source else "Waiting for audio source..."
await websocket.send(json.dumps({"type": "status_update", "message": status_msg}))
else:
print(f"Unknown client type: {client_type}. Disconnecting.")
return
async for message in websocket:
if websocket == audio_source:
await handle_audio_data(message)
except websockets.exceptions.ConnectionClosed as e:
print(f"Connection closed normally for {websocket.remote_address}. Code: {e.code}, Reason: {e.reason}")
except Exception as e:
# THIS IS THE CRITICAL PART
print(f"--- UNEXPECTED SERVER ERROR in connection_handler ---")
print(f"Error Type: {type(e).__name__}")
print(f"Error Message: {e}")
print("Traceback:")
print(traceback.format_exc())
print("----------------------------------------------------")
# Close the connection with a specific error code if it's not already closed
if not websocket.closed:
await websocket.close(code=1011, reason="Internal Server Error")
finally:
if websocket in clients["webapp"]:
clients["webapp"].remove(websocket)
if websocket == audio_source:
audio_source = None
print("Audio capture extension disconnected.")
await broadcast_to_webapps(json.dumps({"type": "status_update", "message": "Audio source disconnected."}))
async def main():
"""Initializes dependencies and starts the WebSocket server."""
if not initialize_dependencies():
print("Server cannot start due to initialization failure.")
return
websocket_port = 7860
print(f"Starting WebSocket server on port {websocket_port}...")
async with websockets.serve(connection_handler, "0.0.0.0", websocket_port):
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())